openapi: 3.0.3
info:
  title: Auth Service API
  description: |
    Authentication and authorization service for the True Markets platform — issues JWT access/refresh tokens used across the Gateway and other True Markets APIs.

    ## Base URLs

    | Environment | Base URL |
    |---|---|
    | Production | `https://api.truemarkets.co/v1/auth` |
    | UAT (sandbox) | `https://api.uat.truemarkets.co/v1/auth` |

    ## Authentication tutorial

    Programmatic clients use an ECDSA-signed challenge to mint short-lived JWTs.

    1. **Create an account** at [https://www.truemarkets.co](https://www.truemarkets.co) (passkey, email, magic link, or Sign in with Apple).
    2. **Register an API key** in your account's *API Keys* settings page. Generate an EC P-256 key pair locally and submit only the public key — the private key never leaves your machine. You'll receive a `key_id` (UUID).
    3. **Mint JWTs** by calling `POST /api-key/token` with `key_id`, a current `timestamp` (Unix seconds, within ±30s of server UTC time), and `signature` — an ES256 (ECDSA P-256 + SHA-256) signature of the message `{key_id}.{timestamp}`, base64url-encoded. The response returns `access_token` and `refresh_token`.
    4. **Call True Markets APIs** (Gateway) with `Authorization: Bearer <access_token>`.
    5. **Refresh** expired access tokens via `POST /token/refresh` with the `refresh_token` — no re-signing required.

    ### Quick start

    ```bash
    # 1. Mint a JWT (key_id and signature computed client-side)
    curl -X POST https://api.truemarkets.co/v1/auth/api-key/token \
      -H "Content-Type: application/json" \
      -d '{"key_id":"<UUID>","timestamp":<UNIX_SECONDS>,"signature":"<BASE64URL_ES256_SIG>"}'

    # 2. Fetch JWKS to verify token signatures locally
    curl https://api.truemarkets.co/.well-known/jwks.json

    # 3. Refresh a token before expiry
    curl -X POST https://api.truemarkets.co/v1/auth/token/refresh \
      -H "Content-Type: application/json" \
      -d '{"refresh_token":"<REFRESH_TOKEN>"}'
    ```

    ## Support
    - 📧 [support@truemarkets.co](mailto:support@truemarkets.co)

    ## Change Log
    | Version | Date       | Notes                                                                                                                                                     |
    |---------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
    | v1.3.1  | 2026-06-29 | `GET /health` and `GET /.well-known/jwks.json` marked internal. `POST /api-key/token` and `POST /token/refresh` consolidated under one `Authentication` tag. |
    | v1.3.0  | 2026-05-16 | Added Sign in with Google: `POST /google/register` and `DELETE /google/disconnect`.                                                                       |
    | v1.2.2  | 2026-04-27 | First-party browser flows (passkey, magic link, one-time code, Sign in with Apple, JumpCloud SSO, credential management) marked internal. The public reference now covers the API-key token flow. |
    | v1.2.1  | 2026-04-24 | Published production and UAT base URLs and documented the API-key JWT flow end to end.                                                                    |
    | v1.2.0  | 2026-03-16 | Added credential management: `POST /api-key/register`, `POST /api-key/token`, `GET /api-key/`, `PUT`/`DELETE /api-key/{id}`, and the matching `/passkey/` endpoints. |
    | v1.1.0  | 2026-01-06 | `POST /email/magic-link` accepts an optional `client_id` selecting the domain the magic link points to.                                                   |
    | v1.0.0  | 2025-12-11 | Initial release. Passkey registration and authentication, email magic link and one-time code, Sign in with Apple, JumpCloud SSO, token refresh, and JWKS. |
  version: 1.3.1
servers:
  - url: https://api.truemarkets.co/v1/auth
    description: Production
  - url: https://api.uat.truemarkets.co/v1/auth
    description: UAT (sandbox)
paths:
  
  /token/refresh:
    post:
      summary: Refresh access token
      description: Exchange a valid refresh token for a new access/refresh token pair
      operationId: refreshToken
      tags:
        - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefreshTokenRequest"
      responses:
        "200":
          description: Tokens refreshed successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccessTokensResponse"
        "400":
          description: Invalid request body or missing refresh token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid or expired refresh token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api-key/token:
    post:
      summary: Exchange API key for tokens
      description: |
        Verify an ECDSA P-256 signature and issue JWT tokens. The client signs
        the message `{key_id}.{unix_timestamp}` with their private key using ES256.
        The timestamp must be within ±30 seconds of the server time.
      operationId: exchangeAPIKeyToken
      tags:
        - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/APIKeyTokenExchangeRequest"
      responses:
        "200":
          description: Tokens issued successfully. A user key yields an access and refresh token; an organization key yields an access token only.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/AccessTokensResponse"
                  - $ref: "#/components/schemas/OrganizationTokenResponse"
        "400":
          description: Invalid request (missing fields)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid API key, expired, revoked, invalid signature, or timestamp out of range
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  
  schemas:
    
    Error:
      type: object
      description: Shared API error envelope
      required:
        - type
        - message
        - request_id
      properties:
        type:
          type: string
          description: Closed error category fixing the HTTP status
          enum:
            - invalid_argument
            - unauthenticated
            - permission_denied
            - not_found
            - method_not_allowed
            - conflict
            - failed_precondition
            - resource_exhausted
            - unavailable
            - internal
        code:
          type: string
          description: >-
            Specific error condition from the fleet code registry. Extensible: clients MUST tolerate values not listed here and fall back to type-based handling. Omitted when no code applies.
        message:
          type: string
          description: Human-readable description of the failure
        request_id:
          type: string
          description: Identifier of the request that produced the error

    AccessTokensResponse:
      type: object
      required:
        - access_token
        - refresh_token
        - expires_in
        - token_type
      properties:
        access_token:
          type: string
          description: JWT access token
        refresh_token:
          type: string
          description: JWT refresh token
        expires_in:
          type: string
          format: date-time
          description: Access token expiration timestamp
        token_type:
          type: string
          description: Token type (always "Bearer")
          example: Bearer

    OrganizationTokenResponse:
      type: object
      required:
        - access_token
        - token_type
        - expires_in
      properties:
        access_token:
          type: string
          description: JWT access token
        token_type:
          type: string
          description: Token type (always "Bearer")
          example: Bearer
        expires_in:
          type: string
          format: date-time
          description: Access token expiration timestamp

    RefreshTokenRequest:
      type: object
      required:
        - refresh_token
      properties:
        refresh_token:
          type: string
          description: The refresh token to exchange

    APIKeyTokenExchangeRequest:
      type: object
      required:
        - key_id
        - timestamp
        - signature
      properties:
        key_id:
          type: string
          format: uuid
          description: The API key ID
        timestamp:
          type: integer
          format: int64
          description: Current Unix timestamp (must be within ±30s of server time)
        signature:
          type: string
          description: Base64url-encoded ECDSA P-256 signature of `{key_id}.{timestamp}`
