Authentication
Authenticate with an organization API key or a Koast OAuth token, and control what each key can do with scopes, surfaces, brand limits, expiry and a publish ceiling.
Every request needs a bearer credential in the Authorization header:
Authorization: Bearer koast_sk_...The API accepts two kinds of credential:
| Credential | Looks like | Acts as | Use it for |
|---|---|---|---|
| Organization API key | koast_sk_… | Your organization, narrowed by the key's scopes and brands | Servers, scripts, CI, integrations |
| OAuth access token | koast_at_… | The person who approved it, with the scopes they approved | The koast CLI's browser sign-in, MCP clients |
A request with no credential, or one Koast doesn't recognise, gets 401 unauthenticated:
{
"error": {
"code": "unauthenticated",
"message": "Unauthorized: a valid Bearer credential is required",
"details": null,
"requestId": "822a70ef-bf74-4a8d-a938-e2027c6156bf"
}
}Organization API keys
Keys belong to the organization, not to the person who created them. A key keeps working when its creator leaves the team. In activity logs, launches and every other audit trail, actions taken with a key show as API key: <key name>.
Only the organization owner and admins can create, edit and revoke keys, in Koast under Settings, API Keys. Other members can open that tab too, but only to see and revoke the apps they signed in to themselves, not the organization's keys. The secret is shown once, when the key is created.
Each key carries five settings.
Scopes
| Scope | Allows |
|---|---|
read | Every GET |
write | Creating and changing drafts, templates, automations, workflows, pipeline cards, webhooks and tracker settings |
publish | launch-campaign and edit-live-campaign, the two operations that spend money on the ad platform. Also saving, activating or running a workflow that has a launch, pause, budget or agent step, and moving a pipeline card into a column that publishes |
A call outside the key's scopes gets 403 insufficient_scope, and details.requiredScope names the missing scope:
{
"error": {
"code": "insufficient_scope",
"message": "This credential does not carry the write scope required by create-campaign-launch",
"details": { "requiredScope": "write" },
"requestId": "3c686d99-8253-418b-8e1b-0d4f99b582c8"
}
}Each operation's required scope is listed in the API reference.
Surfaces: MCP and API
Under Works with, a key has two switches: MCP (the Koast MCP server) and API (this REST API and the CLI). New keys have MCP on and API off. The API switch can only be turned on for an organization on an Agency plan.
A key with API off gets 403 surface_disabled on /v1:
{
"error": {
"code": "surface_disabled",
"message": "This API key is not enabled for the Koast API. An organization owner or admin can turn API access on for the key in Settings, API Keys.",
"details": null,
"requestId": "eff8e5b2-d482-4370-9d6b-77c19d37f67c"
}
}Brand limit
A key reaches every brand in the organization, today and later, unless you limit it to a list of brands. A limited key gets 403 brand_not_allowed on any other brand, and GET /v1/brands lists only its brands:
{
"error": {
"code": "brand_not_allowed",
"message": "This API key is not allowed to access brand 2.",
"details": { "reason": "brand_not_allowed" },
"requestId": "9fa28b1a-b8fc-49d7-8c7b-8af98c356b6d"
}
}A brand-limited key can only create or change webhook endpoints that are filtered to its own brands. It can save and run a workflow with an agent step: the agent then works only on the key's brands, however the run starts. It can save an agent step only in a workflow created with an API key; in a workflow that belongs to a person it gets 403, because that agent would run with the person's access. It only reaches workflows of its own brands: another brand's workflow, or one that works across brands, answers 404, and it can't save a workflow whose trigger or steps reach beyond its brands. OAuth tokens and keys with no brand limit see and manage every workflow, as in the app.
Expiry
A key never expires unless you set an expiry. An expired or revoked key gets 401 unauthenticated.
Publish ceiling
A key with the publish scope also needs a publish limit: the most it may ever put live, in total, in dollars. Each publish and each live budget edit adds its budget to the key's committed total:
launch-campaignadds the budgets the launch sets: the campaign budget for a campaign that carries one, otherwise its ad set budgets.edit-live-campaignadds the new budget amount.
In the app this is the Publish limit. A key without a ceiling gets 403 spend_ceiling_required. A call that would take the key past its ceiling gets 403 spend_ceiling_exceeded, with the ceiling, the amount already committed and this call's amount in the message, and nothing is published. When the ceiling can't be checked, the call gets 503 unavailable and nothing is published.
launch-campaign without "confirm": true only validates and previews, and doesn't count against the ceiling.
A workflow's launch and budget steps, and the launches, budget edits and card moves of its agent steps, are checked against the ceiling of the key that runs it, when each step runs. A run that a key starts runs as that key, whoever owns the workflow. Two cases publish through a key without calling launch-campaign, and both go through the same checks:
- A workflow a key saved, started by its own trigger (a schedule, a pipeline stage, a Frame.io event or a monitor). Koast records which key saved the workflow, and the run acts as that key: its scopes, its spend ceiling and its brand limit apply, its agent steps reach only the key's brands, and the activity shows API key: <key name>. If the key was deleted, revoked or has expired, or its organization's plan no longer admits it, the whole run is refused and nothing runs, for example "The API key that created this workflow no longer exists, so the workflow cannot run as that key. Nothing was run." A step on a brand outside the key's brands is refused: "This step acts on brand N, which the API key running this workflow is not allowed to access. Nothing was published." A key's own
run-workflowchecks the key again when the run starts. - Moving a pipeline card into a column that publishes, or creating one there. A key with
publishpublishes the card, reserved against its ceiling exactly likelaunch-campaign, and released again if the publish fails. With a key that lackspublish, nothing is published and the answer says so: "Not published: this API key does not have the publish scope. Moving a card into a publishing column publishes only for a key with the publish scope, within its spend ceiling."
OAuth tokens
koast login signs you in through the browser with Koast's OAuth server and stores the tokens on your machine. The token acts as you: your role, your brands, and the scopes you tick on the consent screen. publish is off unless you tick it, and needs a spend limit.
OAuth tokens are user credentials, so the API checks your organization's plan, not a key's switches.
Each OAuth token is bound to one resource, the RFC 8707 resource its authorization named: the MCP server, https://mcp.koast.ai/mcp, or the API, https://api.koast.ai/v1.
- An API token works on
/v1only. On MCP it gets401, with JSON-RPC error-32001and aWWW-Authenticateheader. - An MCP token, or one authorized with no resource, works on MCP only. That covers every Claude and ChatGPT connector, existing ones included, so no connector needs reconnecting. On
/v1it gets401 audience_mismatch, "Unauthorized: a valid Bearer credential is required", withWWW-Authenticate: Bearer. - To get an API token, send
resource=https://api.koast.ai/v1to/oauth/authorize. You may repeat it on/oauth/token. A different resource there gets400 invalid_target. - A refresh keeps the binding. You can leave
resourceout of a refresh. Naming the other resource gets400 invalid_target.
koast login asks for the API resource, so its token works on /v1 only. koast login --api-key is unaffected.
An OAuth token acts as the person who approved it:
- A member limited to some brands gets
403 brand_not_allowed, "You do not have access to brand N.", for any other brand, on/v1and on MCP, and those brands are left out of the ad accounts list. - A person in several organizations reaches only the brands of organizations whose plan admits the surface: an Agency plan with an active subscription for
/v1, any active paid plan for MCP.
On webhook endpoints, an OAuth user must be the organization owner or an admin, or the call gets 403 forbidden.
Plan check
Every credential is checked against the organization's plan before anything else runs. The REST API needs an Agency plan with an active subscription. Anything else gets 403 plan_required. See Access and plans.
Keeping keys safe
- Store keys in a secret manager or environment variable, never in code or a repository.
- Use one key per integration, so you can revoke one without breaking the others.
- Give each key the fewest scopes and brands it needs.
- Revoke a key the moment you stop using it, or as soon as you suspect it leaked.