Getting Started

Authentication

Secure every AdaptlyPost API request with an API key, learn where to generate keys and how to send them in your headers.

API Keys

AdaptlyPost uses API keys to authenticate REST API requests. You can generate and manage API keys from your dashboard under Settings > API Tokens.

Keep Your API Key Secure

Never expose your API key in client-side code, public repositories, or frontend applications. Always use environment variables or a secure vault to store your keys.

Using Your API Key

Include your API key in the Authorization header of every request:

Authorization Header
Authorization: Bearer your_api_key_here

Example Request

curl https://post.adaptlypost.com/post/api/v1/social-accounts \
  -H "Authorization: Bearer adaptly_8f3c1d9a7b2e4605c1a8d7f36b904e2d"

Roles and permissions

Every member of a workspace has one of four roles. The role decides what the member can do in the dashboard, and what any API key, MCP session or integration acting for that member can do. The check runs on the server for every request, so a client cannot work around it.

RoleWhat it can do
AdminadminEverything in the workspace: connects and disconnects social accounts, picks pages and boards, manages members, webhooks and every key, and does everything an Editor does.
EditoreditorCreates, edits, schedules, publishes, retries and deletes posts, including posts by other members. Manages webhooks and the post signature, uploads media, uses AI and reads analytics. Cannot connect or disconnect accounts.
ContributorcontributorCreates and edits its own drafts, uploads media, uses AI, reads posts and analytics, and creates its own keys. Cannot schedule, publish, retry or bulk schedule, cannot delete anything but its own drafts, and cannot touch other members' posts, accounts or webhooks.
ViewerviewerReads posts, connected accounts, analytics, members and webhooks. Cannot create or change anything.

Permissions by role

Each endpoint page names the permission it needs. A key holds exactly the permissions of its role, minus anything its creator can no longer do. GET /api/v1/me returns the list.

PermissionAllowsAdminEditorContributorViewer
workspace.readSee the workspace at all✓✓✓✓
workspace.manageChange workspace settings✓–––
posts.readRead posts and their per-platform results✓✓✓✓
posts.draftCreate drafts, and edit or delete your own drafts✓✓✓–
posts.othersEdit, publish, retry or delete posts created by other members✓✓––
posts.scheduleSchedule a post for a future time, and edit a scheduled post✓✓––
posts.publishPublish now, and retry failed platforms✓✓––
posts.deleteDelete posts that are not drafts✓✓––
media.uploadRequest upload URLs for images and videos✓✓✓–
accounts.readList connected social accounts✓✓✓✓
accounts.manageConnect, disconnect and re-check accounts, choose pages and boards, create connect links✓–––
analytics.readRead analytics✓✓✓✓
analytics.syncAsk for an analytics refresh✓✓––
ai.generateGenerate captions and images✓✓✓–
members.readSee who is in the workspace✓✓✓✓
members.manageInvite, change and remove members✓–––
tokens.ownCreate and revoke your own API keys✓✓✓–
tokens.manageSee and revoke every key in the workspace✓–––
webhooks.readList webhooks✓✓–✓
webhooks.manageCreate, update, delete and test webhooks✓✓––
signature.manageChange the post signature✓✓––

Keys carry a role

When you create a key you choose its role. The key then acts with that role in the workspace it was created in, under these rules:

  • A key can never be created with a role above the role of the member creating it. An Editor can mint Editor, Contributor or Viewer keys, not Admin keys.
  • On every request the key's effective permissions are the permissions of its role, minus anything its creator can no longer do. The check reads the creator's current role, not the role at creation.
  • If the creator is demoted, every key they created shrinks with them at once. An Editor key created by a member who is now a Contributor can only draft.
  • If the creator leaves the workspace or is removed, their keys stop working and answer 401 with code token_issuer_lost_access. A workspace admin creates a replacement.
  • An MCP session or OAuth grant has no role of its own: it acts with the signed-in member's role in the workspace the call lands in. That is the member's default workspace unless X-Workspace-Id names another.

Check what a key can do before you use it

Any valid key can call this endpoint. It returns the role, the exact permission list and a can object with draft, schedule and publish flags, so a client or an agent can hide what it may not do instead of finding out through a 403: GET /api/v1/me

Choosing a workspace

