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

# Troca o código de autorização, renova o token do usuário final ou emite o token M2M

> O backend do seu produto troca o código que o widget entregou (authorization_code, com o code_verifier do PKCE) e renova (refresh_token). O cliente M2M pede o próprio token por client_credentials com Authorization Basic, sem a chave secreta (aí o escopo auth:token não se aplica). Sempre Cache-Control: no-store. Exige uma chave secreta criada COM o escopo auth:token (chaves sem restrição não o têm).



## OpenAPI

````yaml /api-reference/openapi.json post /auth/token
openapi: 3.1.0
info:
  title: UNIFOKAL API
  version: v1
  summary: >-
    Verificação de identidade: sessão criada no servidor, jornada no widget,
    resultado no webhook.
  description: >-
    A versão vive no caminho (/v1); não existe header de versão. Adição
    compatível (campo, módulo ou chave nova) NÃO sobe a versão: ignore o que não
    conhecer. Todo erro tem a forma { error, message }: o código é estável, a
    mensagem não. Ids são prefixo + ULID. Webhooks são assinados (HMAC SHA-256
    no header X-IDSAAS-Signature, janela de 300s; o segredo é SEU, mínimo 32
    caracteres) e o header x-idsaas-event carrega o tipo do evento.
  termsOfService: https://unifokal.com/termos
  contact:
    name: UNIFOKAL
    url: https://docs.unifokal.com/
servers:
  - url: https://api.unifokal.com/v1
security: []
externalDocs:
  description: >-
    Documentação completa em https://docs.unifokal.com/. Briefing para agentes
    de IA em https://unifokal.com/llms.txt.
  url: https://docs.unifokal.com/
paths:
  /auth/token:
    post:
      summary: >-
        Troca o código de autorização, renova o token do usuário final ou emite
        o token M2M
      description: >-
        O backend do seu produto troca o código que o widget entregou
        (authorization_code, com o code_verifier do PKCE) e renova
        (refresh_token). O cliente M2M pede o próprio token por
        client_credentials com Authorization Basic, sem a chave secreta (aí o
        escopo auth:token não se aplica). Sempre Cache-Control: no-store. Exige
        uma chave secreta criada COM o escopo auth:token (chaves sem restrição
        não o têm).
      operationId: createAuthToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthTokenRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/AuthTokenRequest'
      responses:
        '200':
          description: >-
            Tokens emitidos. AuthTokenResponse na troca do código,
            AuthRefreshResponse na renovação, M2mTokenResponse no
            client_credentials.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/AuthTokenResponse'
                  - $ref: '#/components/schemas/AuthRefreshResponse'
                  - $ref: '#/components/schemas/M2mTokenResponse'
        '400':
          description: >-
            Códigos estáveis: invalid_json, validation_error, invalid_grant,
            invalid_target, invalid_scope. Ramifique pelo campo error, nunca
            pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - invalid_json
                          - validation_error
                          - invalid_grant
                          - invalid_target
                          - invalid_scope
        '401':
          description: >-
            Códigos estáveis: invalid_api_key, invalid_client. Ramifique pelo
            campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - invalid_api_key
                          - invalid_client
        '402':
          description: >-
            Códigos estáveis: insufficient_balance. Ramifique pelo campo error,
            nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - insufficient_balance
        '403':
          description: >-
            Códigos estáveis: credential_type_not_allowed,
            organization_suspended, test_key_used_in_production,
            insufficient_scope, ip_not_allowed. Ramifique pelo campo error,
            nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - credential_type_not_allowed
                          - organization_suspended
                          - test_key_used_in_production
                          - insufficient_scope
                          - ip_not_allowed
        '413':
          description: >-
            Códigos estáveis: payload_too_large. Ramifique pelo campo error,
            nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - payload_too_large
        '429':
          description: >-
            Códigos estáveis: rate_limited, spend_cap_reached,
            volume_cap_reached. Ramifique pelo campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - rate_limited
                          - spend_cap_reached
                          - volume_cap_reached
      security:
        - secretKey: []
