openapi: 3.0.3
info:
  title: True Markets Account API
  description: |
    Account API for the True Markets platform — deposit addresses, wire instructions

    ## Base URLs

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

    > UAT is the testing environment. It may require VPN/allowlist access for some integrations — contact [support@truemarkets.co](mailto:support@truemarkets.co) if requests time out from a public network.

    ## Authentication

    All endpoints require a JWT access token in the `Authorization: Bearer <token>` header.

    ### Getting credentials

    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 /v1/auth/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 the Account API** with `Authorization: Bearer <access_token>`.
    5. **Refresh** expired access tokens via `POST /v1/auth/token/refresh` with the `refresh_token` — no re-signing required.

    ### Quick start

    ```bash
    # Mint a JWT
    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>"}'

    # List your crypto deposit addresses
    curl https://api.truemarkets.co/v1/account/deposits/addresses \
      -H "Authorization: Bearer <ACCESS_TOKEN>"
    ```

    ## Support
    - 📧 [support@truemarkets.co](mailto:support@truemarkets.co)
  version: "1.17.0"
servers:
  - url: https://api.truemarkets.co/v1/account
    description: Production
  - url: https://api.uat.truemarkets.co/v1/account
    description: UAT (sandbox)
tags:
  - name: Deposits
    description: Crypto deposit addresses and fiat wire instructions for funding an account.
  - name: Agreements
    description: User acceptance of third-party and platform agreements.
  - name: Organizations
    description: The users an organization trades for. Each user is created with wallets and addressed on trading routes through `TM-On-Behalf-Of`.
  - name: Change Log
    description: |
      
      | Version | Date       | Notes                                                                                                                                     |
      |---------|------------|-------------------------------------------------------------------------------------------------------------------------------------------|
      | v1.17.0 | 2026-09-28 | Grouped `POST`/`GET /organizations/{organization_id}/users` and `GET /organizations/{organization_id}/users/{user_id}` under a public `Organizations` tag. |
      | v1.16.1 | 2026-09-28 | KYC and onboarding reference operations, `POST /paxos/setup` and `POST /me/profile/ensure` marked internal, with the `KYC`, `Account` and `Reference` tags. |
      | v1.16.0 | 2026-08-24 | `POST /kyc/prefill/complete` executes an approved identity-link request, returning `200` with the shared identity instead of `409`.       |
      | v1.15.0 | 2026-08-12 | `POST /kyc/prefill/complete` is dual-mode: without `session_id` it performs session-less manual identity entry (institutional users only, feature-gated; `ssn` and `phone_number` must be absent, `address.country_code` required). Returns `403` when the manual mode is not permitted. |
      | v1.14.0 | 2026-07-31 | Added `POST /agreements` for recording agreement acceptance (`coinbase`, `paxos`, `truemarkets`). Deprecated `PATCH /kyc/terms-conditions`. `POST /paxos/setup` now returns `412` when CDD has not been submitted. |
      | v1.13.0 | 2026-07-23 | `POST /kyc/prefill/start` and `POST /kyc/prefill/complete` return `409` for legacy institutional users, who must not enter retail KYC.     |
      | v1.12.0 | 2026-07-21 | `POST /kyc/prefill/complete` can return `202` when a cross-account SSN collision opens an identity-link request.                          |
      | v1.11.2 | 2026-07-20 | Account creation and funding endpoints made public and grouped under `KYC`, `Account`, `Deposits`, and `Reference`.                       |
      | v1.11.1 | 2026-07-10 | Document `org` scope reads and writes are org-wide for any member, not org-admins only.                                                   |
      | v1.11.0 | 2026-07-09 | Added KYB document uploads (multipart start/complete, status, identity linking, `GET /me/documents`) and organization invites.            |
      | v1.10.0 | 2026-07-08 | Added organization KYB endpoints — profile, business operations, regulatory sections, members, and `POST /organizations/{id}/authorize` — plus personal identity and `/me` write endpoints. |
      | v1.9.3  | 2026-06-29 | Deposit read endpoints grouped under the `Deposits` tag.                                                                                  |
      | v1.9.2  | 2026-06-24 | Documented production and UAT base URLs and the JWT auth flow. `POST /deposits/addresses` and `POST /deposits/wire-instructions` marked internal. |
      | v1.9.1  | 2026-06-23 | Published to the public docs site with only the deposit endpoints exposed; KYC, referral, organization, and Paxos operations marked internal. |
      | v1.9.0  | 2026-06-01 | Per-recipient referral rewards gained `payment_status`, distinguishing a queued reward from a paid one.                                   |
      | v1.8.0  | 2026-05-26 | Added organization endpoints: `POST /organizations`, `GET`/`PATCH /organizations/{id}`, and `GET /me/organization`.                       |
      | v1.7.0  | 2026-05-14 | Added `POST /me/signup-source`.                                                                                                           |
      | v1.6.0  | 2026-05-11 | Referral status gained `payment_status` (`pending`, `queued`, `paid`, `failed`, `voided`). `reward_queued` retained for back-compat.      |
      | v1.5.1  | 2026-05-09 | Spec marked internal; not published to the public docs site.                                                                              |
      | v1.5.0  | 2026-05-04 | Added `GET` and `POST /deposits/wire-instructions`. Deprecated `GET`/`POST /paxos/fiat-deposit-instructions`; the `POST` can return `409`. |
      | v1.4.0  | 2026-04-27 | Added `GET` and `POST /deposits/addresses` for crypto deposit addresses, with cursor pagination.                                          |
      | v1.3.0  | 2026-03-11 | Added `POST /paxos/sandbox/approve` for the sandbox KYC flow.                                                                             |
      | v1.2.0  | 2026-03-04 | Prove prefill accepts a flow type, supporting the desktop KYC flow.                                                                       |
      | v1.1.2  | 2026-02-13 | **Breaking:** removed `GET /referral/stats`; its fields moved into the rewards response. `GET /referral/code` no longer requires KYC and no longer returns `403`. |
      | v1.1.1  | 2026-02-10 | **Breaking:** `reward_distributed` renamed to `reward_queued` on the referral status response.                                            |
      | v1.1.0  | 2026-02-05 | Added `GET` and `POST /paxos/fiat-deposit-instructions`. Paths are now relative to the `/v1/account` base URL.                            |
      | v1.0.0  | 2026-02-03 | Initial release. KYC inquiries and status, Prove prefill, CDD, Paxos account setup, referrals, and onboarding reference data.             |
