Authentication

API keys, the read/write/admin ladder, and which accounts a key can reach.

Every request carries a key as a Bearer token:

curl "$BIDANDSCALE_URL/api/v1/accounts" \
  -H "Authorization: Bearer bsk_…"

Create keys in Settings → API — workspace-level for keys that reach several accounts, or inside an account's settings for one clamped to it. The key is shown once. If you lose it, revoke it and create another; there is no way to retrieve it.

Keys start with bsk_. The same key also authenticates the MCP server — send it as the Bearer there and a headless agent gets the gads_* tools with exactly this key's scope, account reach and expiry. Both surfaces verify it with the same code, so revoking a key stops REST and MCP on the next request. A key is an agent credential: nothing it proposes auto-applies, on either surface.

Two dimensions

A key carries a permission scope and an account scope, independently.

Permission scope

Ranked, not orthogonal: a key satisfies a requirement when its own tier is at least as high.

ScopeCan do
readEvery GET. Also starting agent runs — a read key's run gets a restricted toolset with every write tool removed, so it can analyse but not propose.
writeEverything above, plus proposing changes and triggering syncs and audits.
adminEverything above, plus changing optimization targets — which change what the system does unattended.

A request above a key's tier gets 403, not 401. The credential is valid; the permission is not.

The approval grant

Deciding the approval queue — approve, reject, revert — needs the separate canApprove grant, not a higher rung. It sits beside the ladder on purpose: a key that may propose a change is not, by that fact, a key that may wave its own proposal through. A refusal here is a 403 carrying required_grant: "approve" instead of required_scope.

Every change a key proposes queues for review, even one the safe-apply policy would auto-apply for a person. A key is an unattended caller, so applying stays a separate, deliberate decision.

Account scope

Key shapeReaches
Account keyExactly one account. Naming any other in a URL is a 403.
Workspace key, all accountsEvery account in the workspace, current and future.
Workspace key, chosen accountsOnly the accounts picked when the key was created.

GET /api/v1/accounts always returns exactly what the key can reach, so it works as a discovery call whatever the shape.

An out-of-reach account never returns a 404 that would confirm it exists — you get a 403 for accounts, and agent runs outside your reach are reported as not found.

Rotating a key

Create the new key, deploy it, then revoke the old one. Revocation takes effect on the next request. Revoked keys keep their row so /history can still attribute past changes to them.

Handling keys

  • Server-side only. There are no CORS headers; a key in a browser bundle is a key anyone can read.
  • One key per consumer, so you can revoke one without taking down the rest.
  • Give each key the lowest scope that works. A reporting job wants read.

On this page