> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mudraid.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Exchange machine-client credentials for an access token

> The `client_credentials` grant for a machine client linked to an agent.
Authenticate with **exactly one** method:

* `private_key_jwt` (RFC 7523) — send `client_assertion_type` and a
  `client_assertion` signed with the client's registered, activated
  key (RS256 or ES256). The assertion's `iss` and `sub` are the
  `client_id`, its `aud` is the exact token endpoint identifier, and it
  carries `iat`, a short `exp` and a unique `jti` (replay-protected).
  The algorithm is pinned on the registered key, never taken from the
  assertion header alone.
* `client_secret_basic` — HTTP Basic with the client ID and secret. In
  production this needs a recorded, unexpired organization
  compatibility approval, re-checked at token time.

A client secret in the form body (`client_secret_post`) is not a
supported method and is refused as `invalid_client`.

The response is an RFC 9068 access token (`typ: at+jwt`, profile
`mudraid-access-token-v2`) whose `aud` is the requested `resource` and
whose `scope` is exactly the requested scopes. Linking a client grants
nothing on its own: the client needs an effective grant covering that
exact resource, and every requested scope must be covered by it — a
request naming any ungranted scope is refused whole as
`invalid_scope`, never silently narrowed. No refresh token is ever issued to a
machine client — re-authenticate for a new access token. Responses
carry `Cache-Control: no-store`.

`authorization_code` and `refresh_token` grants exist for the
human-delegation profile and are accepted only when enabled; read
`grant_types_supported` from the authorization-server metadata rather
than assuming them.




## OpenAPI

````yaml /openapi.yaml post /oauth2/token
openapi: 3.0.3
info:
  title: MudraID Public Contracts
  version: 2.0.0
  description: >
    This document defines the **public contracts** an integrator calls

    directly: the OAuth endpoints an agent's machine client uses to obtain an

    access token, the discovery documents a resource server verifies those

    tokens with, the scope file the FastAPI middleware loads, and the live

    decision endpoint enforcement adapters call. Administrative and internal

    endpoints are intentionally omitted — they are documented by each FastAPI

    service's auto-generated `/openapi.json` at runtime and may evolve more

    freely.


    What lives in *this* file:

      - `POST /oauth2/token` — machine-client `client_credentials` exchange
      - `POST /oauth2/revoke` — RFC 7009 token revocation
      - `GET /.well-known/oauth-authorization-server` — RFC 8414 metadata
      - `GET /.well-known/jwks.json` — token-verification keys
      - `GET /api/v1/platforms/{platform_id}/scopes.yaml` — scope file for the middleware
      - `POST /api/v1/adapter/enforcement/decide` — live decision for one protected action
      - the access-token claim shape and the `mudraid_scopes.yaml` schema

    **Retired native agent credentials.** `POST /api/v1/auth/token`,

    `POST /api/v1/auth/verify`, `POST /api/v1/auth/agents/me/platforms`,

    `POST /api/v1/agents/register`, `PUT
    /api/v1/agents/{agent_id}/permissions/{platform_id}`

    and `POST /api/v1/agents/{agent_id}/rotate-secret` (and its `/emergency`

    variant) answer `410 Gone` with `code: agent_native_credentials_retired`.

    Register an agent identity (`POST /api/v1/agents/identities`), link an

    OAuth machine client to it and use `POST /oauth2/token`. Every

    `/api/v1/verify/*` path also answers `410 Gone` with the same code.
servers:
  - url: https://api.mudraid.ai
    description: Production
security: []
tags:
  - name: oauth
    description: |
      OAuth 2.0 endpoints. A machine client linked to an agent authenticates
      here with `private_key_jwt` (recommended) or, with an organization
      compatibility approval, `client_secret_basic`.
  - name: resource-server
    description: |
      Documents a protected API (or the FastAPI middleware) reads to verify
      access tokens and enforce route scopes.
  - name: enforcement
    description: |
      The live decision endpoint called by enforcement adapters (the V2
      middleware, the Node adapter/sidecar and the Kong plugin).
