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

# Exchange an authorization code for a grant token

> Server to server, authenticated by the app key alone — there is no grant yet, and minting one is the point. The key decides only *which app is asking*; whether anything is minted at all was decided by a person at authorize time and is carried entirely by the code.



## OpenAPI

````yaml /api-reference/openapi.yaml post /connect/token
openapi: 3.0.3
info:
  title: Payman Connect API
  version: 0.1.0
  description: >-
    The HTTP contract behind Payman Connect: exchange a customer's consent for a
    grant, send actions against the accounts it reaches, and follow an approval
    to its receipt. Every call carries your app key, and every call after the
    exchange carries the grant. Non-2xx responses share one `ApiError` envelope.
    Most integrations never write these calls: the SDK speaks them for you.
servers:
  - url: https://api.paygent.payman.ai
    description: Production
  - url: http://localhost:8891
    description: A local stack
security:
  - bearerAuth: []
tags:
  - name: Connect
    description: >-
      Third-party access. A registered app presents its app key together with a
      grant token its customer consented to; the pair reaches exactly the one
      deployment the grant names, and nothing else on this API.
paths:
  /connect/token:
    post:
      tags:
        - Connect
      summary: Exchange an authorization code for a grant token
      description: >-
        Server to server, authenticated by the app key alone — there is no grant
        yet, and minting one is the point. The key decides only *which app is
        asking*; whether anything is minted at all was decided by a person at
        authorize time and is carried entirely by the code.
      operationId: exchangeConnectCode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConnectTokenRequest'
      responses:
        '200':
          description: The grant. Its token is on this response only.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectGrantWithToken'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: >-
            `invalid_connect_credentials` — an app key that is missing, unknown,
            revoked or expired, or a code that is unknown, expired, already
            used, issued to another app, or bound to a different redirect URI.
            All one answer: which it was is exactly what somebody holding a
            stolen code would use to work out what else they need.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      security:
        - connectAppKey: []
components:
  schemas:
    ConnectTokenRequest:
      type: object
      required:
        - code
        - redirectUri
      properties:
        code:
          type: string
          maxLength: 256
        redirectUri:
          type: string
          maxLength: 2048
          description: The same URI the code was issued for. Compared byte for byte.
    ConnectGrantWithToken:
      allOf:
        - $ref: '#/components/schemas/ConnectGrant'
        - type: object
          required:
            - grantToken
            - environment
          properties:
            grantToken:
              type: string
              description: >-
                The plaintext `pgc_grant_…` token, on this response only.
                Present it together with the app key on every Connect call.
            environment:
              allOf:
                - $ref: '#/components/schemas/ConnectEnvironment'
              description: >-
                The app's world, restated where the credential is handed over so
                an integration can assert it is holding the kind of key it
                thinks it is. Here and not on the base grant shape: the consumer
                console's grant lists have no use for it.
    ApiError:
      type: object
      required:
        - category
        - code
        - message
      description: >-
        The envelope every non-2xx response uses. `code` is a stable slug a
        client can match on exactly; `message` is safe to show a user as-is.
      properties:
        category:
          $ref: '#/components/schemas/ApiErrorCategory'
        code:
          type: string
          example: agent_not_configured
        message:
          type: string
          example: Configure this agent before sending it messages.
        details:
          type: object
          additionalProperties: true
          nullable: true
          description: >-
            Structured context — field errors, the agent's own status, and so
            on.
    ConnectGrant:
      type: object
      required:
        - id
        - appId
        - deploymentId
        - scopes
        - grantTokenMasked
        - createdAt
        - expiresAt
      properties:
        id:
          type: string
          format: uuid
        appId:
          type: string
          format: uuid
        deploymentId:
          type: string
          format: uuid
        scopes:
          type: array
          items:
            type: string
          description: Checked on every Connect route, even while there is only one.
        grantTokenMasked:
          type: string
          description: Enough to recognise the token, never enough to use it.
        createdAt:
          type: string
          format: date-time
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
        expiresAt:
          type: string
          format: date-time
          description: Grants age out at ninety days, like every other credential here.
        revokedAt:
          type: string
          format: date-time
          nullable: true
    ConnectEnvironment:
      type: string
      enum:
        - sandbox
        - live
      description: >-
        Which world a Connect app lives in. `sandbox` — the Durango demo bank,
        where nothing is money; a sandbox app may declare only DURANGO agents
        and its credentials are minted as `pgc_app_test_…` / `pgc_grant_test_…`.
        `live` — everything else; a live app cannot declare DURANGO agents.
    ApiErrorCategory:
      type: string
      description: >-
        Fixed taxonomy, matching K2's so a client that already handles K2 errors
        handles these too.
      enum:
        - VALIDATION
        - AUTH
        - PERMISSION
        - NOT_FOUND
        - CONFLICT
        - RATE_LIMIT
        - UPSTREAM
        - TIMEOUT
        - INTERNAL
  responses:
    BadRequest:
      description: The request failed validation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    TooManyRequests:
      description: >-
        Rate limited (`rate_limited`). `details` carries the limit, the window
        and how long until it reopens.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
  securitySchemes:
    connectAppKey:
      type: apiKey
      in: header
      name: x-paygent-connect-app-key
      description: >-
        A Connect app key (`pgc_app_…`), identifying the registered third-party
        app. Always presented together with `connectGrant` — the pair is one
        credential, and either half alone is refused. Accepted on the
        `/connect/*` surface only.

````