> ## Documentation Index
> Fetch the complete documentation index at: https://docs.social-api.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Base URL: https://api.social-api.ai/v1. Send your API key in the Authorization header: Authorization: Bearer <your API key>.
> Every error response from /v1 is JSON: { "error": { "code", "message", "meta" (optional) }, "request_id" }. Branch on error.code; use error.message only for logs.
> A 429 response carries a Retry-After header, in seconds, when the limit has a known wait. Authenticated responses can carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset for the per-minute request budget your SocialAPI account shares across every API key (see /guides/rate-limits).
> Paginated list endpoints return next_cursor, at the top level or inside pagination (see /guides/pagination). Pass it as the cursor query parameter to get the next page. An absent or empty next_cursor means you have the last page.
> Treat every ID as an opaque string: do not parse it or assume a format.
> Support differs by platform and by operation. Check /platforms/overview before assuming an operation works on a platform. An unsupported operation returns 501 with error.code resource.not_supported.

# Scoped API keys

> Restrict a SocialAPI key to specific scopes and brands for least-privilege automations, AI agents, and agency client isolation.

A scoped API key is a SocialAPI API key restricted to some actions (its scopes), some brands, or both. A key with no restriction has full access: every action on every [brand](https://docs.social-api.ai/guides/concepts.md) of your SocialAPI account.

Two uses are common. A bot that only answers comments holds a key with `inbox:read` and `inbox:write`, and nothing else. An agency gives each client's automation a key limited to that client's brand, so a leaked key cannot reach another client.

| Restriction | Field | Controls |
| - | - | - |
| Scopes | `scopes` | Which actions the key can perform |
| Brands | `brand_ids` | Which brands' accounts, posts, conversations, and Automations the key can see and change |

> [!NOTE]
>
> An empty `scopes` array grants every scope, and an empty `brand_ids` array grants every brand. A key is restricted when at least one of the two arrays holds a value.

## Create a scoped key

Pass `scopes`, `brand_ids`, or both to `POST /v1/keys`:

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/keys \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme client bot",
    "scopes": ["inbox:read", "inbox:write", "dms:send"],
    "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"]
  }'
```

Response (HTTP 201):

```json theme={null}
{
  "id": "5b0e6f3a-8c2d-4e1f-9a7b-3c4d5e6f7a8b",
  "name": "Acme client bot",
  "raw_key": "sapi_key_cd27569b0823266d6431e5431c09fd746edd48faa40aeb43e5e7ccd9a34fee",
  "message": "Store this key securely. It will not be shown again.",
  "scopes": ["inbox:read", "inbox:write", "dms:send"],
  "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"]
}
```

`raw_key` is shown once. Creating keys needs full access (a full-access key, an OAuth access token, or the dashboard), so a scoped key can never create a key with more access than it has.

* An unknown scope returns `400 validation.scope_invalid`, and the message names it.
* A brand ID that does not exist, is not a UUID, or belongs to another SocialAPI account returns `400 validation.brand_invalid`.

## Scopes

Scopes have the form `resource:action`. A scope-restricted key can perform an operation only when it holds the operation's scope.

| Scope | Grants |
| - | - |
| `accounts:read` | List brands and connected accounts. Read account Pages, creator info, usage limits, Pinterest boards and Pins, account and brand analytics, platform account summaries, Google Business Profile media, insights, and search keywords, and the Pages or locations of a Facebook or Google login |
| `accounts:manage` | Connect, reconnect, and disconnect accounts, change an account's [permissions](https://docs.social-api.ai/guides/permissions.md), finish pending connections, assign Facebook Pages and Google locations, change account Page settings, re-sync an account's posts, edit a Google Business Profile and its media, and create, edit, and delete Pinterest boards |
| `posts:read` | List and read posts, their metrics and analytics, and Instagram collaborators |
| `posts:write` | Create, edit, publish, retry, unpublish, validate (`POST /v1/posts/validate`), and delete posts, edit YouTube video details, and accept or decline Instagram collab invites |
| `media:read` | List the media library |
| `media:write` | Get an upload URL, upload, verify, and delete media |
| `inbox:read` | Read comments and their replies, reviews, mentions, and Instagram stories |
| `inbox:write` | Reply to comments, reviews, and mentions, send private replies, hide or unhide comments, delete comments, like or unlike comments, and moderate or edit YouTube comments |
| `dms:read` | Read DM conversations and messages, read Instagram participant profiles, and start or check an inbox sync |
| `dms:send` | Send DMs and add or remove a reaction on a message |
| `automations:read` | Read [Automations](https://docs.social-api.ai/guides/automations.md) and their runs |
| `automations:write` | Create, edit, pause, resume, and delete Automations. Each action also needs its own scope, see [below](#automations-need-their-actions-scopes) |
| `webhooks:manage` | Manage webhook endpoints, read and retry deliveries, and send test events. Refused to brand-restricted keys |
| `analytics:read` | Read the events log and its summary, create and read exports, read LinkedIn Page insights and Threads insights, and look up Threads profiles. Refused to brand-restricted keys |

`GET /v1/keys/scopes` returns this catalog with a `group` and a `description` per scope, so your app can render a scope picker from it:

```bash theme={null}
curl https://api.social-api.ai/v1/keys/scopes \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Response (HTTP 200, first entry shown):