components:
  schemas:
    AuthTokenRequest:
      description: >-
        Três concessões (RFC 6749): authorization_code (com a chave secreta e o
        code_verifier do PKCE S256), refresh_token (com a chave secreta) e
        client_credentials (cliente M2M por client_secret_basic, sem a chave
        secreta). Aceita application/x-www-form-urlencoded ou JSON.
      oneOf:
        - type: object
          required:
            - grant_type
            - code
            - code_verifier
          properties:
            grant_type:
              type: string
              const: authorization_code
            code:
              type: string
              maxLength: 128
            code_verifier:
              type: string
              minLength: 43
              maxLength: 128
        - type: object
          required:
            - grant_type
            - refresh_token
          properties:
            grant_type:
              type: string
              const: refresh_token
            refresh_token:
              type: string
              maxLength: 256
        - type: object
          properties:
            grant_type:
              type: string
              enum:
                - client_credentials
            audience:
              type: string
              maxLength: 200
            scope:
              type: string
              maxLength: 3250
          required:
            - grant_type
          description: 'Cliente M2M: Authorization Basic com o id e o segredo do cliente.'
    AuthTokenResponse:
      type: object
      description: >-
        Resposta da troca do código (authorization_code). Sempre Cache-Control:
        no-store.
      required:
        - access_token
        - token_type
        - expires_in
        - refresh_token
        - refresh_expires_in
        - session_id
        - amr
        - acr
        - user
        - verification_id
      properties:
        access_token:
          type: string
          description: >-
            JWT de acesso (RFC 9068, typ at+jwt, RS256). Verifique localmente
            pelo JWKS do emissor.
        token_type:
          type: string
          const: Bearer
        expires_in:
          type: integer
          description: Segundos de vida do token de acesso.
        refresh_token:
          type: string
          description: >-
            Refresh opaco com rotação: cada renovação devolve um novo;
            reapresentar um antigo derruba a sessão.
        refresh_expires_in:
          type: integer
          description: >-
            Segundos até o fim da sessão (o menor entre o prazo ocioso e o
            absoluto).
        session_id:
          type: string
          pattern: ^ses_[0-9A-HJKMNP-TV-Z]{26}$
        amr:
          type: array
          items:
            type: string
          description: >-
            Métodos de autenticação usados (RFC 8176): pwd, otp_email, otp_sms,
            totp, recovery.
        acr:
          type: string
          enum:
            - aal1
            - aal2
        user:
          $ref: '#/components/schemas/AuthTokenUser'
        verification_id:
          type:
            - string
            - 'null'
          description: A verificação do cadastro com KYC no mesmo flow; null no login.
    AuthRefreshResponse:
      type: object
      description: >-
        Resposta da renovação (refresh_token): sem user. Sempre Cache-Control:
        no-store.
      required:
        - access_token
        - token_type
        - expires_in
        - refresh_token
        - refresh_expires_in
        - session_id
        - amr
        - acr
      properties:
        access_token:
          type: string
          description: >-
            JWT de acesso (RFC 9068, typ at+jwt, RS256). Verifique localmente
            pelo JWKS do emissor.
        token_type:
          type: string
          const: Bearer
        expires_in:
          type: integer
          description: Segundos de vida do token de acesso.
        refresh_token:
          type: string
          description: >-
            Refresh opaco com rotação: cada renovação devolve um novo;
            reapresentar um antigo derruba a sessão.
        refresh_expires_in:
          type: integer
          description: >-
            Segundos até o fim da sessão (o menor entre o prazo ocioso e o
            absoluto).
        session_id:
          type: string
          pattern: ^ses_[0-9A-HJKMNP-TV-Z]{26}$
        amr:
          type: array
          items:
            type: string
          description: >-
            Métodos de autenticação usados (RFC 8176): pwd, otp_email, otp_sms,
            totp, recovery.
        acr:
          type: string
          enum:
            - aal1
            - aal2
    M2mTokenResponse:
      type: object
      description: 'Resposta do client_credentials (RFC 6749 4.4.3): sem refresh token.'
      required:
        - access_token
        - token_type
        - expires_in
        - scope
      properties:
        access_token:
          type: string
          description: >-
            JWT de acesso (RFC 9068, typ at+jwt, RS256). Verifique localmente
            pelo JWKS do emissor.
        token_type:
          type: string
          const: Bearer
        expires_in:
          type: integer
          description: Segundos de vida do token de acesso.
        scope:
          type: string
          description: Escopos concedidos, separados por espaço.
    ErrorEnvelope:
      type: object
      description: >-
        Todo erro da API tem exatamente esta forma. O campo error é um código
        estável (contrato); message é texto livre e pode mudar sem aviso.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Código estável do erro. Ramifique por ele.
        message:
          type: string
          description: Texto explicativo. NÃO é contrato.
      additionalProperties: false
    AuthTokenUser:
      type: object
      required:
        - id
        - email
        - email_verified
        - phone
        - phone_verified
        - external_id
        - status
        - mfa
        - public_metadata
        - created_at
        - last_sign_in_at
      properties:
        id:
          type: string
          pattern: ^usr_[0-9A-HJKMNP-TV-Z]{26}$
        email:
          type:
            - string
            - 'null'
        email_verified:
          type: boolean
        phone:
          type:
            - string
            - 'null'
        phone_verified:
          type: boolean
        external_id:
          type:
            - string
            - 'null'
        status:
          type: string
          enum:
            - pending
            - active
            - locked
            - disabled
        mfa:
          type: object
          required:
            - enrolled
          properties:
            enrolled:
              type: array
              items:
                type: string
                enum:
                  - totp
                  - sms
                  - email
        public_metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        last_sign_in_at:
          type:
            - string
            - 'null'
          format: date-time
  securitySchemes:
    secretKey:
      type: http
      scheme: bearer
      description: >-
        Chave secreta (unifokal_live_ em produção, unifokal_test_ no sandbox: 64
        hex mais CRC-32) no Authorization: Bearer. Só no seu servidor: nunca no
        browser ou app (o widget monta apenas com o id vs_ da sessão). Não
        existe chave publishable.

````

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