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

# Token endpoint (all grants)

> authorization_code (PKCE verifier required, single-use code) | refresh_token (rotates — a stale token returns invalid_grant and revokes the session) | password (first-party dev only, org-scoped; 401 mfa_required when enrolled) | client_credentials (M2M, access token only) | device_code (poll) | token-exchange (guardian with guardian:act_as scope to a minor, consent-checked).



## OpenAPI

````yaml /api-reference/openapi.json post /oauth/token
openapi: 3.0.1
info:
  title: TKTChurch Identity (TKTAuth)
  version: v1
  description: >-
    Authorization server at prod-auth.tktchurch.com. Public OAuth2/OIDC
    endpoints need no auth; /api/v1 self routes need a Bearer access token
    issued to the user. the listed resource:action permission. Management lists
    return `{ metadata: { total, page, per }, items: [...] }`.
servers:
  - url: https://prod-auth.tktchurch.com
    description: Production
  - url: http://localhost:8080
    description: Local backchannel
  - url: https://calendar.tktchurch.net
    description: Calendar (Events)
security: []
tags:
  - name: authentication
    description: >-
      OAuth2 (RFC 6749), PAR (9126), revocation/introspection (7009/7662),
      device flow (8628), OIDC, registration (7591), sign-in flows, password
      reset and federated identity.
  - name: users
    description: >-
      Signed-in profile, identities, recovery, membership and Member Pass.
      Bearer only.
  - name: membership
    description: Membership matching, Member Pass, invite claims and family join codes.
  - name: sessions
    description: List, inspect and revoke your own sessions. Bearer only.
  - name: security
    description: TOTP setup and passkey management. Bearer only.
  - name: consents-privacy
    description: Your consent grants, privacy notices, data requests and exports.
  - name: family
    description: >-
      Families, dependents and guardian delegation. Bearer; delegation needs
      guardian:act_as.
  - name: system
    description: Liveness, readiness, build info and public maintenance status.
  - name: events
    description: >-
      Public church events listing, search and detail on calendar.tktchurch.net.
      No auth.
