{
  "openapi": "3.1.0",
  "info": {
    "title": "Auth Service API",
    "description": "Authentication and authorization service for the True Markets platform — issues JWT access/refresh tokens used across the Gateway, DeFi, and other True Markets APIs.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| Production | `https://api.truemarkets.co/v1/auth` |\n| UAT (sandbox) | `https://api.uat.truemarkets.co/v1/auth` |\n\n## Authentication tutorial\n\nProgrammatic clients use an ECDSA-signed challenge to mint short-lived JWTs.\n\n1. **Create an account** at [https://www.truemarkets.co](https://www.truemarkets.co) (passkey, email, magic link, or Sign in with Apple).\n2. **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).\n3. **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`.\n4. **Call True Markets APIs** (Gateway, DeFi) with `Authorization: Bearer <access_token>`.\n5. **Refresh** expired access tokens via `POST /token/refresh` with the `refresh_token` — no re-signing required.\n\n### Quick start\n\n```bash\n# 1. Mint a JWT (key_id and signature computed client-side)\ncurl -X POST https://api.truemarkets.co/v1/auth/api-key/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"key_id\":\"<UUID>\",\"timestamp\":<UNIX_SECONDS>,\"signature\":\"<BASE64URL_ES256_SIG>\"}'\n\n# 2. Fetch JWKS to verify token signatures locally\ncurl https://api.truemarkets.co/.well-known/jwks.json\n\n# 3. Refresh a token before expiry\ncurl -X POST https://api.truemarkets.co/v1/auth/token/refresh \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"refresh_token\":\"<REFRESH_TOKEN>\"}'\n```\n\n## Support\n- 📧 [support@truemarkets.co](mailto:support@truemarkets.co)\n",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.truemarkets.co/v1/auth",
      "description": "Production"
    },
    {
      "url": "https://api.uat.truemarkets.co/v1/auth",
      "description": "UAT (sandbox)"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check",
        "description": "Returns the health status of the auth service",
        "operationId": "getHealth",
        "tags": [
          "Health"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/jwks.json": {
      "get": {
        "summary": "Get JSON Web Key Set",
        "description": "Returns the public keys used to verify JWT tokens",
        "operationId": "getJWKS",
        "tags": [
          "JWKS"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "JWKS retrieved successfully",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "Caching directive (public, max-age=3600)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JWKSResponse"
                }
              }
            }
          },
          "500": {
            "description": "Failed to retrieve JWKS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/token/refresh": {
      "post": {
        "summary": "Refresh access token",
        "description": "Exchange a valid refresh token for a new access/refresh token pair",
        "operationId": "refreshToken",
        "tags": [
          "Token"
        ],
        "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/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired refresh token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api-key/token": {
      "post": {
        "summary": "Exchange API key for tokens",
        "description": "Verify an ECDSA P-256 signature and issue JWT tokens. The client signs\nthe message `{key_id}.{unix_timestamp}` with their private key using ES256.\nThe timestamp must be within ±30 seconds of the server time.\n",
        "operationId": "exchangeAPIKeyToken",
        "tags": [
          "API Key Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/APIKeyTokenExchangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tokens issued successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccessTokensResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (missing fields)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key, expired, revoked, invalid signature, or timestamp out of range",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT access token obtained from authentication endpoints"
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "required": [
          "status",
          "timestamp",
          "service"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "Health status",
            "example": "healthy"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Current server timestamp"
          },
          "service": {
            "type": "string",
            "description": "Service name",
            "example": "auth-service"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "Error message"
          }
        }
      },
      "StatusResponse": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "Operation status",
            "example": "success"
          }
        }
      },
      "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"
          }
        }
      },
      "JWKSResponse": {
        "type": "object",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JWK"
            },
            "description": "Array of JSON Web Keys"
          }
        }
      },
      "JWK": {
        "type": "object",
        "required": [
          "kty",
          "kid",
          "use",
          "alg"
        ],
        "properties": {
          "kty": {
            "type": "string",
            "description": "Key type",
            "example": "RSA"
          },
          "kid": {
            "type": "string",
            "description": "Key ID"
          },
          "use": {
            "type": "string",
            "description": "Key use (sig for signature)",
            "example": "sig"
          },
          "alg": {
            "type": "string",
            "description": "Algorithm",
            "example": "RS256"
          },
          "n": {
            "type": "string",
            "description": "RSA modulus (base64url encoded)"
          },
          "e": {
            "type": "string",
            "description": "RSA exponent (base64url encoded)"
          }
        }
      },
      "RefreshTokenRequest": {
        "type": "object",
        "required": [
          "refresh_token"
        ],
        "properties": {
          "refresh_token": {
            "type": "string",
            "description": "The refresh token to exchange"
          }
        }
      },
      "OTCRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address to send the one-time code"
          }
        }
      },
      "VerifyOTCRequest": {
        "type": "object",
        "required": [
          "email",
          "code"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address used for verification"
          },
          "code": {
            "type": "string",
            "description": "The one-time code received via email",
            "minLength": 6,
            "maxLength": 6
          }
        }
      },
      "MagicLinkRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address to send the magic link"
          },
          "client_id": {
            "type": "string",
            "description": "Client application identifier. Determines which domain the magic link points to. Defaults to \"true_markets\" if not specified.",
            "example": "truemarkets_ai"
          }
        }
      },
      "MagicLinkVerifyRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The magic link token from the email URL"
          }
        }
      },
      "MagicLinkClaimRequest": {
        "type": "object",
        "required": [
          "session_id"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "description": "The session ID returned when creating the magic link"
          }
        }
      },
      "MagicLinkSessionResponse": {
        "type": "object",
        "required": [
          "session_id",
          "status",
          "expires_at"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "description": "Unique session identifier"
          },
          "status": {
            "type": "string",
            "description": "Session status (pending, approved, claimed)",
            "enum": [
              "pending",
              "approved",
              "claimed"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Session expiration timestamp"
          }
        }
      },
      "GenerateAuthenticationOptionsRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the user to authenticate"
          }
        }
      },
      "PasskeyRegistrationRequest": {
        "type": "object",
        "required": [
          "response"
        ],
        "properties": {
          "response": {
            "$ref": "#/components/schemas/WebAuthnCredentialCreationResponse"
          }
        }
      },
      "PasskeyAuthenticationRequest": {
        "type": "object",
        "required": [
          "email",
          "response"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the user"
          },
          "response": {
            "$ref": "#/components/schemas/WebAuthnCredentialAssertionResponse"
          }
        }
      },
      "WebAuthnRegistrationOptions": {
        "type": "object",
        "description": "WebAuthn PublicKeyCredentialCreationOptions",
        "properties": {
          "challenge": {
            "type": "string",
            "description": "Base64url encoded challenge"
          },
          "rp": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "id": {
                "type": "string"
              }
            }
          },
          "user": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "displayName": {
                "type": "string"
              }
            }
          },
          "pubKeyCredParams": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "alg": {
                  "type": "integer"
                }
              }
            }
          },
          "timeout": {
            "type": "integer"
          },
          "authenticatorSelection": {
            "type": "object",
            "properties": {
              "authenticatorAttachment": {
                "type": "string"
              },
              "residentKey": {
                "type": "string"
              },
              "userVerification": {
                "type": "string"
              }
            }
          },
          "attestation": {
            "type": "string"
          }
        }
      },
      "WebAuthnAuthenticationOptions": {
        "type": "object",
        "description": "WebAuthn PublicKeyCredentialRequestOptions",
        "properties": {
          "challenge": {
            "type": "string",
            "description": "Base64url encoded challenge"
          },
          "timeout": {
            "type": "integer"
          },
          "rpId": {
            "type": "string"
          },
          "allowCredentials": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "transports": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "userVerification": {
            "type": "string"
          }
        }
      },
      "WebAuthnCredentialCreationResponse": {
        "type": "object",
        "description": "WebAuthn credential creation response from authenticator",
        "properties": {
          "id": {
            "type": "string"
          },
          "rawId": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "response": {
            "type": "object",
            "properties": {
              "attestationObject": {
                "type": "string"
              },
              "clientDataJSON": {
                "type": "string"
              }
            }
          }
        }
      },
      "WebAuthnCredentialAssertionResponse": {
        "type": "object",
        "description": "WebAuthn credential assertion response from authenticator",
        "properties": {
          "id": {
            "type": "string"
          },
          "rawId": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "response": {
            "type": "object",
            "properties": {
              "authenticatorData": {
                "type": "string"
              },
              "clientDataJSON": {
                "type": "string"
              },
              "signature": {
                "type": "string"
              },
              "userHandle": {
                "type": "string"
              }
            }
          }
        }
      },
      "RegisterAPIKeyRequest": {
        "type": "object",
        "required": [
          "label",
          "public_key",
          "fingerprint"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "Human-readable label for the API key",
            "minLength": 1,
            "maxLength": 255
          },
          "public_key": {
            "type": "object",
            "description": "EC P-256 public key in JWK format",
            "required": [
              "kty",
              "crv",
              "x",
              "y"
            ],
            "properties": {
              "kty": {
                "type": "string",
                "enum": [
                  "EC"
                ]
              },
              "crv": {
                "type": "string",
                "enum": [
                  "P-256"
                ]
              },
              "x": {
                "type": "string",
                "description": "Base64url-encoded x coordinate"
              },
              "y": {
                "type": "string",
                "description": "Base64url-encoded y coordinate"
              }
            }
          },
          "fingerprint": {
            "type": "string",
            "description": "SHA-256 hash of the canonical JWK representation",
            "minLength": 1,
            "maxLength": 64
          }
        }
      },
      "RegisterAPIKeyResponse": {
        "type": "object",
        "required": [
          "key_id",
          "label",
          "fingerprint",
          "created_at"
        ],
        "properties": {
          "key_id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the registered API key"
          },
          "label": {
            "type": "string",
            "description": "Human-readable label"
          },
          "fingerprint": {
            "type": "string",
            "description": "Key fingerprint"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the key was registered"
          }
        }
      },
      "APIKeySummary": {
        "type": "object",
        "required": [
          "key_id",
          "label",
          "fingerprint",
          "key_algorithm",
          "scopes",
          "created_at"
        ],
        "properties": {
          "key_id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "fingerprint": {
            "type": "string"
          },
          "key_algorithm": {
            "type": "string",
            "example": "ES256"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "PasskeySummary": {
        "type": "object",
        "required": [
          "id",
          "label",
          "device_type",
          "backup_eligible",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "device_type": {
            "type": "string"
          },
          "backup_eligible": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "UpdateLabelRequest": {
        "type": "object",
        "required": [
          "label"
        ],
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "The new label for the resource"
          }
        }
      },
      "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}`"
          }
        }
      },
      "SignInWithAppleRequest": {
        "type": "object",
        "required": [
          "authorization_code"
        ],
        "properties": {
          "authorization_code": {
            "type": "string",
            "description": "Authorization code from Apple"
          }
        }
      },
      "AccountLinkingRequiredResponse": {
        "type": "object",
        "required": [
          "requires_linking",
          "email",
          "message",
          "action"
        ],
        "properties": {
          "requires_linking": {
            "type": "boolean",
            "description": "Indicates account linking is required",
            "example": true
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address that requires linking"
          },
          "message": {
            "type": "string",
            "description": "Human-readable message explaining the situation"
          },
          "action": {
            "type": "string",
            "description": "Recommended action to take",
            "example": "contact_support_for_linking"
          }
        }
      },
      "DisconnectAppleResponse": {
        "type": "object",
        "required": [
          "status",
          "message"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "Operation status",
            "example": "success"
          },
          "message": {
            "type": "string",
            "description": "Human-readable success message",
            "example": "Apple account disconnected successfully"
          }
        }
      },
      "AppleWebhookRequest": {
        "type": "object",
        "required": [
          "payload"
        ],
        "properties": {
          "payload": {
            "type": "string",
            "description": "JWT payload from Apple containing the webhook event"
          }
        }
      },
      "WebhookResponse": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "Processing status",
            "example": "success"
          },
          "event_type": {
            "type": "string",
            "description": "Type of webhook event processed"
          },
          "message": {
            "type": "string",
            "description": "Human-readable status message"
          }
        }
      }
    }
  }
}