Authentication
Free tier
Create a free account at app.apistash.io and connect with an API key or an OAuth token — MCP requests must be authenticated, so there is no anonymous access. The free tier gives you the built-in tools plus your own custom tools, prompts, resources, and agent definitions, subject to the free-plan rate limits and quota.
Which built-in tools your credentials actually reach is your choice: select them under Settings → MCP Settings in the dashboard. Nothing is selected to begin with, which keeps each agent's tool list short — see Quick Start.
Rate limits and the monthly quota apply per account — per organization for organization credentials — not per API key. Upgrade your plan for higher limits.
Account registration and email verification
To create an account, register at app.apistash.io with your email address and a password.
CAPTCHA verification
The registration endpoint requires a CAPTCHA token to protect against automated account creation. Pass the token returned by the CAPTCHA widget as the captcha_token field in the request body:
{
"email": "you@example.com",
"password": "your-password",
"first_name": "Jane",
"last_name": "Smith",
"locale": "en",
"captcha_token": "<token-from-captcha-widget>"
}
locale is required and sets the language of every email the account receives. It is a two-letter language code; a regioned tag such as de-CH is accepted and stored as its primary subtag (de).
apistash supports a fixed set of languages — the ones the dashboard's language picker lists. At sign-up the language comes from your browser rather than from a choice you made, so a supported-looking code outside that set is stored as English instead of failing the registration. A value that is not a two-letter code at all is a 400. Note that the supported set is wider than the set apistash has translations for: an email in a language with no translation yet is sent in English.
Registering through the dashboard does not ask for it: the form sends your browser's language. You can change it any time under Settings → Account → Email language, and from then on that choice is what counts — it lives with your account, so it follows you to every browser and device. Changing it there is a deliberate choice, so PATCH /api/user/profile rejects a language outside the supported set with a 400 rather than quietly storing a different one.
If the captcha_token field is missing or the token is invalid, registration returns 422 Unprocessable Entity. The dashboard registration page handles this automatically — the CAPTCHA widget is embedded in the form and the token is submitted with your credentials.
CAPTCHA verification is required on account registration — including registering through an organization invitation — when resending the verification email, and when requesting a password reset. Login and token refresh do not require a CAPTCHA token.
After registering, apistash sends a verification email to the address you provided. You must click the link in that email before you can sign in. Attempting to log in with unverified credentials returns:
{
"errors": [
{ "code": "authentication_error", "message": "Email address not verified", "detail": null }
]
}
with HTTP status 401 Unauthorized. (Shown here trimmed to the errors array — every response carries the full envelope.)
If you sign in with a third-party OAuth provider (e.g. Google), your account is automatically verified — no email step is required.
Verification links expire after 24 hours. If your link has expired, you can request a new one — see Resending the verification email below.
Resending the verification email
If your verification link has expired, request a new one via the login page — when you attempt to sign in with an unverified account, an amber notice appears with a "Resend verification email" button. You can also resend directly via the API:
curl -X POST https://api.apistash.io/api/auth/resend-verification \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"captcha_token": "<token-from-captcha-widget>"
}'
200 OK means the resend request was accepted. It does not confirm an account's state or guarantee that an email was delivered: an unknown address, an already-verified address, or another suppressed outcome receives the same response, and a later delivery failure does not change that response. This keeps address state private.
The endpoint can also return:
| Status | Meaning |
|---|---|
400 Bad Request | The request body is not valid JSON or does not match the documented request format. |
413 Payload Too Large | The request body exceeds the 2 MB limit. |
415 Unsupported Media Type | The Content-Type has neither the json subtype nor a +json suffix. |
422 Unprocessable Entity | CAPTCHA verification failed. |
503 Service Unavailable | Email delivery is temporarily unavailable before the request can be accepted. Nothing was saved, so it is safe to retry. |
To prevent email flooding, apistash enforces a per-user cooldown between resend requests (5 minutes). Requests within the cooldown window are silently ignored — no email is sent, but the endpoint still returns the same status and body as a successful send.
Password reset
To request a password reset, send a POST request to /api/auth/password/reset with your email and a CAPTCHA token:
curl -X POST https://api.apistash.io/api/auth/password/reset \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"captcha_token": "<token-from-captcha-widget>"
}'
200 OK means the password-reset request was accepted. It does not confirm that an account exists or guarantee that an email was delivered: suppressed outcomes receive the same response, and a later delivery failure does not change that response. This keeps address state private.
The endpoint can also return:
| Status | Meaning |
|---|---|
400 Bad Request | The request body is not valid JSON or does not match the documented request format. |
413 Payload Too Large | The request body exceeds the 2 MB limit. |
415 Unsupported Media Type | The Content-Type has neither the json subtype nor a +json suffix. |
422 Unprocessable Entity | CAPTCHA verification failed. |
503 Service Unavailable | Email delivery is temporarily unavailable before the request can be accepted. Nothing was saved, so it is safe to retry. |
To prevent email flooding, apistash enforces a per-user cooldown between password reset requests (5 minutes). Requests within the cooldown window are silently ignored — no email is sent, but the endpoint still returns 200 OK.
Signing in with SSO
If your organization has set up single sign-on, sign in with your work email: enter it on the login page and you will be redirected to your company's identity provider to authenticate. When you return, you are signed in — no separate apistash password required.
If your organization requires SSO for your email domain, password sign-in is disabled for members; use the organization sign-in button instead. A linked personal account can still reset its password, but cannot use that password to sign in while the SSO requirement remains active. Organization-managed accounts are passwordless, so their reset requests are silently ignored. The organization owner remains exempt from the SSO requirement. Single sign-on is an Enterprise-plan feature, configured by an organization administrator.
API keys
An API key is how an agent authenticates to the MCP endpoint. Generate one from the dashboard after verifying your account. (Rate limits and quota are set by your plan and shared across all your keys — see Plans & Limits.)
Creating an API key
- Sign in at app.apistash.io.
- Navigate to API Keys in the sidebar.
- Click New API Key.
- Enter a descriptive name (e.g. "My MCP Client").
- Select the grants, policies, and capabilities the key should carry. A key with no capability selected reaches only the authenticated system tools over MCP and no governed content category.
- Optionally assign an agent definition as this key's default for primary tasks. Enable Agents for assignment and use; the key's final scope, team bindings, and any Agent policy must make that Agent available.
- Click Create key.
The optional Agent assignment is saved atomically with the key. If the selected
Agent is not available to the final credential configuration, no key is created
and no one-time secret is issued. You can leave the key unassigned or change its
assignment later from the key detail page.
The task-start instruction identifies the primary with
primary_agent: true. Subagents never inherit the assignment: their parent explicitly supplies a
named role, permission to self-select, or no role. Empty Bootstrap arguments are invalid.
For optional persistent task-start setup, the authenticated system tools setup, bootstrap,
and agent_context do not require a Tools setting or tool-policy entry.
If an assigned agent definition should be selected, offered, or
used for conditional behavior, enable Agents. Agent ownership, team exposure, and Agent
policies still apply.
After connecting the credential to a client, ask its local agent exactly "Set up apistash." The dashboard's key and OAuth-connection detail pages repeat this step and show the relevant capability or scope configuration. Bootstrap and advertised tools establish current effective access. General setup also offers optional guided starter content, with concrete drafts and targets confirmed before governed writes. See Set up task-start context; the setup does not put secrets in the prompt and does not let the apistash server or dashboard write local client files.
Your new key is shown once only, immediately after creation. Copy it and store it in a secure location (e.g. a password manager or secrets vault) before leaving the page. It cannot be retrieved again.
Key names must be unique across your active keys. Attempting to create a second key with the same name returns 409 Conflict. Choose a different name or revoke the existing key first.
API keys are prefixed with sk_ followed by a UUID (e.g. sk_550e8400-e29b-41d4-a716-446655440000). Always store the full string.
What controls an API key
API keys do not have an OAuth-style scopes list — scopes belong to
OAuth tokens. What an API key can do is governed entirely by the
controls described in the sections below:
- Grants — which management endpoints the key may call.
- MCP capabilities — which native MCP surfaces (tools, resources, prompts) and whether Agent-definition use the key reaches.
- Tool, resource, prompt, and agent policies — which specific items the key may use.
- Organization scope and team bindings — which organization the key runs against and which team content it sees.
To limit a key to a single tool, use a tool policy — not a scope.
The four per-key item policies — tool_policy, resource_policy,
prompt_policy, and agent_policy — apply only to personal keys without
org_scope. On an org-scoped key, content reach comes from the organization and
the key's team bindings instead. Creating an org-scoped key with a non-all
item policy, or supplying any item-policy value when updating one (including an
explicit {"mode": "all"}), returns 422 Unprocessable Entity. In key
list, detail, and update responses, all four flat item-policy fields are omitted
for this organization lane. The applicable policies are returned per bound team
under teams instead.
Grants
An API key may declare grants that define which management contexts it is permitted to act in. A grant is either personal (the key can act on your own account) or org-scoped (the key can act within a specific organization). Omitting grants, or passing [], is valid; that key simply has no grant-gated management permissions.
Personal grant
A personal grant allows the key to authenticate on personal endpoints (e.g. GET /api/me/resources). You can optionally list which fine-grained personal permissions the grant carries:
| Permission | What it permits |
|---|---|
can_view_resources | Read your personal resources. |
can_create_resources | Create new personal resources (implies view). |
can_update_resources | Edit existing personal resources (implies view). |
can_delete_resources | Delete personal resources (implies view). |
can_view_tools | Read your personal custom-tool inventory. |
can_create_tools | Create personal custom tools (implies view). |
can_update_tools | Edit personal custom tools (implies view). |
can_delete_tools | Delete personal custom tools (implies view). |
can_view_prompts | Read your personal prompt inventory. |
can_create_prompts | Create personal prompts (implies view). |
can_update_prompts | Edit personal prompts (implies view). |
can_delete_prompts | Delete personal prompts (implies view). |
can_view_agents | Read your personal Agent inventory. |
can_create_agents | Create personal Agent definitions (implies view). |
can_update_agents | Edit personal Agent definitions (implies view). |
can_delete_agents | Delete personal Agent definitions (implies view). |
can_view_usage | Read your activity history. |
can_update_settings | Update your account settings. |
can_view_dashboard | Read the personal dashboard summary. |
If permissions is omitted or empty, the key can authenticate to personal endpoints but will be denied by any handler that checks a specific permission.
These management permissions are also used by the corresponding MCP mutation tools. They are
independent of MCP-use capabilities: Agent mutation tools require Tools plus their action
permission, while list_agents and get_agent require Tools plus Agents visibility.
Interactive authentication required
The following endpoints are only accessible via session cookie or JWT — API key authentication is always rejected with 403 Forbidden, regardless of the permissions on the key:
| Endpoint | Description |
|---|---|
GET /api/keys | List API keys |
GET /api/keys/{id} | Get a single API key |
POST /api/keys | Create an API key |
DELETE /api/keys/{id} | Revoke an API key |
POST /api/auth/logout | Sign out |
POST /api/user/email | Initiate an email address change |
DELETE /api/user/account | Delete the account |
These endpoints require a browser session or a short-lived JWT to prevent an API key from being used to escalate privileges, rotate credentials, or destroy the account that issued it.
{
"name": "read-only agent",
"grants": [
{
"scope": { "type": "personal" },
"permissions": ["can_view_resources", "can_view_usage"]
}
]
}
Org grant
An org grant allows the key to act within a specific organization. Pass the organization's UUID in org_id and list the organization permissions the grant should carry:
{
"name": "ci-agent",
"grants": [
{
"scope": { "type": "personal" },
"permissions": []
},
{
"scope": { "type": "org", "org_id": "<org-uuid>" },
"permissions": ["can_view_organization_members", "can_view_organization_api_keys"]
}
]
}
A key with only an org grant (no personal grant) cannot access personal endpoints — any personal endpoint returns 401 Unauthorized. A key with no org grant for a given organization is treated as a non-member — the org endpoint returns 404 Not Found.
Grant rules
- Grants are optional.
grants: []is valid and grants no management permissions. - You cannot grant more than you hold. If you try to include a permission you do not currently have in that organization, the key creation is rejected with
422. This prevents privilege escalation. Permissions you hold by implication count as held — see below. - Owner-only permissions cannot be delegated. The permissions
can_delete_organization,can_update_organization_billing, andcan_transfer_organization_ownershipcannot be placed in any API key grant and are always rejected with422. - One grant per management context. Duplicate contexts (two personal grants, or two org grants for the same org) return
422. - You must be a member of the org. Trying to create an org grant for an organization you are not a member of returns
422.
Implied permissions
Holding a write permission means holding the matching read permission. Personal-lane examples:
can_create_resources→ impliescan_view_resourcescan_update_resources→ impliescan_view_resourcescan_delete_resources→ impliescan_view_resources
And in an organization grant:
can_invite_organization_members→ impliescan_view_organization_memberscan_remove_organization_members→ impliescan_view_organization_members
This works in both directions, and both are useful:
- You never need to list an implied permission. Grant the write permission and the read comes with it at enforcement time.
- You may grant an implied permission on its own. If you hold
can_update_organization_roles, you also holdcan_view_organization_roles— so you can issue a key that only reads roles, without handing it the ability to change them. That is usually what you want for a CI or reporting agent.
JWT and cookie callers
Grants only apply to API key authentication. Sessions authenticated via JWT or browser cookie carry full personal and organizational permissions, unchanged from before grants were introduced.
Tool policy
When creating an API key you can optionally restrict which tools it may call. Three modes are available:
| Mode | Behaviour |
|---|---|
all (default) | The key can call every tool. |
whitelist | The key can only call the tools listed in tools. |
blacklist | The key can call every tool except those listed in tools. |
tools and custom_tools take tool IDs, not MCP names. The two lists are separate: tools holds built-in platform tools, custom_tools your own custom tools. In the dashboard's key form you pick tools by name and it submits their IDs; over the REST API, GET /api/me/tools/scopable returns the built-in tools you can scope a key to (each with its id and name), and GET /api/me/custom-tools/scopable does the same for your custom tools.
Set the policy when creating a key via the REST API:
{
"name": "read-only agent",
"mcp_capabilities": ["tools"],
"grants": [{ "scope": { "type": "personal" }, "permissions": [] }],
"tool_policy": {
"mode": "whitelist",
"tools": ["<calculator-tool-id>", "<datetime-now-tool-id>"]
}
}
{
"name": "restricted agent",
"mcp_capabilities": ["tools"],
"grants": [{ "scope": { "type": "personal" }, "permissions": [] }],
"tool_policy": {
"mode": "blacklist",
"tools": ["<blocked-tool-id>"]
}
}
Omitting tool_policy (or setting {"mode": "all"}) applies no per-key tool
filter — the key can call everything its tools capability and your account's
tool selection allow.
The policy is enforced on every MCP call:
tools/list— the server returns only the tools the key is permitted to call.tools/call— calling a tool excluded by the credential's policy returnstool_not_permitted; a genuinely absent or deprecated tool remainsmethod_not_found.
The system tools setup, bootstrap, and agent_context do not appear in a tool policy and are
not affected by any tool whitelist or blacklist.
Tools are executed exclusively over MCP — there is no REST tool-execution endpoint. On MCP the key still calls tools by name; IDs appear only in the policy itself.
Organization scope
Personal API keys can operate in an organization's context. When org_scope is set to an org UUID, the key's usage, quota, and rate limits run against that org instead of your personal account:
{
"name": "org-agent",
"grants": [{ "scope": { "type": "personal" }, "permissions": [] }],
"org_scope": "<org-uuid>",
"mcp_capabilities": ["tools"]
}
You must be an active member of the referenced organization. Passing an org you are not a member of returns 422 Unprocessable Entity.
MCP capabilities
Every API key must declare which MCP feature categories it may access via mcp_capabilities:
| Capability | What it gates |
|---|---|
tools | Tool listing (tools/list) and execution (tools/call) |
resources | Resource listing and reading (resources/list, resources/read) |
prompts | Prompt listing (prompts/list) and retrieval (prompts/get) |
agents | Agent-definition assignment, selection, and content |
A key without a capability cannot use that governed category. An empty mcp_capabilities list still permits the authenticated system tools setup, bootstrap, and agent_context, but no other MCP tool, resource, prompt, or agent definition. Tools and Agents are independent. The system tools do not require tools or tool-policy access; Agent selection or content returned by bootstrap and agent_context still requires agents.
Where a capability can come from. In an organization, roles grant capabilities on two levels:
- A capability from one of your organization roles applies organization-wide — it backs any of your credentials in that organization, whatever teams they are attached to.
- A capability from one of your team roles counts for a credential only when that credential is active on the team the role is granted on — for a key, that means the team is among the key's teams: its bound teams, the teams you are currently in for a personal key that follows your memberships, or every team of the organization for an organization-owned key that follows the org's teams. A team-role capability on some other team does nothing for that credential.
Escalation guard: for keys operating in an organization context, the capabilities you request at key-creation time are bounded by your own effective capabilities in the new key's team context. An organization-role capability always qualifies; a team-role capability qualifies only if the granting team is among the teams the new key will be active on — holding it via a team the key is not attached to is not enough. If you request a capability outside that set, the key creation is rejected with 422 capability_escalation. The same check runs when an organization key's team bindings are edited later, so a key cannot be moved into a team context its capabilities were never authorized for. This prevents a key from having more power than its creator.
With unchanged team bindings, an update checks only newly added capabilities. Removing capabilities does not require you still to hold them. You can clear the capabilities of your personal key after leaving its organization; adding them back requires current membership and sufficient capabilities. Your account and the key's organization must still be active.
System role defaults:
| System role | Default capabilities |
|---|---|
| Owner | tools, resources, prompts, agents |
| Admin | tools, resources, prompts, agents |
| Member | (none — add via custom role) |
The system team_admin role also grants no MCP capability; add capabilities through a custom
organization or team role when needed.
Custom roles carry their own capability set, configured at role creation time.
Resource policy
resource_policy restricts which personal resources the key may access by matching their paths. It applies in addition to the resources capability gate — both must pass.
Three modes:
| Mode | Behaviour |
|---|---|
all (default) | Key may access any resource in its ownership space. |
whitelist | Key may only access resources whose paths match a listed pattern. |
blacklist | Key may access resources except those whose paths match a listed pattern. |
{
"name": "docs-agent",
"mcp_capabilities": ["resources"],
"resource_policy": {
"mode": "whitelist",
"patterns": ["documents/**", "public/**"]
}
}
Patterns are globs matched against the resource's path, rather than its full URI. For example, "documents/**" matches documents/report.pdf and documents/reports/annual.pdf, while private/secret.txt stays outside that whitelist. Use a resource's exact path to allow or block only that resource.
Prompt policy
prompt_policy restricts which platform prompts and customer prompts in the caller's personal space a key may use. It applies in addition to the prompts capability gate — both must pass.
Customer prompt visibility is ownership-driven (there is no prompt allowlist). On the MCP surface, prompt names are namespaced by ownership space so they don't shadow each other: personal-in-org prompts appear as me/{name}, team-owned prompts as {team}/{name}, and org-catalog prompts unprefixed.
Platform prompts such as apistash/agent-doctor additionally need selection in the platform-prompt allowlist. The same policy mode filters them through the optional platform_prompts ID list; prompts keeps customer prompt IDs. A restricted policy must select at least one ID across the two lists. Use prompts: [] with platform_prompts: ["<platform-prompt-id>"] for a platform-only whitelist. See Platform prompts.
Same three modes as tool policy, operating on prompt IDs:
{
"name": "restricted-agent",
"mcp_capabilities": ["prompts"],
"prompt_policy": {
"mode": "blacklist",
"prompts": ["<internal-system-prompt-id>"]
}
}
prompts takes the IDs of your own personal-space prompts — GET /api/me/prompts/scopable returns them with their names. On MCP a prompt is still addressed by name (prompts/get); the stored policy references it by ID.
Agent policy
agent_policy restricts which of your personal agent definitions a personal API key is eligible
to use. It has the same three modes as the prompt policy and defaults to all:
| Mode | Behaviour |
|---|---|
all (default) | The key is eligible to use every personal agent. |
whitelist | The key is eligible to use only the agents listed in agents. |
blacklist | The key is eligible to use every agent except those in agents. |
{
"name": "review-agent",
"mcp_capabilities": ["agents"],
"agent_policy": {
"mode": "whitelist",
"agents": ["<personal-agent-id>"]
}
}
For an org-scoped key, this personal policy does not apply. The caller can instead reach its own
personal-in-organization agents, agents owned by its bound teams, and organization-owned agents
that are organization-wide or admitted by at least one bound team's agent policy. Team policies
use the same all, whitelist, and blacklist modes for the organization's agent catalog;
team-owned agents are visible through ownership and are not filtered by that catalog policy.
Policies and dashboard assignment keep stable Agent IDs internally. Agent-facing MCP tools use the
qualified names returned by list_agents or bootstrap (me/name, <team>/name, or a bare
personal/catalogue name), and each content read resolves the current active version.
The Agent policy is a secondary filter after the independent agents capability. Assignment
choices and set/replace require that capability as well as policy and ownership visibility; a
stored assignment that becomes unavailable hides its identity but can still be cleared. Agent
bootstrap and agent_context themselves do not require tools, but their Agent selection and
content still pass this Agent policy. Organization keys have no per-key Agent policy: team policy and organization
exposure control their Agent reach.
Policy request shape
Tool, resource, prompt, and agent policies all use mode as a discriminator. In all mode, do
not send any of that policy's known list fields. In whitelist and blacklist modes, send the
required, non-null list: tools for a tool policy (custom_tools remains optional), patterns
for a resource policy, prompts for a prompt policy, or agents for an agent policy. Every list
that is supplied must be non-null. Known list fields that do not belong to the selected mode are
rejected; genuinely unknown extension fields are tolerated and ignored.
A malformed policy shape—such as a missing required list, null, a wrong JSON type, or a value
that is not a valid ID—returns 400 Bad Request. A well-shaped policy whose contents violate a
domain rule—such as an empty or duplicate list, an unknown ID, or an ID outside the permitted
owner space—returns 422 Unprocessable Entity.
Management and MCP access are separate
An API key can carry controls for two different uses. They are independent, but they do not form one authorization chain:
| Control | What it controls |
|---|---|
grants | Which REST and governed MCP management operations the key may perform |
org_scope | Which organization context, billing, and limits an MCP request uses |
mcp_capabilities | Which native MCP surfaces and Agent-definition use the key can reach |
| Per-item policies | Which specific items are reachable on a standalone personal key |
| Team bindings | Which team-owned and team-admitted items an org-scoped key can reach |
For management API requests, the relevant grant and permission checks apply. MCP capabilities and item policies do not grant management access.
For ordinary MCP use, org_scope selects the execution context,
mcp_capabilities opens the corresponding MCP category, and either the
standalone key's item policy or the org-scoped key's team bindings and team
policies narrows what is reachable. Management grants do not make content usable.
Governed MCP management tools add a separate management check. list_*, get_*, and
grep_resource use the visibility rules of the surface they read. For create, update, patch, and
delete, a standalone personal target requires the corresponding personal permission, while a Team
target requires both a Team binding and the corresponding Team permission. A me/ target in an
organization requires an acting user with active membership; it does not use a separate personal
permission. Every governed management tool still requires Tools and admission by the applicable
tool policy.
JWT and cookie sessions are for interactive management access. They do not use API-key MCP capabilities or item policies and are rejected by the MCP endpoint.
Dashboard JWTs are not accepted on MCP endpoints
The POST /mcp endpoint rejects requests authenticated with a dashboard JWT or
session cookie. Only API keys or OAuth tokens are valid credentials on
this endpoint.
If you use a dashboard session token on an MCP endpoint, you will receive 403 Forbidden.
Use an API key as described above.
Using your API key
Send the key in the X-API-Key header on every request:
X-API-Key: YOUR_API_KEY
The Authorization: Bearer header is reserved for OAuth access tokens (see OAuth below) — an API key sent that way is rejected.
For MCP clients, set this in your client configuration:
{
"mcpServers": {
"apistash": {
"type": "http",
"url": "https://api.apistash.io/mcp",
"headers": {
"X-API-Key": "YOUR_API_KEY"
}
}
}
}
Listing your keys
GET /api/keys returns all active keys for your account. GET /api/keys/{id} returns a single key by UUID. Each entry includes a last_used_at timestamp (ISO 8601, null if the key has never been used) so you can identify unused keys for cleanup.
OAuth
Alongside API keys, apistash supports OAuth 2.0 for agent tools — CLIs and coding agents — that authorize to the MCP server on a user's or organization's behalf. OAuth access tokens are accepted on the MCP endpoint as Authorization: Bearer <token>.
Flows
apistash implements three OAuth grant types:
| Grant | Use |
|---|---|
| Authorization Code + PKCE | Interactive apps acting for a user. PKCE (S256) is required for public clients and available to confidential clients. |
| Refresh Token | Obtain a fresh access token without re-prompting. Refresh tokens rotate on each use. |
| Client Credentials | Machine-to-machine access for an organization — a userless client authenticating as itself. Public clients cannot use this grant. |
Client types
- Confidential clients have a client secret and authenticate with
client_secret_post. - Public clients have no secret, use PKCE only, and are limited to the authorization-code grant.
Registering a client
OAuth clients are registered in the dashboard by a signed-in user. Dynamic Client Registration (RFC 7591) is not supported — clients cannot self-register.
Choose the registration lane that matches the connection:
- Authorization Code + PKCE creates a delegated connection only after a user completes consent. Reauthorizing the same client creates a separate connection; each connection keeps its own Agent assignment.
- Client Credentials creates an organization machine-to-machine connection for the registered client. It has no user or personal content lane. A hybrid organization client keeps this M2M connection separate from every delegated connection it creates.
The connection detail page in the dashboard shows its granted scope envelope, team binding, Agent assignment, and the optional task-start setup. A client registration's allowed scopes are only a ceiling: an issued delegated token still carries the scopes the user actually authorized.
Token lifetime and ongoing sessions
Every MCP HTTP request needs a currently valid credential, including requests on an
existing SSE session. An OAuth access token is rejected with HTTP 401 at its signed expiry
(exp), without a grace period; send the next request with a fresh access token. Keeping a
session open does not preserve old grants, team bindings or connection permissions.
A team write requires both an active binding to that team and the matching action permission; granting a permission does not add a team binding. See Management permissions.
Management scopes
An OAuth management scope is one exact permission written in OAuth's string format. Mutation
actions are independent: selecting create does not also select update or delete. A mutation
may imply the matching view permission at authorization time, but it never implies another content
family.
Personal OAuth clients can request these 21 management scopes:
| Area | Exact scopes |
|---|---|
| Resources | read:resources, create:resources, update:resources, delete:resources |
| Tools | read:tools, create:tools, update:tools, delete:tools |
| Prompts | read:prompts, create:prompts, update:prompts, delete:prompts |
| Agents | read:agents, create:agents, update:agents, delete:agents |
| Account | read:usage, read:dashboard, write:settings, read:mcp_settings, write:mcp_settings |
Organization OAuth clients can request these 47 organization-management scopes:
| Area | Exact scopes |
|---|---|
| Organization | read:organization, write:organization, read:organization_mcp_settings, write:organization_mcp_settings |
| Members | read:organization_members, invite:organization_members, remove:organization_members, update:organization_members |
| Organization roles | read:organization_roles, create:organization_roles, update:organization_roles, delete:organization_roles |
| Team-role catalog | read:organization_team_roles, create:organization_team_roles, update:organization_team_roles, delete:organization_team_roles |
| Teams | read:organization_teams, write:organization_teams, write:all_organization_teams |
| API keys | read:organization_keys, create:organization_keys, update:organization_keys, revoke:organization_keys |
| OAuth clients | read:organization_oauth_clients, create:organization_oauth_clients, update:organization_oauth_clients, delete:organization_oauth_clients |
| Resources | read:organization_resources, create:organization_resources, update:organization_resources, delete:organization_resources |
| Tools | read:organization_tools, create:organization_tools, update:organization_tools, delete:organization_tools |
| Prompts | read:organization_prompts, create:organization_prompts, update:organization_prompts, delete:organization_prompts |
| Agents | read:organization_agents, create:organization_agents, update:organization_agents, delete:organization_agents |
| Reporting and SSO | read:organization_usage, read:organization_billing, read:organization_sso, write:organization_sso |
Organization OAuth clients can also request these 40 Team-management scopes:
| Area | Exact scopes |
|---|---|
| Members | read:team_members, create:team_members, update:team_members, delete:team_members |
| Resources | read:team_resources, create:team_resources, update:team_resources, delete:team_resources, promote:team_resources |
| Tools | read:team_tools, create:team_tools, update:team_tools, delete:team_tools, promote:team_tools |
| Prompts | read:team_prompts, create:team_prompts, update:team_prompts, delete:team_prompts, promote:team_prompts |
| Agents | read:team_agents, create:team_agents, update:team_agents, delete:team_agents |
| Usage | read:team_usage |
| Roles | read:team_roles, create:team_roles, update:team_roles, delete:team_roles |
| Team settings | update:team_general, delete:team_general |
| All exposure policies | read:team_policies, write:team_policies |
| Resource policies | read:team_resource_policies, write:team_resource_policies |
| Tool policies | read:team_tool_policies, write:team_tool_policies |
| Prompt policies | read:team_prompt_policies, write:team_prompt_policies |
| Agent policies | read:team_agent_policies, write:team_agent_policies |
promote exists only for Team Resources, Tools, and Prompts; Agents have no promote scope.
General Team policy scopes expand to all four policy families. A family policy scope affects only
that family and never grants content-management authority.
write:all_organization_teams grants every Team permission, but only inside the OAuth
connection's independent Team binding. For Authorization Code connections, the authorizing user
must also still have live access to each bound Team; for Client Credentials, the registered Team
binding is the boundary.
Scopes and exposure policies do different jobs. A management scope permits the corresponding
dashboard/API operation; a Team exposure policy is a separate rule deciding which concrete
entities a credential can use through MCP. The token must also have the matching mcp_*
capability, and organization credentials can act only on their bound Teams. None of those controls
substitutes for another.
OAuth uses mcp_tools, mcp_resources, mcp_prompts, and mcp_agents for the same four
independent MCP-use categories. Management scopes such as read:agents and update:agents control
Agent inventory; they do not grant mcp_agents. Only these exact mcp_* names grant a category;
unknown or wildcard scope values grant none. For an organization registration containing
Client Credentials—including a hybrid registration—the requested management and mcp_* scopes
cannot exceed the registrar's current permission and capability authority in the selected team
context. An Authorization Code-only registration is bounded by the authorizing user's live
authority whenever its token is used instead, so a later role change takes effect on the next
request. A userless Client Credentials token has no live user-role intersection; its authority is
therefore capped when the client and its team bindings are registered.
Endpoints and discovery
| Endpoint | Purpose |
|---|---|
GET https://api.apistash.io/oauth/authorize | Authorization endpoint |
POST https://api.apistash.io/oauth/token | Token endpoint |
https://api.apistash.io/.well-known/oauth-authorization-server | Authorization-server metadata (RFC 8414) |
https://api.apistash.io/.well-known/oauth-protected-resource/mcp | Protected-resource metadata for the MCP endpoint (RFC 9728) |
MCP clients that support OAuth can discover these endpoints automatically: the MCP endpoint advertises its protected-resource metadata, which points to the authorization server. Discovery does not register the client. Supply the client ID — and the secret for a confidential client — from a dashboard registration. Claude Desktop's custom connector is one such confidential-client flow; see MCP Clients.
What an OAuth token can reach on MCP is governed by its scopes and — for organization clients — its team bindings, then filtered by the access model.
Persistent task-start setup does not require mcp_tools: setup, bootstrap, and agent_context
are always available to an authenticated token. The token must carry mcp_agents for Agent
selection or content, and the applicable Agent visibility policies still apply.
Rate limits and usage quotas
Every tool call is subject to a per-minute, per-hour, and per-day rate limit, and each account has a monthly tool-invocation quota. Both scale with your plan.
Fetching prompts and reading resources has its own, more generous rate limit and does not consume the monthly tool quota.
See Plans & Limits for the current tiers and the full list of enforced limits.
Revoking an API key
To revoke a key from the dashboard:
- Navigate to API Keys in the sidebar.
- Click Revoke next to the key you want to remove.
- Confirm the action in the dialog.
A revoked key is rejected on the next request, without a grace period. A request that was already authenticated and authorized may finish. The same request boundary applies when a user is deactivated or permissions are removed. The request must still satisfy the operation's consistency rules. For example, an earlier authorization does not allow a role to be changed in an organization that has since been deleted, or a credential to receive rights beyond the current delegation limits.
Security recommendations
- Do not commit API keys to source control. Use environment variables or secret managers.
- Use one key per agent or application so you can revoke access selectively.
- Rotate keys regularly, especially if they may have been exposed.