paths:
  /oauth2/token:
    post:
      tags:
        - oauth
      summary: Exchange machine-client credentials for an access token
      description: >
        The `client_credentials` grant for a machine client linked to an agent.

        Authenticate with **exactly one** method:


        * `private_key_jwt` (RFC 7523) — send `client_assertion_type` and a
          `client_assertion` signed with the client's registered, activated
          key (RS256 or ES256). The assertion's `iss` and `sub` are the
          `client_id`, its `aud` is the exact token endpoint identifier, and it
          carries `iat`, a short `exp` and a unique `jti` (replay-protected).
          The algorithm is pinned on the registered key, never taken from the
          assertion header alone.
        * `client_secret_basic` — HTTP Basic with the client ID and secret. In
          production this needs a recorded, unexpired organization
          compatibility approval, re-checked at token time.

        A client secret in the form body (`client_secret_post`) is not a

        supported method and is refused as `invalid_client`.


        The response is an RFC 9068 access token (`typ: at+jwt`, profile

        `mudraid-access-token-v2`) whose `aud` is the requested `resource` and

        whose `scope` is exactly the requested scopes. Linking a client grants

        nothing on its own: the client needs an effective grant covering that

        exact resource, and every requested scope must be covered by it — a

        request naming any ungranted scope is refused whole as

        `invalid_scope`, never silently narrowed. No refresh token is ever
        issued to a

        machine client — re-authenticate for a new access token. Responses

        carry `Cache-Control: no-store`.


        `authorization_code` and `refresh_token` grants exist for the

        human-delegation profile and are accepted only when enabled; read

        `grant_types_supported` from the authorization-server metadata rather

        than assuming them.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/TokenRequest'
      responses:
        '200':
          description: Access token issued
          headers:
            Cache-Control:
              schema:
                type: string
                example: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '400':
          description: |
            `invalid_request`, `invalid_grant` (no effective grant, or the
            linked agent is no longer active), `invalid_scope`,
            `invalid_target` or `unsupported_grant_type`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: |
            `invalid_client` — one uniform answer for every client
            authentication failure, so the response never says which check
            failed. Carries a `WWW-Authenticate` header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '429':
          description: |
            `temporarily_unavailable` — this client's per-minute token budget
            is spent. Read `Retry-After` rather than inventing a delay. For
            `private_key_jwt` the budget is spent only after the assertion
            verifies, so a third party cannot exhaust it with forged
            assertions naming your client.
          headers:
            Retry-After:
              description: Whole seconds to wait.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '500':
          description: |
            `server_error` — an unexpected internal fault. No token was
            released.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
components:
  schemas:
    TokenRequest:
      type: object
      required:
        - grant_type
        - resource
        - scope
      properties:
        grant_type:
          type: string
          enum:
            - client_credentials
          description: |
            `client_credentials` for a machine client. Other grants are
            accepted only when enabled — see `grant_types_supported` in the
            authorization-server metadata.
        resource:
          type: string
          format: uri
          description: |
            The exact approved resource URI (RFC 8707). Becomes the token's
            `aud`.
        scope:
          type: string
          example: tasks:read tasks:write
          description: |
            Space-delimited requested scopes. Required and non-empty — an
            absent or empty scope is `invalid_scope`, never a request for the
            full entitlement. Every scope must be covered by an effective
            grant for this resource.
        client_assertion_type:
          type: string
          enum:
            - urn:ietf:params:oauth:client-assertion-type:jwt-bearer
          description: Required with `client_assertion` (`private_key_jwt`).
        client_assertion:
          type: string
          description: Signed RFC 7523 client assertion (`private_key_jwt`).
    TokenResponse:
      type: object
      required:
        - access_token
        - token_type
        - expires_in
      properties:
        access_token:
          type: string
          description: RS256 `at+jwt` access token. See AccessTokenClaims.
        token_type:
          type: string
          enum:
            - Bearer
        expires_in:
          type: integer
          default: 300
          maximum: 900
          description: |
            Lifetime in seconds actually applied to this token. The default is
            `300` (five minutes). Operators can tune it, but the value is
            hard-capped at the `900`-second (fifteen-minute) ceiling — a
            configured value above the ceiling is clamped down to it. Read the
            lifetime from this field rather than assuming either number.
        scope:
          type: string
          description: The granted space-delimited scopes (the requested set).
    OAuthError:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          enum:
            - invalid_request
            - invalid_client
            - invalid_grant
            - invalid_scope
            - invalid_target
            - unsupported_grant_type
            - temporarily_unavailable
            - server_error
        error_description:
          type: string
          description: Human-readable reason. Branch on `error`, never on this text.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.