paths:
  /deposits/addresses:
    post:
      operationId: createDepositAddress
      tags:
        - Deposits
      summary: Create deposit address
      description: Creates a new crypto deposit address for the specified network
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - network
              properties:
                network:
                  type: string
                  minLength: 1
                  description: The network for which to create a deposit address
      responses:
        "201":
          description: Deposit address created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DepositAddress"
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Deposit address already exists for this network
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      operationId: listDepositAddresses
      summary: List deposit addresses
      description: Returns all deposit addresses for the authenticated user
      tags:
        - Deposits
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Deposit addresses retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListDepositAddressesResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /deposits/wire-instructions:
    post:
      operationId: createWireInstructions
      tags:
        - Deposits
      summary: Create wire instructions
      description: Creates wire deposit instructions for the user's Paxos account. Returns 409 if instructions already exist.
      security:
        - bearerAuth: []
      responses:
        "201":
          description: Wire instructions created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WireInstruction"
        "400":
          description: Bad request (no Paxos account)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Wire instructions already exist
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      operationId: listWireInstructions
      summary: List wire instructions
      description: Returns the user's wire deposit instructions. Returns an empty list if not yet provisioned (no 404).
      tags:
        - Deposits
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Wire instructions retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListWireInstructionsResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /organizations/{organization_id}/users:
    post:
      x-internal: false
      operationId: createOrganizationUser
      tags:
        - Organizations
      summary: Create a user your organization trades for
      description: |
        Creates the user your organization trades for under the reference you name them by, with
        their wallets, or returns the existing one. Re-sending the same request is safe: a 503 means
        the wallet step did not finish, and repeating the request completes it. Once your organization
        reaches its user limit a new reference is refused, while re-sending an existing one still works.
        Use the returned `user_id` as the `TM-On-Behalf-Of` value on trading routes.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/OrganizationIdParam"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrganizationUserRequest"
      responses:
        "200":
          description: User already existed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizationUser"
        "201":
          description: User created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizationUser"
        "400":
          description: Malformed organization id, external reference or signer public key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: The reference names a member of the organization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: The organization has reached its user limit; contact support to raise it
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          description: Service unavailable; retry the same request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      x-internal: false
      operationId: listOrganizationUsers
      tags:
        - Organizations
      summary: List the users your organization trades for
      description: |
        Returns your organization's users, newest first. Page with the opaque `cursor` from
        `pagination.next_cursor`. Supply `external_ref_id` instead to look up a single user by the
        reference you created them with; a reference matching nothing is an empty page.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/OrganizationIdParam"
        - name: external_ref_id
          in: query
          required: false
          description: Narrow the page to the user created under this reference.
          schema:
            $ref: "#/components/schemas/ExternalRefId"
        - name: cursor
          in: query
          required: false
          description: Opaque cursor from a prior page.
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Maximum users to return; above 100 is clamped to 100 and echoed back.
          schema:
            type: integer
            minimum: 1
            default: 50
      responses:
        "200":
          description: A page of users
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizationUserPage"
        "400":
          description: Malformed organization id, external reference, cursor or limit
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          description: Service unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /organizations/{organization_id}/users/{user_id}:
    get:
      x-internal: false
      operationId: getOrganizationUser
      tags:
        - Organizations
      summary: Get a user your organization trades for
      description: Returns one of your organization's users, with the wallets held for them.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/OrganizationIdParam"
        - name: user_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The user your organization trades for
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizationUser"
        "400":
          description: Malformed organization id or user id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Your organization does not trade for this user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          description: Service unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT access token obtained from authentication endpoints
  parameters:
    OrganizationIdParam:
      name: organization_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    ExternalRefId:
      type: string
      pattern: "^[A-Za-z0-9_-]{1,64}$"
      description: >-
        The reference the client names a served user by, unique within the organization. Compared exactly as sent, with no case or format normalisation, so send the same string every time (a UUID in one consistent case).
    CreateOrganizationUserRequest:
      type: object
      description: Creates or finds a user the organization trades for
      required:
        - external_ref_id
        - signer_public_key
      properties:
        external_ref_id:
          $ref: "#/components/schemas/ExternalRefId"
        signer_public_key:
          type: string
          description: Turnkey signer key, a 33-byte compressed P-256 point as 66 hex characters
    OrganizationUser:
      type: object
      description: A user the organization trades for
      required:
        - user_id
        - external_ref_id
        - created_at
        - wallets
      properties:
        user_id:
          type: string
          format: uuid
        external_ref_id:
          $ref: "#/components/schemas/ExternalRefId"
        created_at:
          type: string
          format: date-time
        wallets:
          type: array
          description: Empty only while the wallet step has not completed; re-send the create to finish it.
          items:
            $ref: "#/components/schemas/OrganizationUserWallet"
    OrganizationUserPage:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/OrganizationUser"
        pagination:
          $ref: "#/components/schemas/Pagination"
    OrganizationUserWallet:
      type: object
      required:
        - address
        - chain_family
      properties:
        address:
          type: string
        chain_family:
          type: string
          enum: [evm, solana]
          description: >-
            The family of chains this address serves. An `evm` address is the same on every EVM chain (base, arbitrum, robinhood); a `solana` address serves solana.
    WireInstruction:
      type: object
      properties:
        id:
          type: string
        network_type:
          type: string
        memo_id:
          type: string
        account_number:
          type: string
        routing_number:
          type: string
        bank_name:
          type: string
        bank_address:
          type: string
        account_owner_name:
          type: string
        account_owner_address:
          type: string
        created_at:
          type: string
          format: date-time
      required:
        - id
        - network_type
        - memo_id
        - account_number
        - routing_number
        - bank_name
        - bank_address
        - account_owner_name
        - account_owner_address
        - created_at
    ListWireInstructionsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WireInstruction"
        pagination:
          $ref: "#/components/schemas/Pagination"
      required:
        - data
        - pagination
    # Deposit Schemas
    DepositAddress:
      type: object
      description: A crypto deposit address for a specific network
      required:
        - id
        - network
        - address
        - compatible_networks
        - created_at
      properties:
        id:
          type: string
          description: Deposit address identifier
        network:
          type: string
          description: The network this address belongs to
        address:
          type: string
          description: The deposit address
        compatible_networks:
          type: array
          items:
            type: string
          description: Other networks compatible with this address
        created_at:
          type: string
          format: date-time
          description: Address creation timestamp
    Pagination:
      type: object
      description: Pagination metadata
      required:
        - next_cursor
        - limit
      properties:
        next_cursor:
          type: string
          nullable: true
          description: Cursor for the next page of results, null if no more pages
        limit:
          type: integer
          description: Number of results returned in this page
    ListDepositAddressesResponse:
      type: object
      description: Paginated list of deposit addresses
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/DepositAddress"
          description: List of deposit addresses
        pagination:
          $ref: "#/components/schemas/Pagination"

    # Common 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, which is shared across services rather than owned by this one. Extensible: clients MUST tolerate any value and fall back to type-based handling. Omitted when no code applies, which is every error this service currently emits.
        message:
          type: string
          description: Human-readable error message
        metadata:
          type: object
          description: >-
            Machine-readable context scoped to `code`; values are always strings. Which keys appear is determined by `code`.
          additionalProperties:
            type: string
        field_violations:
          type: array
          description: >-
            Per-field rejections, present on `invalid_argument` when the request failed field validation.
          items:
            type: object
            required:
              - field
              - message
            properties:
              field:
                type: string
                description: Dotted path to the offending field, as the client sent it
              message:
                type: string
                description: Human-readable reason the field was rejected
        request_id:
          type: string
          description: Per-request correlation ID, empty when unavailable
