# API reference

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

Source: https://opensocial.run/docs/api
Markdown: https://opensocial.run/docs/api.md



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](https://opensocial.run/docs/getting-started).

## Authentication and requests [#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 [#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 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 [#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 [#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](https://opensocial.run/docs/video-platforms#set-publication-options).

## Media [#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 [#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 [#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:

```text
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 [#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.

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

```sh
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 [#cli-sdk-and-mcp]

Use the [getting started guide](https://opensocial.run/docs/getting-started#use-the-cli-or-api) for CLI
and REST connection instructions, and the
[agent workflow](https://opensocial.run/docs/getting-started#use-an-agent) 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.
