# API authentication protocol

For direct API clients. CLI and MCP handle token exchange internally.
Developer preview: use an operator-approved client and matching environment.

## Connection parameters

| Parameter | Value |
| --- | --- |
| App origin | `https://localhost:3000`, `https://dev.postede.com`, or production contract `https://postede.com` |
| OAuth issuer | `<app-origin>/api/auth` |
| OAuth discovery | `<app-origin>/.well-known/oauth-authorization-server/api/auth` |
| CLI OAuth resource | `<app-origin>/cli`; dedicated registered CLI client |
| MCP OAuth resource | Exact operator-registered MCP URL; separate client |
| API resource | `<app-origin>/api/agent/v1` |
| OAuth flow | Authorization code with S256 PKCE, exact registered callback and user consent |
| Public native client authentication | `none`; no client secret |

Use the discovery response's authorization and token endpoints. Dynamic client
registration is disabled. The subject token must match the registered client,
issuer and exact CLI or MCP resource. Keep registrations/grants separate across
environments. Production registration and acceptance remain separate release gates.

## Token exchange

`POST <app-origin>/api/agent/v1/token-exchange`

Content type: `application/x-www-form-urlencoded`. Body limit: 16,384 bytes.

| Form field | Required | Value |
| --- | --- | --- |
| `grant_type` | Yes | `urn:ietf:params:oauth:grant-type:token-exchange` |
| `subject_token_type` | Yes | `urn:ietf:params:oauth:token-type:access_token` |
| `subject_token` | Yes | Resource-bound OAuth access token |
| `resource` | Yes | Exact API resource for the selected environment |
| `scope` | No | Space-separated subset of granted Postede scopes |

```sh
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`.

```json
{
  "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 values reflect the grant or requested subset. Store the API token in
process memory and exchange again before expiry. Every exchange validates live
consent. API requests use:

```http
Authorization: Bearer API_ACCESS_TOKEN
```

Project access, scopes and action rules are checked per API request. Never
include credentials in prompts, logs or support reports.

## Exchange failures

The exchange uses an OAuth error object, not the endpoint API error envelope:

```json
{"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 from 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` | Live consent is absent or revoked; reconnect with user consent. |
| 429 | `slow_down` | Wait for `Retry-After` (60 seconds); exchange cap is 240/minute per actor/client. |
| 503 | `temporarily_unavailable` | Check environment registration/configuration or retry after service recovery. |

Do not repeat invalid requests unchanged or switch environments to bypass a
failure. API operation-specific statuses are in the
[endpoint reference](https://postede.com/docs/api). Client recovery instructions
are in [CLI setup](https://postede.com/docs/cli/setup) and
[MCP setup](https://postede.com/docs/mcp/setup).
