API authentication protocol
For direct API clients. CLI and MCP handle token exchange internally.
Connection parameters
| App origin | Local: https://localhost:3000 · Dev: https://dev.postede.com · Production contract: https://postede.com |
|---|---|
| OAuth issuer | <app-origin>/api/auth |
| OAuth discovery | <app-origin>/.well-known/oauth-authorization-server/api/auth |
| CLI resource | <app-origin>/cli; dedicated registered CLI client |
| MCP resource | Exact operator-registered MCP URL; separate client |
| API resource | <app-origin>/api/agent/v1 |
| Flow | Authorization code, S256 PKCE, exact registered callback, user consent |
| Public native client auth | none; no client secret |
Use the discovery response’s authorization and token endpoints. Requires operator-approved registration; dynamic registration is disabled. Match issuer, client and exact resource. Production acceptance is unverified.
Token exchange
POST <app-origin>/api/agent/v1/token-exchange
Content-Type: application/x-www-form-urlencoded · Body limit: 16,384 bytes.
| grant_type · required | urn:ietf:params:oauth:grant-type:token-exchange |
|---|---|
| subject_token_type · required | urn:ietf:params:oauth:token-type:access_token |
| subject_token · required | Resource-bound OAuth access token |
| resource · required | Exact API resource in the selected environment |
| scope · optional | Space-separated subset of granted Postede scopes |
curl -X POST "https://localhost:3000/api/agent/v1/token-exchange" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
--data-urlencode "subject_token=$POSTEDE_OAUTH_ACCESS_TOKEN" \
--data-urlencode "resource=https://localhost:3000/api/agent/v1"Response and API header
HTTP 200 · Cache-Control: no-store · Pragma: no-cache
{
"access_token": "API_ACCESS_TOKEN",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 60,
"scope": "postede.profile:read postede.projects:read"
}Scope reflects the grant or requested subset. Keep the API token in process memory; exchange again before its 60-second expiry. Live consent is validated on exchange; scopes and project access apply per API request.
Authorization: Bearer API_ACCESS_TOKENExchange failures
{"error":"invalid_token"}| HTTP | Error | Next action |
|---|---|---|
| 400 | invalid_request | Correct content type, body size or required fields. |
| 400 | invalid_target | Use the exact API resource in the same environment. |
| 400 | invalid_scope | Request a subset of valid granted scopes. |
| 401 | invalid_token | Refresh expired OAuth access; check issuer, audience and client binding. Reconnect if refresh fails. |
| 401 | invalid_grant | Reconnect with user consent; the grant is absent or revoked. |
| 429 | slow_down | Wait for Retry-After (60 seconds). Exchange limit: 240/minute per actor/client. |
| 503 | temporarily_unavailable | Check environment registration/configuration or retry after recovery. |
Do not retry invalid input unchanged or change environments to bypass a failure. Never include credentials in prompts, logs or support reports.