Adds the full bedroom-developer-docs OpenSpec change implementation: - docs/developers/README.md — index page - docs/developers/quick-start.md — bootstrap to working token in 7 steps - docs/developers/concepts.md — AgentIdP, AGNTCY, lifecycle, OAuth 2.0, free tier - docs/developers/guides/README.md — guide index - docs/developers/guides/register-an-agent.md — all fields, validation, common errors - docs/developers/guides/manage-credentials.md — generate, list, rotate, revoke - docs/developers/guides/issue-and-revoke-tokens.md — OAuth 2.0 flow, introspect, revoke - docs/developers/guides/query-audit-logs.md — filters, pagination, 90-day retention - docs/developers/api-reference.md — all 14 endpoints, all error codes, curl examples Also commits deferred OpenSpec housekeeping from previous session: - Archives phase-1-mvp-implementation change to openspec/changes/archive/ - Adds bedroom-developer-docs change artifacts (30/30 tasks complete) - Syncs 4 delta specs to openspec/specs/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.6 KiB
ADDED Requirements
Requirement: Issue access token via Client Credentials grant
The system SHALL issue a signed RS256 JWT access token when an agent authenticates with a valid client_id (agentId) and client_secret using the OAuth 2.0 Client Credentials grant (RFC 6749 §4.4). The request body SHALL use application/x-www-form-urlencoded encoding. The response SHALL include Cache-Control: no-store and Pragma: no-cache headers. The system SHALL enforce a free-tier limit of 10,000 token requests per calendar month per client.
Scenario: Successful token issuance
- WHEN a POST request to
/tokenis received withgrant_type=client_credentials, a validclient_id, and a validclient_secretfor anactiveagent - THEN the system verifies the credential, issues a signed JWT with
sub=agentId,scope= requested (or default) scope,exp= now + 3600s, and returns200 OKwithTokenResponse
Scenario: Invalid client credentials rejected
- WHEN a POST request to
/tokenis received with aclient_idthat does not exist or aclient_secretthat does not match - THEN the system returns
401 Unauthorizedwitherror: invalid_client
Scenario: Suspended agent cannot obtain tokens
- WHEN a POST request to
/tokenis received for an agent withstatus: suspended - THEN the system returns
403 Forbiddenwitherror: unauthorized_clientand a description indicating the agent is suspended
Scenario: Decommissioned agent cannot obtain tokens
- WHEN a POST request to
/tokenis received for an agent withstatus: decommissioned - THEN the system returns
403 Forbiddenwitherror: unauthorized_client
Scenario: Unsupported grant type rejected
- WHEN a POST request to
/tokenis received with agrant_typeother thanclient_credentials - THEN the system returns
400 Bad Requestwitherror: unsupported_grant_type
Scenario: Invalid scope rejected
- WHEN a POST request to
/tokenis received with ascopevalue that contains an unrecognised scope identifier - THEN the system returns
400 Bad Requestwitherror: invalid_scope
Scenario: Free tier monthly token limit enforced
- WHEN a POST request to
/tokenis received and the agent has already made 10,000 token requests in the current calendar month - THEN the system returns
403 Forbiddenwitherror: unauthorized_clientand a description indicating the monthly free-tier limit is reached
Requirement: Token introspection (RFC 7662)
The system SHALL determine whether a given access token is currently active (valid, not expired, not revoked). The endpoint SHALL return 200 OK for both active and inactive tokens — the active field in the response SHALL indicate validity. The caller SHALL hold a valid Bearer token with tokens:read scope.
Scenario: Active token introspection
- WHEN a POST request to
/token/introspectis received with a valid, non-expired, non-revoked token and the caller hastokens:readscope - THEN the system returns
200 OKwithactive: trueand the token's claims (sub,client_id,scope,token_type,iat,exp)
Scenario: Expired or revoked token introspection
- WHEN a POST request to
/token/introspectis received with a token that is expired or has been revoked - THEN the system returns
200 OKwithactive: falseand no other claims
Scenario: Insufficient scope for introspection
- WHEN a POST request to
/token/introspectis received with a valid Bearer token that does not havetokens:readscope - THEN the system returns
403 Forbiddenwithcode: INSUFFICIENT_SCOPE
Requirement: Token revocation (RFC 7009)
The system SHALL invalidate a given access token immediately. Revoking an already-revoked or expired token SHALL be a successful, idempotent operation (RFC 7009 §2.1). Revoked token JTIs SHALL be stored in Redis with TTL equal to the token's remaining lifetime.
Scenario: Successful token revocation
- WHEN a POST request to
/token/revokeis received with a valid Bearer token and atokenparameter containing a valid JWT - THEN the system adds the token's JTI to the Redis revocation list, and returns
200 OKwith an empty body
Scenario: Revocation of already-revoked token is idempotent
- WHEN a POST request to
/token/revokeis received with a token that is already in the Redis revocation list - THEN the system returns
200 OKwith an empty body (no error)
Scenario: Missing token parameter rejected
- WHEN a POST request to
/token/revokeis received with notokenfield in the body - THEN the system returns
400 Bad Requestwithcode: VALIDATION_ERROR
Requirement: JWT claims structure
All issued JWTs SHALL contain the following claims: sub (agentId), client_id (agentId), scope (space-separated granted scopes), jti (UUID, unique per token), iat (issued-at Unix timestamp), exp (expiry Unix timestamp). Tokens SHALL be signed with RS256.
Scenario: JWT contains required claims
- WHEN a token is issued via
POST /token - THEN the decoded JWT payload contains
sub,client_id,scope,jti,iat, andexpfields
Requirement: Rate limiting on token endpoints
The system SHALL enforce a rate limit of 100 requests per minute per client_id on all token endpoints.
Scenario: Rate limit exceeded on token endpoint
- WHEN a client sends more than 100 requests to any token endpoint within a 60-second window
- THEN the system returns
429 Too Many RequestswithX-RateLimit-Limit,X-RateLimit-Remaining: 0, andX-RateLimit-Resetheaders