```json theme={null}
{
  "data": [
    { "name": "accounts:read", "description": "List brands, connected accounts, and their metadata", "group": "Accounts" }
  ],
  "count": 14
}
```

### Automations need their actions' scopes

An [Automation](https://docs.social-api.ai/guides/automations.md) carries out its actions long after the request that configured it. A key can configure only actions it can perform itself: besides `automations:write`, it needs the scope of each action.

| Action | Also needs |
| - | - |
| `hide_comment`, `reply_comment`, `private_reply` | `inbox:write` |
| `send_dm` | `dms:send` |

A key that holds only `automations:write` and creates an Automation with a `send_dm` action gets `403 auth.insufficient_scope` with `meta.required_scope` set to `dms:send`.

The check runs on every change that can make the Automation send more: creating it, resuming it, or changing its `steps`, `post_ids`, or `once_per_person`. Renaming, pausing, and deleting need only `automations:write`, so a key can always stop an Automation it can see.

### Always available

Any valid key can call these endpoints, whatever its scopes and brands:

* `GET /v1/users/me`
* `GET /v1/usage`
* `GET /v1/billing/plans`
* `GET /v1/media/storage`
* `GET /v1/posts/validate`
* `GET /v1/keys/scopes`
* `GET /v1/webhooks/events`
* `GET /v1/announcements` and `POST /v1/announcements/{id}/seen`

### Full-access operations

A restricted key can never perform these operations. They need full access: a full-access key, an OAuth access token, or the dashboard.

* Managing API keys: list, create, edit, rotate, and revoke, including the key itself
* Editing or deleting your SocialAPI account (`PATCH` and `DELETE /v1/users/me`)
* Creating, editing, or deleting brands. Listing brands needs `accounts:read`
* Listing, creating, and revoking invites

A key that can create keys can create itself an unrestricted one, so every key operation needs full access.

> [!WARNING]
>
> A restricted key cannot rotate or revoke itself. Rotate or revoke a leaked scoped key with a full-access key or from the dashboard.

## Brand restrictions

A brand-restricted key behaves as if its brands are the only brands in your SocialAPI account. It never learns that other resources exist:

* **Reading** an account, brand, or other resource outside the key's brands returns the same `404` as a resource that does not exist, for example `404 account.not_found` or `404 brand.not_found`.
* **Lists** (accounts, brands, posts, conversations, comments, mentions, Automations) return only resources inside the key's brands. `GET /v1/brands` returns only the key's brands. An Automation belongs to the brand of its account.
* **Connecting** an account needs a `brand_id` from the key's list. A connection without `brand_id` creates a new brand that the key cannot see. See [Connect a social account](https://docs.social-api.ai/guides/connect-accounts.md).

### Posts

A post belongs to the brands of its target accounts:

* **Read.** A post is visible when at least one of its target accounts is in the key's brands. A draft with no target is not visible to a brand-restricted key. A post the key cannot see returns `404 resource.not_found`.
* **Change** (edit, publish, retry, unpublish, delete). Every target account must be in the key's brands. Otherwise the post returns `404 resource.not_found`.
* **Create or retarget.** Every account in the request must be in the key's brands. Otherwise the request returns `404 account.not_found`.

### Media is shared

`media:read` and `media:write` work on the whole media library, whatever the key's brands. A file in the library reaches a platform only when a post uses it, and creating or changing a post is limited to the key's brands.

### Webhooks and analytics need every brand

Webhook endpoints, their deliveries, the events log, and exports cover every brand of your SocialAPI account. A brand-restricted key is refused `webhooks:manage` and `analytics:read` operations even when it holds the scope, with `403 auth.insufficient_scope`. A key restricted only by scopes can hold and use them.

### Deleting a brand never widens a key

When you delete a brand that a key lists in `brand_ids`, its ID stays in the array and matches nothing. The key does not become unrestricted, even when that brand was its only one.

## Read a key's restrictions

`GET /v1/keys` returns `scopes` and `brand_ids` on every key. Both arrays are always present, and two empty arrays mean full access:

```json theme={null}
{
  "id": "5b0e6f3a-8c2d-4e1f-9a7b-3c4d5e6f7a8b",
  "name": "Acme client bot",
  "preview": "sapi_key_cd275...",
  "is_active": true,
  "last_used_at": "2026-10-12T16:04:00Z",
  "created_at": "2026-09-30T08:15:00Z",
  "scopes": ["inbox:read", "inbox:write", "dms:send"],
  "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"]
}
```

## Change the restrictions on a key

`PATCH /v1/keys/{id}` replaces the key's `name`, `scopes`, and `brand_ids` together. The secret does not change, so services using the key keep working:

```bash theme={null}
curl -X PATCH https://api.social-api.ai/v1/keys/5b0e6f3a-8c2d-4e1f-9a7b-3c4d5e6f7a8b \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme client bot",
    "scopes": ["inbox:read", "inbox:write"],
    "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"]
  }'
```

Response (HTTP 200):

```json theme={null}
{
  "id": "5b0e6f3a-8c2d-4e1f-9a7b-3c4d5e6f7a8b",
  "name": "Acme client bot",
  "scopes": ["inbox:read", "inbox:write"],
  "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"]
}
```

> [!WARNING]
>
> Send all three fields. `name` is required, or the request returns `400 validation.field_required`. A `scopes` or `brand_ids` you leave out is saved as empty, which removes that restriction.

Editing a key needs full access. Editing a revoked key, or an ID that is not one of your keys, returns `404 resource.not_found`. To issue a new secret with the same restrictions, rotate the key: see [Authentication](https://docs.social-api.ai/guides/authentication.md).

## Errors

A restricted key that is refused gets `403 auth.insufficient_scope`. `meta.required_scope` tells you what is missing:

* **The key lacks the scope.** `meta.required_scope` names the missing scope.
* **A `webhooks:manage` or `analytics:read` operation with a brand-restricted key.** `meta.required_scope` names that scope. Brand-restricted keys are refused it even when they hold it.
* **A full-access operation.** `meta.required_scope` is absent.
* **An operation no scope covers.** `meta.required_scope` is absent.

```json theme={null}
{
  "error": {
    "code": "auth.insufficient_scope",
    "message": "This API key does not have the required scope: posts:write",
    "meta": { "required_scope": "posts:write" }
  },
  "request_id": "a1b2c3d4"
}
```

A resource outside the key's brands returns its ordinary `404`, never a `403`. Full-access keys and [OAuth 2.1 access tokens](https://docs.social-api.ai/integrations/oauth.md) are never restricted.

## Related

* [Authentication](https://docs.social-api.ai/guides/authentication.md) for creating, rotating, and revoking keys
* [Connect a social account](https://docs.social-api.ai/guides/connect-accounts.md) for connecting accounts with a brand-restricted key
* [Automations](https://docs.social-api.ai/guides/automations.md) for the scopes each action needs
* [Account permissions](https://docs.social-api.ai/guides/permissions.md) for limiting what a connected account can do, which differs from a key's scopes
* [The MCP server](https://docs.social-api.ai/integrations/overview.md) for giving an AI agent a scoped key
* [Errors](https://docs.social-api.ai/guides/errors.md) for every `auth.*` code


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.