API reference
Use accounts, capabilities, media, posts, webhooks, scopes, and usage through the REST API.
Use the same service address as the dashboard, with the /v1 path. Requests
and responses use JSON, except for media byte uploads. For account and API key
setup, see Getting started.
Authentication and requests
Send your full API key in each authenticated request:
Authorization: Bearer sk_YOUR_API_KEY
Content-Type: application/json
Keys belong to an organisation. Do not expose them in public browser code.
Create an account through Better Auth at /login, verify your email, and sign
in. Create the first API key at /dashboard/keys. Programmatic key creation
uses POST /v1/session/keys with the verified session cookie and application
Origin header. Keys are shown only once. GET /v1/health and GET /v1/openapi
are public. The public email-only sign-up route is removed.
Use the returned IDs, such as acct_…, post_…, pub_…, and med_….
Use RFC 3339 timestamps for publish_at. Responses use UTC timestamps.
Each response includes X-Request-Id. Include it in a support request.
For authenticated requests that change data, send an Idempotency-Key of
8–255 characters. Reuse that key only for the same request. A repeated request
returns the stored response with Idempotency-Replayed: true. A different
request with the same key returns 409 idempotency_key_reused.
List responses contain data and has_more. The current API does not provide
cursor pagination. GET /v1/posts accepts limit. GET /v1/events accepts
limit, type, and after, where after is a timestamp.
Errors
An unsuccessful request returns an error object:
{
"error": {
"type": "validation_error",
"code": "publications_required",
"message": "At least one publication target is required.",
"param": "publications",
"retryable": false,
"request_id": "req_EXAMPLE"
}
}
Additional fields can include platform, docs_url, and details.
Check code and retryable to decide the next action. Correct validation
errors before you retry. For a failed publication, read its error field.
| HTTP status | Meaning |
|---|---|
| 400 | Invalid request or media. |
| 401 | Missing or invalid API key. |
| 403 | Missing permission. |
| 404 | Resource not found. |
| 409 | State conflict or idempotency conflict. |
| 429 | Rate limit reached. |
| 500 | Service error. |
| 502 | Platform error. |
Capabilities
GET /v1/capabilities
GET /v1/capabilities?platform=bluesky
Read capabilities before you prepare a post. The response includes text and
media limits, requires_media, implemented, auth_kind, and connect_fields.
A platform can appear in the catalog without publication support. Check
implemented before you select it.
Accounts
GET /v1/accounts
GET /v1/accounts/{id}
POST /v1/accounts/{platform}/connect
GET /v1/accounts/{id}/publishing-options
DELETE /v1/accounts/{id}
For an OAuth platform, send a connect request and open the returned
authorization_url. The user must complete sign-in and consent. Then read the
account list to get the connected account ID.
For Bluesky, send credentials in values:
{
"values": {
"handle": "you.bsky.social",
"password": "YOUR_APP_PASSWORD"
}
}
Account responses include id, platform, handle, display_name, status,
scopes, and connected_at. Reconnect accounts with status needs_reconnect.
For TikTok, read publishing options before each post. See the
platform guide.
Media
POST /v1/media
GET /v1/media
GET /v1/media/{id}
POST /v1/media/{id}/complete
- Initiate the upload with filename, MIME type, and size.
- Send the file bytes to the returned
upload_urlwith its method and headers. For a URL on the same service, also send your API key. Do not send the key to an external upload host. - Call the complete endpoint after the byte upload succeeds.
- Use the returned media ID in the post's
mediaarray.
{
"filename": "clip.mp4",
"content_type": "video/mp4",
"size_bytes": 184320,
"duration_ms": 12000,
"width": 1080,
"height": 1920,
"alt_text": "A short product demonstration"
}
duration_ms, width, height, and alt_text are optional metadata.
TikTok videos require duration_ms. Complete the upload before you create a
post. The service accepts files up to 2 GiB; each platform can have a lower limit.
Posts and publications
POST /v1/posts/validate
POST /v1/posts
GET /v1/posts
GET /v1/posts/{id}
POST /v1/posts/{id}/publish
POST /v1/posts/{id}/schedule
POST /v1/posts/{id}/cancel
DELETE /v1/posts/{id}
Validate the intended request body before you create or publish the post.
Validation does not create a post. It returns valid and a results array
with errors and warnings for each target account.
{
"content": { "text": "Hello from Open Social" },
"media": [],
"publications": [
{ "account_id": "acct_YOUR_ACCOUNT" }
]
}
Send this body to POST /v1/posts to create a draft. The response includes the
post ID and a publications array. Each publication has its own ID, account,
platform, status, publication time, external post ID, external URL, and error.
Use platform_options on each target for platform-specific fields.
Add publish_at to the create body to schedule the post. To schedule an
existing draft, send this body to POST /v1/posts/{id}/schedule:
{ "publish_at": "2026-10-12T09:00:00Z" }
Send POST /v1/posts/{id}/publish to publish now. Its 202 response means the
request was accepted. Check each publication status before you report success.
Publish and schedule requests can include publications, an array of
publication IDs, to select a subset. Without that field, they select all eligible
publications. Cancel requests cancel targets in draft, scheduled, or failed
states. Published targets stay on their platforms. If a target is publishing,
wait for it to finish before you cancel the post. If a target is publishing or
there are no targets to cancel, the API returns 400 with code
post_not_cancellable.
Post statuses include draft, scheduled, publishing, published,
partially_published, failed, and cancelled. A partially_published post
has different results across its publications. Read each result before you retry
an unsuccessful target.
Webhooks and events
POST /v1/webhooks
GET /v1/webhooks
PATCH /v1/webhooks/{id}
DELETE /v1/webhooks/{id}
GET /v1/webhooks/{id}/deliveries
GET /v1/events
Register an HTTPS endpoint for publication updates:
{
"url": "https://your-app.example.com/hook",
"event_types": ["post.published", "post.failed"],
"description": "Publication updates"
}
The create response includes a signing secret. Store it when it is shown;
it is shown only once. An empty event_types array selects all events.
GET /v1/webhooks returns the available event types in available_events.
Event types include account.connected, account.disconnected, post.created,
post.scheduled, post.publishing, post.published, post.partially_published,
post.failed, post.cancelled, media.ready, and media.failed.
Each event contains id, type, created_at, and data.
Verify the OpenSocial-Signature header before you accept a delivery:
OpenSocial-Signature: t=<unix_seconds>,v1=<hex_signature>
Compute HMAC-SHA256 with the signing secret and the exact string
<timestamp>.<raw_request_body>. Use a constant-time comparison and reject
signatures more than five minutes from the current time. Return a 2xx status
for an accepted delivery. Unsuccessful deliveries are retried. Use the
deliveries endpoint to inspect attempts.
Scopes, usage, and limits
GET /v1/me
GET /v1/scopes
GET /v1/keys
POST /v1/keys
DELETE /v1/keys/{id}
GET /v1/usage?from=YYYY-MM-DD&to=YYYY-MM-DD
GET /v1/organization
PATCH /v1/organization
An empty key scope list gives full access. A restricted key must have the scope
required for each operation. Missing scopes return 403 insufficient_scope.
GET /v1/scopes returns the scope catalog and the current key's scopes.
| Scope | Operations |
|---|---|
capabilities:read | Read platform capabilities. |
accounts:read / accounts:write | Read accounts and publishing options / connect or disconnect accounts. |
posts:read / posts:write | Read posts / validate, create, publish, schedule, cancel, or delete posts. |
media:read / media:write | Read media / upload and complete media. |
webhooks:read / webhooks:write | Read endpoints and deliveries / create, update, or delete endpoints. |
events:read | Read events. |
usage:read | Read usage. |
keys:read / keys:write | Read keys / create or revoke keys. |
org:read / org:write | Read / update the organisation and its limits. |
Usage includes daily counters and month-to-date totals for posts, publication
attempts, successes, failures, blocked publications, media uploads, media bytes,
and cost_micros.
DELETE /v1/keys/{id} revokes a key and returns 204. The key stops working
for new requests at once, including requests from CLI and MCP clients that are
already running. Revocation does not cancel a publication already queued or a
request already accepted. A revoked key is removed from active key lists; its
record is kept for history. Repeated revocation of a key in your organisation
returns 204. A missing key or another organisation's key returns 404.
Use a key with keys:write to revoke keys. A full access key can revoke itself.
You can also select the Revoke API key row action in the dashboard, or run:
bun run cli -- keys revoke key_example
Update limits with monthly_publication_limit and
monthly_cost_limit_micros in PATCH /v1/organization. Limits default to
unlimited. A reached limit causes a publication to fail with
publication_limit_exceeded or spend_limit_exceeded.
CLI, SDK, and MCP
Use the getting started guide for CLI and REST connection instructions, and the agent workflow for MCP tools.
The SDK provides OpenSocial({ apiKey, baseUrl }). Use the service address
without /v1 as baseUrl. Its accounts, capabilities, media, posts,
webhooks, and events methods use the same REST resources.