REST API authentication
Authenticate server-side calls to the AgentStack REST API.
The REST API base path is /api/v1. Send the API key from a server that you control. Use the header Authorization: Bearer <API key>. Do not put the key in a widget, a mobile bundle, or a public repository. The generated OpenAPI contract is available at /api/v1/openapi.json. An interactive reference is available at /reference.
API access is included on every plan, the free plan too. The plan sets the rate limit, not access. The API returns 401 with UNAUTHORIZED for a missing key. The API returns 401 with INVALID_API_KEY for an invalid or expired key. Keys use a scope hierarchy: read, write, and admin. A higher scope includes the permissions of a lower scope. Most mutations need write scope. Destructive operations can require admin scope. Custom-role mutations also require an owner-created key and Enterprise.
The JSON envelope is { success, data, error }. A success response sets error to null. A failure response sets data to null. The OpenAPI document and GET /webhooks/event-types do not require authentication. Authenticate every other request.
OAuth access tokens
The bearer value can also be an OAuth access token. The token comes from the same flow that an MCP client uses: Dynamic Client Registration, then /api/auth/mcp/authorize, then /api/auth/mcp/token. Send the token exactly as you send a key: Authorization: Bearer <access token>. The same token works on /api/mcp and on /api/v1.
A token acts as the user who approved it. The API resolves the organization the same way the MCP server does: the user's pinned MCP organization (set by switch_organization), then the most recent dashboard organization, then the oldest membership. The token has the user's real role and permissions in that organization. Tokens work on every plan, the free plan too. The REST API keeps the resolved organization for up to 30 seconds per token, so a switch_organization call or a removed membership can take up to 30 seconds to reach /api/v1.
A token gets exactly what it gets on the MCP server, no more and no less. The read, write, and admin scopes of an API key do not apply to a token. Each endpoint checks the token against the MCP scope of its operation: the same scope that the matching MCP tool needs. The endpoint then checks the same admin-role floor and the same member permissions that the MCP tool checks.
| Operation | Token scope needed |
|---|---|
| Most reads | mcp:read |
| Most writes and deletes | mcp:write |
POST /inbox/tickets/{ticketId}/reply (the reply_to_ticket tool) | mcp:send |
POST /inbox/tickets/{ticketId}/attachments (the upload_attachment tool) | mcp:send |
GET /billing/* | mcp:billing |
GET /api-keys, DELETE /api-keys/{id}, GET /organization/members, GET /organization/invitations | mcp:admin |
POST /chat, POST /chat/stream | mcp:chat |
A token that holds only mcp:read and mcp:send can reply to a ticket, but it cannot change the ticket status. That needs mcp:write. For example, GET /api-keys needs the mcp:admin scope and an owner or admin user. A member's token is refused, whatever scopes it holds.
The two chat endpoints have no MCP tool. A token needs the mcp:chat scope for them, and the user needs permission to view agents. mcp:write alone is not enough, because a chat turn spends the organization's credits. A client must request mcp:chat by name: it is never part of the default grant.
Some endpoints do not accept a token and answer 403 with API_KEY_REQUIRED: the custom-role endpoints and the seed endpoints. Use an API key for them.
Token errors:
401withINVALID_ACCESS_TOKEN: the token is unknown, expired, or revoked, or its client is disabled. The response carriesWWW-Authenticate: Bearer error="invalid_token". Refresh the token, or authorize again.403withINSUFFICIENT_SCOPE: the token has nomcp:*scope, or the operation needs a scope the token does not have.403withPERMISSION_DENIED: on a chat endpoint, the user's role does not allow viewing agents.403withNO_ORGANIZATION: the user does not belong to any organization.403withNOT_ORGANIZATION_MEMBER: the user is no longer a member of the organization the token resolved to, for example because the membership was removed during the request. Retry the request; the token then resolves to another organization of the user, or answersNO_ORGANIZATION.
A token request writes an API log entry with no API key. Token requests share the organization's REST rate limit with its API keys.