# Issue an access token

OAuth 2.0 token endpoint (RFC 6749). Supports three grants:
- `authorization_code` — exchange a code obtained from the browser
`GET /oauth/authorize` flow (PKCE `code_verifier` required). Returns an access
token and a rotating refresh token.
- `client_credentials` — machine-to-machine token for a marketplace installation.
Always requires the explicit `location_id`; the installation is resolved from the
authenticated OAuth client plus that Location. No refresh token is returned.
- `refresh_token` — rotate a refresh token from the Authorization Code flow.
The previous refresh token is invalidated; replaying it revokes the token family.
The new access token never exceeds the scopes of the original consent: `scope` may
narrow it, unknown codes are ignored, and `invalid_scope` is returned when every known
scope in `scope` is outside the consent (the refresh token is not rotated then). The
same rule applies to `scope` on the code exchange. A refresh token lives 30 days from
its issue, the family 90 days from the code exchange; after that the user approves
access again.

Confidential clients authenticate with HTTP Basic (`client_secret_basic`) or send
`client_id` and `client_secret` in the form body (`client_secret_post`); public clients
(PKCE-only) send their `client_id` in the form body. There is **no password grant**.
Errors from this endpoint use the standard OAuth 2.0 error object
(`{"error": "...", "error_description": "..."}`), not `application/problem+json`.

Endpoint: POST /oauth/token
Version: 3.0.0-preview

## Request fields (application/x-www-form-urlencoded):

  - `grant_type` (string, required)
    Enum: "authorization_code"

  - `code` (string, required)

  - `redirect_uri` (string)
    Optional (OAuth 2.1 §10.2): PKCE already binds the code to the client. If sent, it
must equal the `redirect_uri` of the authorization request byte for byte.

  - `code_verifier` (string, required)

  - `client_id` (string, required)

  - `client_secret` (string)
    Confidential clients using `client_secret_post` only.

  - `scope` (string)

  - `resource` (string)
    RFC 8707 resource indicator: `https://api.alteg.io/api/v3` or an Altegio MCP
address under `https://mcp.alteg.io/pro` (an address ending in `/readonly` caps
the scopes at read-only). Compared case-insensitively on scheme and host and
without a trailing slash. Every accepted address yields the same audience
`https://api.alteg.io/api/v3`; on code exchange it must equal the `resource` of
the authorization request.

  - `grant_type` (string, required)
    Enum: "refresh_token"

  - `refresh_token` (string, required)

  - `scope` (string)
    Optional. Narrows the new access token within the scopes of the original consent.
Unknown codes (such as `openid`) are ignored; known scopes outside the consent are
dropped, and if none of the known scopes is granted the request fails with
`invalid_scope` and the refresh token is not rotated. Omitted or no known scope:
every scope of the consent that is still granted.

  - `resource` (string)
    RFC 8707 resource indicator: `https://api.alteg.io/api/v3` or an Altegio MCP
address under `https://mcp.alteg.io/pro`. Compared case-insensitively on scheme
and host and without a trailing slash. Every accepted address yields the same
audience `https://api.alteg.io/api/v3`, so on refresh it may differ from the
address used at authorization. An address ending in `/readonly` narrows the new
access token to read scopes; the next refresh without it gets the full set again.

  - `grant_type` (string, required)
    Enum: "client_credentials"

  - `location_id` (integer, required)
    The Location this token acts in. The server resolves the installation from the
authenticated OAuth client plus this Location, so one token is always bound to
exactly one Location.

## Request examples:

  - `Marketplace M2M token for one Location` (unknown)

  - `Exchange an authorization code (PKCE)` (unknown)

  - `Rotate a refresh token` (unknown)

## Response 200:

  - `200` (unknown)
    Token issued.

## Response 200 fields (application/json):

  - `access_token` (string, required)
    Opaque access token — never parse it.

  - `token_type` (string, required)
    Enum: "Bearer"

  - `expires_in` (integer, required)
    Access token lifetime in seconds (3600).

  - `refresh_token` (string)
    Rotating refresh token; only issued by the Authorization Code flow.

  - `scope` (string)
    Space-separated scopes carried by the token.

## Response 400:

  - `400` (unknown)
    Invalid request or grant (RFC 6749 error object).

## Response 400 fields (application/json):

  - `error` (string, required)
    OAuth error code (`invalid_request`, `invalid_grant`, `invalid_client`, ...).

  - `error_description` (string)

## Response 401:

  - `401` (unknown)
    Client authentication failed (RFC 6749 error object).

## Response 401 fields (application/json):

  - `error` (string, required)
    OAuth error code (`invalid_request`, `invalid_grant`, `invalid_client`, ...).

  - `error_description` (string)

## Response 429:

  - `429` (unknown)
    Too many token requests for this client, subject or address. Failed grant attempts
count against the same budget. Retry after the interval in `Retry-After`; do not
retry a rejected `authorization_code` — start a new authorization.

## Response 429 fields (application/json):

  - `error` (string, required)
    OAuth error code (`invalid_request`, `invalid_grant`, `invalid_client`, ...).

  - `error_description` (string)

## Response 429 headers (application/json):

  - `Retry-After` (integer)
    Seconds to wait before retrying.
    Example: 60

## Response 500:

  - `500` (unknown)
    Token could not be issued because of an unexpected server error (OAuth error object).

## Response 500 fields (application/json):

  - `error` (string, required)
    OAuth error code (`invalid_request`, `invalid_grant`, `invalid_client`, ...).

  - `error_description` (string)

## Response 503:

  - `503` (unknown)
    An authorization dependency is unavailable (OAuth error object). Back off and retry;
already-issued access tokens keep working until they expire.

## Response 503 fields (application/json):

  - `error` (string, required)
    OAuth error code (`invalid_request`, `invalid_grant`, `invalid_client`, ...).

  - `error_description` (string)