A member can belong to several workspaces, in one organization or several. An OAuth connection, such as the Claude or ChatGPT connector, can act in any of them. An API key acts only in the workspace it was created in. Send the optional X-Workspace-Id header on any request to choose the workspace. Without it the call lands in the default workspace.

Workspace header
X-Workspace-Id: ag_02k7m

This endpoint lists the workspace ids you can send, with your role and a can object for each: GET /api/v1/workspaces

Authentication Errors

A missing, invalid or expired key answers 401. A key that exists but may not do what you asked answers 403. Every error body carries a machine-readable code so a client can tell them apart:

401 Unauthorized
{
  "statusCode": 401,
  "message": "Invalid or missing API key",
  "error": "Unauthorized"
}

The key is valid but the member who created it no longer belongs to the workspace. The key is dead for good; ask a workspace admin for a new one.

401 Unauthorized: creator lost access
{
  "statusCode": 401,
  "error": "Unauthorized",
  "code": "token_issuer_lost_access",
  "message": "This API key no longer works: the member who created it lost access to the workspace. Ask a workspace admin for a new key."
}

An OAuth sign-in, such as the Claude or ChatGPT connector, used an email that has no AdaptlyPost account. Disconnect the app and connect again with the email you use on AdaptlyPost.

401 Unauthorized: no AdaptlyPost account
{
  "statusCode": 401,
  "error": "Unauthorized",
  "code": "oauth_account_not_found",
  "message": "You signed in as jane@example.com, which has no AdaptlyPost account. Disconnect this app and connect again with the email you use on AdaptlyPost."
}

The key's role does not hold the permission this operation needs. The body names the missing permission, the role the key acts as, and what to do instead. For a post operation the required permission depends on the request: publishing now needs posts.publish, a future scheduledAt needs posts.schedule, and touching another member's post also needs posts.others.

403 Forbidden: permission denied
{
  "statusCode": 403,
  "error": "Forbidden",
  "code": "permission_denied",
  "requiredPermission": "posts.publish",
  "role": "contributor",
  "keyRole": "contributor",
  "issuerRole": "editor",
  "tokenType": "api_token",
  "message": "The Contributor role cannot publish posts. Send the post with saveAsDraft: true and ask a workspace member to publish it."
}

Stop on a 403

A 403 with code permission_denied is final for that key. Retrying, waiting, or trying another key created by the same member will not change the answer. If your integration should not have hit it, ask a workspace admin for a key with a role that holds the permission, or send the post with saveAsDraft: true and let a member with posts.publish publish it.

The organization's plan is not active, or does not include API access. Every call with the key or OAuth sign-in gets this answer until the plan is renewed or upgraded. code tells this 403 apart from the others.

403 Forbidden: subscription required
{
  "statusCode": 403,
  "error": "Forbidden",
  "code": "subscription_required",
  "message": "This API key no longer works: your organization's plan does not include API access. Renew or upgrade the plan to use it again."
}

X-Workspace-Id names a workspace the caller cannot reach. An API key gets the same answer for any workspace other than its own. Send an id from GET /api/v1/workspaces, or leave the header out.

403 Forbidden: workspace access denied
{
  "statusCode": 403,
  "error": "Forbidden",
  "code": "workspace_access_denied",
  "workspaceId": "ag_03p4r",
  "message": "You have no access to workspace ag_03p4r. Call GET /workspaces to list the workspaces you can use, and send one of those ids in the X-Workspace-Id header."
}

For regulated teams

A Contributor seat and a Contributor key are enforced on the server, not in the interface. Every request, whether it comes from the dashboard, the REST API, the MCP server or an integration such as Zapier or n8n, passes through the same permission check in the post service before anything is written: the check reads the caller's role in the workspace at that moment, works out the permission the request needs from its facts (draft, scheduled or publish now; own post or a teammate's), and refuses with a 403 when it is missing. A key is created under a role, never above the member creating it, and its permissions are recalculated on every call as the overlap of the key's role and the creator's current role, so demoting the creator shrinks the key at once and removing them kills it. An auditor can point at three things: GET /api/v1/me, which returns the role and the exact permission list a key has right now; the 403 body, which names the missing permission, the role that lacked it and the kind of token that tried; and the post audit trail, where every create, schedule, publish, retry and delete attempt is recorded with who did it, through which key, under which role, from which status to which, and whether it was allowed or refused. The trail is written by the post service; it is not exposed in the dashboard or the API yet.