Public API
Current v1 REST API reference for posts, sites, and webhooks.
This page reflects the routes currently exposed under
/api/v1. At the moment, the public v1 API includes posts, sites, and webhook management only.
Quick Start
Base URL
https://postion.app/api/v1Authentication
All requests must include a bearer token:
Authorization: Bearer pk_your_api_key_hereNew API keys are shown only once after creation. Store the raw key securely when you create it.
Rate Limits
The API is limited to 100 requests per minute per API key and endpoint. Responses include:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset
Scopes
Use the smallest scope set that fits your integration:
| Scope | Required for |
|---|---|
posts:read | GET /posts, GET /posts/{id} |
posts:write | POST /posts, PUT /posts/{id} |
posts:delete | DELETE /posts/{id} |
sites:read | GET /sites |
webhooks:read | GET /webhooks |
webhooks:write | POST /webhooks |
Response Conventions
Most successful responses use a data envelope:
{
"data": {}
}List endpoints may also include pagination metadata:
{
"data": [],
"pagination": {
"page": 1,
"limit": 10,
"total": 42,
"pages": 5
}
}Validation and auth failures return an error string, and some endpoints also return details or resetTime.
Endpoints
GET /api/v1/posts
List the authenticated user's posts.
Query parameters
| Name | Type | Default | Notes |
|---|---|---|---|
page | integer | 1 | Minimum 1 |
limit | integer | 10 | Range 1-100 |
siteId | string | - | Filter to one site |
published | boolean | - | Use true or false |
search | string | - | Case-insensitive search in title and description |
Example
curl "https://postion.app/api/v1/posts?page=1&limit=10&published=true" \
-H "Authorization: Bearer pk_your_api_key_here"Each item includes:
id,title,description,slug,imagestatus,published,public,isPaidpricingsitetagsstats.likes,stats.comments,stats.bookmarkscreatedAt,updatedAt
POST /api/v1/posts
Create a post for a site you own.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | Yes | Max length 200 |
siteId | string | Yes | Must belong to the API key owner |
content | JSON | No | Rich editor payload or your own JSON structure |
description | string | No | Short summary |
slug | string | No | Auto-generated from title if omitted |
image | string | No | Must be a valid URL |
status | string | No | Defaults to draft |
published | boolean | No | Defaults to model default |
tags | string[] | No | Missing tags are created automatically |
public | boolean | No | Defaults to true |
isPaid | boolean | No | Defaults to false |
pricing.price | number | No | Required when pricing is sent |
pricing.currency | string | No | 3-letter currency code, default USD |
pricing.isPaid | boolean | No | Defaults to false |
pricing.previewLength | integer | No | Optional preview length |
Notes
- Slugs are unique per site. A duplicate slug returns
409 Conflict. - If you send
status: "published"withoutpublished, the API also marks the post as published. The same applies todraft. - If no slug can be generated from the title, the API falls back to an auto-generated slug.
- Decimal values inside
pricingmay be serialized back as strings in responses.
Example
{
"title": "API launch checklist",
"siteId": "site_123",
"description": "Things to verify before rolling out a public API",
"published": true,
"tags": ["api", "launch"],
"public": true
}GET /api/v1/posts/{id}
Return a single post with full content, tags, pricing, site, and stats.
PUT /api/v1/posts/{id}
Update a post you own.
Supported fields:
titlecontentdescriptionslugimagestatuspublishedtags
If tags is present, the API replaces the post's entire tag set with the provided list. If you update status to draft or published without sending published, the API keeps the boolean flag in sync.
DELETE /api/v1/posts/{id}
Soft-delete a post. The response returns:
{
"message": "Post deleted successfully"
}GET /api/v1/sites
List sites owned by the authenticated user.
Each site includes:
id,name,description,logosubdomain,customDomaincreatedAt,updatedAtstats.posts
GET /api/v1/webhooks
List webhook endpoints owned by the authenticated user.
Query parameters
| Name | Type | Notes |
|---|---|---|
siteId | string | Optional site filter |
The response includes:
data: webhook listavailableEvents: supported event types
For security, webhook secrets are not returned by the list endpoint.
POST /api/v1/webhooks
Create a webhook endpoint.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | Max length 100 |
url | string | Yes | Must be a valid URL |
events | string[] | Yes | Must contain at least one supported event |
siteId | string | No | Restrict deliveries to one site |
headers | object | No | Custom string headers sent with each delivery |
retryCount | integer | No | Range 0-10 |
timeoutSeconds | integer | No | Range 5-300 |
Supported events
post.createdpost.publishedpost.deleteduser.registereduser.updatedsite.createdsubscription.createdsubscription.cancelledtest.event
The creation response includes the webhook secret. Save it when you create the webhook, because the list endpoint does not return it again.
Webhook Deliveries
Outbound webhooks are signed with X-Webhook-Signature using HMAC-SHA256 over the raw request body.
Headers sent with each delivery:
X-Webhook-SignatureX-Webhook-EventX-Webhook-DeliveryUser-Agent: Position-Webhooks/1.0
Payload shape:
{
"id": "delivery_payload_id",
"event": {
"type": "post.published",
"data": {},
"timestamp": "2026-04-04T12:00:00.000Z",
"site_id": "site_123",
"user_id": "user_123"
},
"webhook": {
"id": "webhook_123",
"name": "Production Listener"
}
}For a deeper guide to verification and handler examples, see Webhooks.
Error Codes
| Status | Meaning |
|---|---|
400 | Validation failed or query parameters are invalid |
401 | Missing, invalid, or expired API key |
403 | API key does not have the required scope |
404 | Resource not found or does not belong to the API key owner |
409 | Slug conflict on post create or update |
429 | Rate limit exceeded |
500 | Internal server error |
Integration Tips
- Keep API keys on your server only.
- Store the webhook secret when you create the webhook.
- Prefer webhooks over polling for downstream automation.
- Treat
DELETE /posts/{id}as a soft delete.
Versioning
The API is versioned in the URL path. Breaking changes will be released under a new version such as /api/v2.