Open Social
Documentation

API reference

Use accounts, capabilities, media, posts, webhooks, scopes, and usage through the REST API.

View Markdown

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:

HTTP
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:

JSON
{
  "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 statusMeaning
400Invalid request or media.
401Missing or invalid API key.
403Missing permission.
404Resource not found.
409State conflict or idempotency conflict.
429Rate limit reached.
500Service error.
502Platform error.

Capabilities

HTTP
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

HTTP
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:

JSON
{
  "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

HTTP
POST /v1/media
GET  /v1/media
GET  /v1/media/{id}
POST /v1/media/{id}/complete
  1. Initiate the upload with filename, MIME type, and size.
  2. Send the file bytes to the returned upload_url with 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.
  3. Call the complete endpoint after the byte upload succeeds.
  4. Use the returned media ID in the post's media array.
JSON
{
  "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

HTTP
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.

JSON
{
  "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:

JSON
{ "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

HTTP
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:

JSON
{
  "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:

Code
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

HTTP
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.

ScopeOperations
capabilities:readRead platform capabilities.
accounts:read / accounts:writeRead accounts and publishing options / connect or disconnect accounts.
posts:read / posts:writeRead posts / validate, create, publish, schedule, cancel, or delete posts.
media:read / media:writeRead media / upload and complete media.
webhooks:read / webhooks:writeRead endpoints and deliveries / create, update, or delete endpoints.
events:readRead events.
usage:readRead usage.
keys:read / keys:writeRead keys / create or revoke keys.
org:read / org:writeRead / 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:

Shell
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.

On this page