paths:
  /oauth/token:
    post:
      tags:
        - authentication
      summary: Token endpoint (all grants)
      description: >-
        authorization_code (PKCE verifier required, single-use code) |
        refresh_token (rotates — a stale token returns invalid_grant and revokes
        the session) | password (first-party dev only, org-scoped; 401
        mfa_required when enrolled) | client_credentials (M2M, access token
        only) | device_code (poll) | token-exchange (guardian with
        guardian:act_as scope to a minor, consent-checked).
      operationId: token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - grant_type
                - client_id
              properties:
                grant_type:
                  type: string
                  enum:
                    - authorization_code
                    - refresh_token
                    - password
                    - client_credentials
                    - urn:ietf:params:oauth:grant-type:device_code
                    - urn:ietf:params:oauth:grant-type:token-exchange
                  example: password
                client_id:
                  type: string
                  example: dev-console
                client_secret:
                  type: string
                code:
                  type: string
                redirect_uri:
                  type: string
                code_verifier:
                  type: string
                username:
                  type: string
                  example: amina@tktchurch.com
                email:
                  type: string
                  format: email
                password:
                  type: string
                mfa_code:
                  type: string
                  description: 6-digit TOTP when MFA is enrolled
                mfa_backup_code:
                  type: string
                refresh_token:
                  type: string
                scope:
                  type: string
                  example: openid profile email offline_access
                device_code:
                  type: string
                subject_token:
                  type: string
                  description: Guardian access token (token-exchange)
                subject_token_type:
                  type: string
                requested_subject:
                  type: string
                  description: Minor user id (token-exchange)
                resource:
                  type: string
                  description: RFC 8707 indicator
                authorization_details:
                  type: string
                  description: RFC 9396 JSON array (string-encoded)
      responses:
        '200':
          description: Issued tokens
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
              example:
                access_token: >-
                  eyJhbGciOiJFUzI1NiIsImtpZCI6IjAyMDI2MDkyMiJ9.eyJzdWIiOiJ1c2VyX2EwMSIsIm9yZ19pZCI6Im9yZ19lMDIiLCJjbGllbnRfaWQiOiJkZXYtY29uc29sZSIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwgb2ZmbGluZV9hY2Nlc3MiLCJleHAiOjE3OTg3NjQwMDB9.c2lnbmF0dXJl
                token_type: Bearer
                expires_in: 3600
                refresh_token: >-
                  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl9pZCI6InRva19iazAxIiwiZXhwIjoxODAxNjk2MDAwfQ.c2lnbmF0dXJl
                scope: openid profile email offline_access
                id_token: >-
                  eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJ1c2VyX2EwMSIsImF1ZCI6ImRldi1jb25zb2xlIiwiZXhwIjoxNzk4NzY0MDAwfQ.c2lnbmF0dXJl
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: invalid_grant
                error_description: Authorization code has expired or already been used
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_client:
                  value:
                    error: invalid_client
                    error_description: Client authentication failed
                mfa_required:
                  value:
                    error: mfa_required
                    error_description: Multi-factor authentication is required
                reuse_detected:
                  value:
                    error: invalid_grant
                    error_description: Refresh token reuse detected — session revoked
      security:
        - {}
        - clientBasic: []
      x-codeSamples:
        - lang: Swift
          label: Swift (token password)
          source: >-
            import Foundation


            let url = URL(string:
            "https://prod-auth.tktchurch.com/oauth/token")!

            var request = URLRequest(url: url)

            request.httpMethod = "POST"

            request.setValue("application/json", forHTTPHeaderField:
            "Content-Type")

            request.httpBody = try JSONSerialization.data(withJSONObject: [
                "grant_type": "password",
                "client_id": "dev-console",
                "client_secret": ProcessInfo.processInfo.environment["TKT_CLIENT_SECRET"]!,
                "username": "amina@tktchurch.com",
                "password": "correct-horse-battery-staple",
                "scope": "openid profile email offline_access"
            ])

            let (data, _) = try await URLSession.shared.data(for: request)

            // 200 → TokenResponse JSON. 401 {"error":"mfa_required"} → retry
            with "mfa_code".

            let token = try JSONDecoder().decode(TokenResponse.self, from: data)

            print(token.accessToken)
        - lang: Swift
          label: Swift (token refresh)
          source: >-
            import Foundation


            var request = URLRequest(url: URL(string:
            "https://prod-auth.tktchurch.com/oauth/token")!)

            request.httpMethod = "POST"

            request.setValue("application/json", forHTTPHeaderField:
            "Content-Type")

            request.httpBody = try JSONSerialization.data(withJSONObject: [
                "grant_type": "refresh_token",
                "client_id": "dev-console",
                "refresh_token": storedRefreshToken
            ])

            let (data, response) = try await URLSession.shared.data(for:
            request)

            // Rotation: persist the NEW refresh_token. A stale token returns

            // invalid_grant and revokes the whole session (reuse detection).

            guard (response as? HTTPURLResponse)?.statusCode == 200 else {
                throw AuthError.refreshReuseDetected
            }

            let token = try JSONDecoder().decode(TokenResponse.self, from: data)
        - lang: Swift
          label: Swift (token client credentials)
          source: >-
            import Foundation


            // Machine-to-machine: no user, access token only (no refresh_token,
            no id_token).

            var request = URLRequest(url: URL(string:
            "https://prod-auth.tktchurch.com/oauth/token")!)

            request.httpMethod = "POST"

            request.setValue("application/json", forHTTPHeaderField:
            "Content-Type")

            let credentials = "\(clientID):\(clientSecret)".data(using:
            .utf8)!.base64EncodedString()

            request.setValue("Basic \(credentials)", forHTTPHeaderField:
            "Authorization")

            request.httpBody = try JSONSerialization.data(withJSONObject: [
                "grant_type": "client_credentials",
                "client_id": clientID,
                "scope": "read write"
            ])

            let (data, _) = try await URLSession.shared.data(for: request)

            let token = try JSONDecoder().decode(TokenResponse.self, from: data)
components:
  schemas:
    TokenResponse:
      type: object
      properties:
        access_token:
          type: string
        token_type:
          type: string
          example: Bearer
        expires_in:
          type: integer
          example: 3600
        refresh_token:
          type: string
        scope:
          type: string
        id_token:
          type: string
        issued_token_type:
          type: string
    Error:
      type: object
      required:
        - error
        - error_description
      properties:
        error:
          type: string
          description: Machine-readable code
        error_description:
          type: string
          description: Human-readable detail
  securitySchemes:
    clientBasic:
      type: http
      scheme: basic

````