Skip to main content
Every public BeeOS endpoint is authenticated. There are four credential types, chosen by the protocol surface you’re calling — not by the SDK language. Pick the one that matches the host you’re hitting:
CredentialHeaderWhere it worksWho issues it
User JWTAuthorization: Bearer <jwt>gateway.beeos.ai (web / mobile / desktop) and openapi.beeos.ai (SDK)User login (Gateway POST /api/v1/auth/login)
User API Key (oag_)Authorization: Bearer oag_...openapi.beeos.ai (SDK) onlyUser self-service (Gateway)
Agent API Key (bak_)Authorization: Bearer bak_...a2a.beeos.ai (A2A JSON-RPC) onlyAgent owner (Gateway)
MCP OAuthAuthorization: Bearer <oauth-access-token>mcp.beeos.ai (MCP) onlyOAuth dance with Gateway
The two host buckets are deliberately separate so SDK consumers never have to think about JWT refresh, and so A2A / MCP clients can’t accidentally pick up a user’s session cookie.

1. User JWT — web / mobile / desktop sessions

Issued by the main Gateway (gateway.beeos.ai, local :9080) on login. Short-lived (typically 1 h, refreshed automatically by the SDK / web client). Grants full access to the user’s own resources — any operation the user can do from the web UI works with their JWT.
POST /api/v1/auth/login        ← Gateway, not openapi-gw
Content-Type: application/json

{ "email": "you@example.com", "password": "..." }
The response contains an access_token; send it as Authorization: Bearer <jwt> to either Gateway (web traffic) or openapi.beeos.ai (SDK traffic — it accepts the same JWT). Use JWT only for interactive sessions. For backend cron jobs, CI, or anything where you store the credential at rest, prefer an oag_ key — JWTs expire and rotating refresh tokens through a headless script is more friction than it’s worth.

2. oag_ — User API Key (SDK calls)

The credential SDK consumers actually pass through. Self-issued by the user — no admin involvement.
POST /api/v1/api-keys            ← Gateway
Authorization: Bearer <user-jwt>
Content-Type: application/json

{
  "name": "my-cron-job",
  "rate_limit_per_minute": 600,
  "expires_at": "2027-05-01T00:00:00Z"
}
The response contains the plaintext key (oag_<64-hex>) once; store it immediately. The Gateway only retains a SHA-256 hash.

Format

oag_   ────────────────────────────────────────────
↑      ↑
fixed  32 random bytes hex-encoded (64 chars)
The first 12 characters (oag_<8 hex>) are stored separately as key_prefix for safe display in lists / audit logs.

Authorization

oag_ User API Keys are user-scoped: each key is bound to one owner and inherits full access to that owner’s resources. Every route is gated by owner-ACL inside the handler — cross-tenant access is denied uniformly, no matter which credential the caller used. There is no per-route scope vocabulary any more. Earlier revisions of this surface required scopes like agents:read / tasks:write to be explicitly granted at key-creation time, with missing scopes returning 403 insufficient_scope. That gate has been removed (see the v1.1.0 changelog migration below) — once authenticated, an oag_ key behaves like the underlying user’s JWT for the purposes of authorization.
Migration from pre-v1.1.0 SDKs. If you used createAPIKey(name, scopes, ...) or POST /api/v1/api-keys with a scopes body field, drop the argument — existing keys continue to work and grant full owner-level access. The 403 insufficient_scope error is no longer emitted; fold it into your generic 403 / forbidden handler.

Listing / revoking

GET    /api/v1/api-keys              ← Gateway, list yours
DELETE /api/v1/api-keys/{id}         ← Gateway, revoke
There is no rotation endpoint — rotate by issuing a new key, flipping callers over, and revoking the old one. (See Versioning & Deprecation once P2-D lands for the recommended rollover window.)

3. bak_ — Agent API Key (A2A JSON-RPC)

For external agents (or other organisations’ systems) calling your agent over the A2A protocol on a2a.beeos.ai. Scoped to a single agent — not a user. Each agent can hold at most 3 active keys at a time.
POST /api/v1/agents/{agentId}/keys   ← Gateway (NOT openapi-gw)
Authorization: Bearer <agent-owner-jwt>
Content-Type: application/json

{ "name": "partner-integration-acme-corp" }

Format

bak_   ────────────────────────────────────────────
↑      ↑
fixed  32 random bytes hex-encoded (64 chars)

Listing / revoking

GET    /api/v1/agents/{agentId}/keys                ← Gateway
DELETE /api/v1/agents/{agentId}/keys/{keyId}        ← Gateway

Why a separate prefix?

bak_ keys have no scope system and no user identity — they’re an agent invocation token. A2A Gateway authenticates the key, resolves the agent owner, then admits the request as that agent. Mixing oag_ and bak_ namespaces would have made this mapping ambiguous. See a2a.beeos.ai agent integration contract.

4. MCP OAuth — Model Context Protocol clients

mcp.beeos.ai is a Model Context Protocol host. MCP clients (Claude Desktop, Cursor, custom MCP servers) discover the authorisation server via the standard MCP discovery doc and complete a short OAuth 2.0 authorisation-code flow; the access token they receive is only valid on mcp.beeos.ai — not on openapi.beeos.ai and not on a2a.beeos.ai. You shouldn’t normally hand-craft this flow — your MCP client implements it. If you’re building a new MCP client, see the MCP authentication spec.

Choosing between them

See Choosing a Protocol for the full side-by-side comparison (host, wire format, scope unit, streaming / async / push semantics, and worked examples). For deeper context on “which protocol surface should I be calling in the first place” see docs/guides/choosing-a-protocol.md (P1-G, planned).

Common mistakes

  • Using oag_ on a2a.beeos.ai — A2A Gateway rejects it with 401 unauthorized. The two host buckets share zero credential state by design.
  • Embedding oag_ keys in shipped mobile / desktop binaries — the user’s keys belong to the user, not your app. Web apps should authenticate the user (JWT) and your server mints a server-side oag_ for backend work.
  • Expecting per-route scope gating — the agents:* / tasks:* / files:* / instances:* scope vocabulary was removed in v1.1.0. oag_ keys are user-scoped; owner-ACL inside each handler is the sole authorization gate.
  • Expecting key rotation from openapi-gw — rotation / revocation lives on the main Gateway (see endpoints above). The OpenAPI Gateway is read-only for credential metadata; it does not expose POST /api-keys.

Where each credential is checked in code

CredentialValidator locationNote
User JWTbackend/pkg/infrastructure/authclient/middleware.go AuthenticateRSA public-key verification of RS256 JWT
oag_backend/pkg/infrastructure/authclient/middleware.go Authenticate → Auth Service gRPC LookupAPIKeySHA-256 hash compared against api_keys.key_hash
bak_A2A Gateway auth/agent_api_key.go → Agent Identity gRPC LookupAgentAPIKeySHA-256 hash compared against agent_api_keys.key_hash
MCP OAuthMCP Gateway internal OAuth providerPer MCP spec

See also