Docs
Public API

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/v1

Authentication

All requests must include a bearer token:

Authorization: Bearer pk_your_api_key_here

New 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-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset

Scopes

Use the smallest scope set that fits your integration:

ScopeRequired for
posts:readGET /posts, GET /posts/{id}
posts:writePOST /posts, PUT /posts/{id}
posts:deleteDELETE /posts/{id}
sites:readGET /sites
webhooks:readGET /webhooks
webhooks:writePOST /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

NameTypeDefaultNotes
pageinteger1Minimum 1
limitinteger10Range 1-100
siteIdstring-Filter to one site
publishedboolean-Use true or false
searchstring-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, image
  • status, published, public, isPaid
  • pricing
  • site
  • tags
  • stats.likes, stats.comments, stats.bookmarks
  • createdAt, updatedAt

POST /api/v1/posts

Create a post for a site you own.

Body

FieldTypeRequiredNotes
titlestringYesMax length 200
siteIdstringYesMust belong to the API key owner
contentJSONNoRich editor payload or your own JSON structure
descriptionstringNoShort summary
slugstringNoAuto-generated from title if omitted
imagestringNoMust be a valid URL
statusstringNoDefaults to draft
publishedbooleanNoDefaults to model default
tagsstring[]NoMissing tags are created automatically
publicbooleanNoDefaults to true
isPaidbooleanNoDefaults to false
pricing.pricenumberNoRequired when pricing is sent
pricing.currencystringNo3-letter currency code, default USD
pricing.isPaidbooleanNoDefaults to false
pricing.previewLengthintegerNoOptional preview length

Notes

  • Slugs are unique per site. A duplicate slug returns 409 Conflict.
  • If you send status: "published" without published, the API also marks the post as published. The same applies to draft.
  • If no slug can be generated from the title, the API falls back to an auto-generated slug.
  • Decimal values inside pricing may 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:

  • title
  • content
  • description
  • slug
  • image
  • status
  • published
  • tags

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, logo
  • subdomain, customDomain
  • createdAt, updatedAt
  • stats.posts

GET /api/v1/webhooks

List webhook endpoints owned by the authenticated user.

Query parameters

NameTypeNotes
siteIdstringOptional site filter

The response includes:

  • data: webhook list
  • availableEvents: supported event types

For security, webhook secrets are not returned by the list endpoint.

POST /api/v1/webhooks

Create a webhook endpoint.

Body

FieldTypeRequiredNotes
namestringYesMax length 100
urlstringYesMust be a valid URL
eventsstring[]YesMust contain at least one supported event
siteIdstringNoRestrict deliveries to one site
headersobjectNoCustom string headers sent with each delivery
retryCountintegerNoRange 0-10
timeoutSecondsintegerNoRange 5-300

Supported events

  • post.created
  • post.published
  • post.deleted
  • user.registered
  • user.updated
  • site.created
  • subscription.created
  • subscription.cancelled
  • test.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-Signature
  • X-Webhook-Event
  • X-Webhook-Delivery
  • User-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

StatusMeaning
400Validation failed or query parameters are invalid
401Missing, invalid, or expired API key
403API key does not have the required scope
404Resource not found or does not belong to the API key owner
409Slug conflict on post create or update
429Rate limit exceeded
500Internal 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.

Β© Postion 2026 β€” BuouTech Inc.