{
  "openapi": "3.1.0",
  "info": {
    "title": "Etherfuse FX API",
    "description": "Partner-facing ramp API, version 2: payment accounts, quotes, orders and webhooks.",
    "license": {
      "name": "Proprietary"
    },
    "version": "2.0.0"
  },
  "servers": [
    {
      "url": "https://api.sand.etherfuse.com",
      "description": "Sandbox"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Payment Accounts",
      "description": "Register the bank accounts and wallets that money moves from and to, and read their readiness per rail."
    },
    {
      "name": "Quotes",
      "description": "Price a transfer between two registered payment accounts."
    },
    {
      "name": "Orders",
      "description": "Create orders from quotes and track them."
    },
    {
      "name": "Webhook Subscriptions",
      "description": "Subscribe to webhook events."
    },
    {
      "name": "Webhook Events",
      "description": "Payloads Etherfuse sends to your webhook subscriptions."
    },
    {
      "name": "Sandbox",
      "description": "Endpoints that work only in sandbox, for testing."
    },
    {"name": "Assets", "description": "The markets and tokens a quote can use, and the balances of a wallet."},
    {"name": "Authentication", "description": "Partner JWT authentication: exchange a JWT for an access token, or launch a user into the app."},
    {"name": "KYC", "description": "Submit a customer's verification data, documents and questionnaire answers, import a verification they already passed elsewhere, and check where their KYC stands."},
    {"name": "KYB", "description": "Check where a business organization's KYB stands."},
    {"name": "Lookup", "description": "Public reference data: exchange rates, stablebonds, bond costs, country codes."},
    {"name": "Organizations", "description": "Your organization, your default partner fee, and the customer organizations you create."},
    {"name": "Members", "description": "The people who belong to a customer business organization and complete its KYB."},
    {"name": "Statements", "description": "Monthly revenue share statements for closed months, as a list or as a CSV or PDF download."}
  ],
  "paths": {
    "/organization/{organizationId}/payment_account": {
      "post": {
        "tags": ["Payment Accounts"],
        "summary": "Register payment account",
        "operationId": "v2CreatePaymentAccount",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "description": "Registers a bank account or wallet for the organization in the route and returns it with its per-rail readiness.\n\nState where the account lives in one of two ways:\n- **Bank account:** set `kind` to `bank`, send `country` and `currency` (for example `MX` and `MXN`), and describe the account in `accountDetails` (for example a `clabe`).\n- **Wallet:** set `kind` to `wallet` and send `accountDetails` of kind `address` with its `chain`. Do not send `country` or `currency` for a wallet. A wallet requires no `registrationInfo`, and we ignore any you send.\n\nRegistering does not prove that the customer owns the account.\n\n**Registration info.** A bank account route requires holder fields such as `accountHolderName` and `taxId`. We fill each one from the organization's verified identity when we hold it. When a required field is still missing, the request fails with `422` `registration-info-required`, and the problem lists every required field in `required` and the ones still needed in `missing`, as JSON Pointers into the request (for example `#/registrationInfo/taxId`). Send the missing fields in `registrationInfo` and retry. A supplied `taxId` must match the verified one (`422` `holder-tax-id-mismatch`).\n\nYou can send any registration field, including ones this route does not require; we check each value's format and ignore the fields the route does not use. A name that is not a registration field returns `400` `validation-error`.\n\n**Readiness.** A new account starts `pending`, and so does each rail direction. We register it with the payment provider, then move each direction to `ready` or `rejected`. Subscribe to `paymentAccountUpdated`, or read the account again, and quote a direction once it is `ready`.\n\n**Registering an account again.** If the organization already holds the account, you get it back with `200`. When a direction was `rejected`, we register the account with the provider again using the `registrationInfo` you send, and those directions go back to `pending`. Otherwise we change nothing. If another organization holds it, the request fails with `409` `payment-account-unavailable`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreatePaymentAccountRequest" },
              "examples": {
                "bank": {
                  "summary": "Mexican bank account on SPEI",
                  "value": {
                    "kind": "bank",
                    "label": "Operating account",
                    "country": "MX",
                    "currency": "MXN",
                    "accountDetails": { "kind": "clabe", "clabe": "012180000000000002" },
                    "registrationInfo": { "accountHolderName": "Ejemplo Comercial SA de CV", "taxId": "XAXX010101000" }
                  }
                },
                "wallet": {
                  "summary": "Stellar wallet",
                  "value": {
                    "kind": "wallet",
                    "label": "Treasury wallet",
                    "accountDetails": {
                      "kind": "address",
                      "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
                      "chain": "stellar"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The organization already holds this account. Returns it. A `rejected` direction is registered again and returns as `pending`.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentAccount" }
              }
            }
          },
          "201": {
            "description": "The registered account: a new registration, or an account the organization removed and registered again.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentAccount" }
              }
            }
          },
          "400": {
            "description": "`invalid-request`: the body is malformed, has an unknown enum value, a blank `label`, an invalid CLABE, PIX key or wallet address, an unknown `chain`, or an incomplete or conflicting location. `validation-error`: `accountDetails` is missing a field its kind requires or has a field it does not allow, or `registrationInfo` has a field that is not a registration field or has a malformed value. See `errors` for the pointers.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": {
            "description": "`payment-account-unavailable`: another organization holds this account. `payment-account-disabled`: the account is disabled and cannot be registered. `conflict`: the organization holds this account in a different market.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "422": {
            "description": "`registration-info-required`: required registration fields are missing; see `required` and `missing`. `holder-tax-id-mismatch`: `registrationInfo.taxId` does not match the verified tax ID. `unsupported`: this kind of account, or this identifier in this location, cannot be registered.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/payment_account/requirements": {
      "get": {
        "tags": ["Payment Accounts"],
        "summary": "Get registration requirements",
        "operationId": "v2GetPaymentAccountRequirements",
        "description": "Returns what you need to send to register an account of this kind for the organization in the route: the `accountDetails` fields for the identifier kind, and the `registrationInfo` fields the route requires. `registrationInfo.missing` lists the required fields we hold no verified value for, so you ask the customer only for those. Pass the same `kind`, `country`, `currency` and `accountDetails.kind` you would send to register.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          {
            "name": "kind",
            "in": "query",
            "required": true,
            "schema": { "$ref": "#/components/schemas/PaymentAccountKind" }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Two-letter country code. Omit for a wallet.",
            "schema": { "type": "string" }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Three-letter currency code. Omit for a wallet.",
            "schema": { "type": "string" }
          },
          {
            "name": "accountDetailsKind",
            "in": "query",
            "required": true,
            "description": "The `accountDetails.kind` you would register, for example `clabe`.",
            "schema": { "$ref": "#/components/schemas/AccountDetailsKind" }
          }
        ],
        "responses": {
          "200": {
            "description": "The fields to send.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentAccountRequirements" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "422": {
            "description": "`unsupported`: this kind of account, or this identifier in this location, cannot be registered. Registration returns the same error for the same request.",
            "content": {
              "application/problem+json": {
                "schema": { "$ref": "#/components/schemas/Problem" }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/payment_account/{id}": {
      "get": {
        "tags": ["Payment Accounts"],
        "summary": "Get payment account",
        "operationId": "v2GetPaymentAccount",
        "description": "Returns one of the organization's payment accounts with its per-rail readiness. An account the organization removed returns `404`.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/PaymentAccountId" }
        ],
        "responses": {
          "200": {
            "description": "The payment account.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentAccount" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "patch": {
        "tags": ["Payment Accounts"],
        "summary": "Update payment account",
        "operationId": "v2UpdatePaymentAccount",
        "description": "Changes the label the organization gave the account. Nothing else on an account changes through this endpoint.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/PaymentAccountId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UpdatePaymentAccountRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated payment account.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentAccount" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/payment_accounts": {
      "get": {
        "tags": ["Payment Accounts"],
        "summary": "List payment accounts",
        "operationId": "v2ListPaymentAccounts",
        "description": "Lists the organization's payment accounts, oldest first. Accounts the organization removed are not listed.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/PageSize" },
          { "$ref": "#/components/parameters/PageNumber" }
        ],
        "responses": {
          "200": {
            "description": "One page of payment accounts.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentAccountPage" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/quote": {
      "post": {
        "tags": ["Quotes"],
        "summary": "Create quote",
        "operationId": "v2CreateQuote",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "description": "Prices a transfer from one of the organization's payment accounts to another. Both accounts must belong to the organization in the route. Pass the quote's `quoteId` to `POST /organization/{organizationId}/order` with the same `organizationId` before `expiresAt`; a quote is valid for 2 minutes.\n\nOne side must be a wallet. Supported shapes: fiat to crypto (onramp), crypto to fiat (offramp), a same-chain swap between two wallets, and a cross-chain transfer of the same token between wallets on two supported chains. Fiat to fiat is not supported.\n\n**Rails.** Each side moves on one rail. Omit `sourceRail` or `destinationRail` and we use the account's default rail for that direction (the rail with `isDefault: true` in the account's `rails`). When two of an account's rails tie, name the rail. The account's status for that direction on that rail must be `ready`, or the quote fails with `409` `payment-account-not-ready`.\n\n**Assets.** Send an `asset` from [`GET /supported_assets`](/v2/api-reference/assets/list-supported-assets): a currency code such as `MXN` for a fiat side, a token's `asset` for a crypto side. Filter that list by `paymentAccountId` to see what each account can use.\n\n**Approval.** The organization must be approved, or the quote fails with `409` `organization-not-approved`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateQuoteRequest" },
              "example": {
                "source": { "type": "stored", "id": "3f9a2c1e-5b7d-4e8f-9a01-2b3c4d5e6f70" },
                "destination": { "type": "stored", "id": "8c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e" },
                "sourceAmount": "1000",
                "sourceAsset": "MXN",
                "destinationAsset": "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Quote" }
              }
            }
          },
          "400": {
            "description": "`invalid-request`: the body is malformed; `sourceAmount` is not a positive decimal or is too small to cover the fees; a side is `inline` or is not registered to the organization; the pair is fiat to fiat, two different tokens on different chains, or uses an unknown or unsupported asset; the named rail cannot carry that direction; or two rails tie and none was named. `amount-out-of-range`: the amount is below the minimum or above the maximum of a side's rail. `sandbox-limit-exceeded`: the sandbox limit for onramps was exceeded.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": {
            "description": "`organization-not-approved`: the organization is not approved. `payment-account-disabled`: a side's account is disabled. `payment-account-not-ready`: a side's account is not `ready` on the chosen rail in that direction, is not registered on the named rail, or has no rail that can carry that direction.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "422": {
            "description": "`quote-unavailable`: we cannot price this route right now. Retry later. `unsupported`: the route is not supported, for example a principal token on a chain that does not carry it.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/order": {
      "post": {
        "tags": ["Orders"],
        "summary": "Create order",
        "operationId": "v2CreateOrder",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "description": "Creates an order from a quote and returns the full order. Use the `organizationId` the quote was created with. The order's `id` is the `quoteId`.\n\nOne quote makes one order. Sending the same `quoteId` again returns the existing order with `200`, even after the quote has expired, so a retry never creates a second order. If two requests with the same `quoteId` arrive at the same time, one gets `201` and the other gets `409` `conflict`; read the order to get it.\n\n`fundingInstructions.reference` is set when the order is created. `fundingInstructions.destination` is absent until the funding details are available. Subscribe to `orderUpdated` to receive the order when they arrive, or read the order again.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateOrderRequest" },
              "example": {
                "quoteId": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
                "memo": "Invoice 1042"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An order already exists for this quote. Returns that order.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Order" }
              }
            }
          },
          "201": {
            "description": "The created order.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Order" }
              }
            }
          },
          "400": {
            "description": "`invalid-request`: the body is malformed, or `memo` is longer than 255 characters. `quote-expired`: the quote has expired or does not exist. Create a new quote.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": {
            "description": "`conflict`: another request created an order for this quote at the same time. Read the order with `GET /organization/{organizationId}/order/{id}`, using the `quoteId` as the id.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/order/{id}": {
      "get": {
        "tags": ["Orders"],
        "summary": "Get order",
        "operationId": "v2GetOrder",
        "description": "Returns one of the organization's orders. An order that belongs to a different organization returns `404`.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/OrderId" }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Order" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/orders": {
      "get": {
        "tags": ["Orders"],
        "summary": "List orders",
        "operationId": "v2ListOrders",
        "description": "Lists orders belonging to the API key's organization and all its customer organizations, newest first. Each order's `organizationId` is the organization it belongs to. Pass `organizationId` to list one organization's orders; an organization your API key cannot act for returns `404` `not-found`.",
        "parameters": [
          {
            "name": "organizationId",
            "in": "query",
            "required": false,
            "description": "Only list this organization's orders. It must be your API key's organization or one of its customer organizations; any other id returns `404` `not-found`.",
            "schema": { "type": "string", "format": "uuid" }
          },
          { "$ref": "#/components/parameters/PageSize" },
          { "$ref": "#/components/parameters/PageNumber" }
        ],
        "responses": {
          "200": {
            "description": "One page of orders.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OrderPage" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/order/{id}/cancel": {
      "post": {
        "tags": ["Orders"],
        "summary": "Cancel order",
        "operationId": "v2CancelOrder",
        "description": "Requests cancellation of an order and returns the order. No request body. An order that can no longer be canceled returns `409` `order-not-cancelable`.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/OrderId" }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Order" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "`order-not-cancelable`: the order can no longer be canceled, for example it is already `completed`, `failed`, `canceled` or `refunded`.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/order/{id}/funding_instructions/refresh": {
      "post": {
        "tags": ["Orders"],
        "summary": "Refresh funding instructions",
        "operationId": "v2RefreshFundingInstructions",
        "description": "Requests new funding instructions for an order and returns the order as it is. No request body. When the order is `created`, before its funding deadline, and either its funding destination has expired or its instructions carry a `preparedTransaction`, we get new instructions from the payment provider. Refresh a prepared transaction after its `refreshAfter`, or whenever submitting it fails because the wallet's sequence number or nonce moved. On Stellar and EVM chains, a refresh after the old transaction landed changes nothing. New instructions arrive in an `orderUpdated` webhook and on GET. Otherwise nothing changes. Calling it again before the new instructions arrive does not request them twice.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/OrderId" }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Order" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/order/{id}/claim": {
      "post": {
        "tags": ["Orders"],
        "summary": "Prepare claim",
        "operationId": "v2PrepareClaim",
        "description": "Prepares the claim on a `claimable` order and returns what the customer does next. This call does not claim the funds; the customer finishes the claim with what we return. We build it on every call and never store it, because it goes stale. No request body.\n\nThe wallet's custody decides the `kind`:\n- `stellarTransaction`: a self-held wallet signs and submits `xdr`.\n- `authorization`: an embedded wallet's holder approves the order `authorization` named by `authorizationId`.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/OrderId" }
        ],
        "responses": {
          "200": {
            "description": "What the customer does next to claim.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ClaimResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "`order-not-claimable`: the order is not `claimable`.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/order/{id}/authorization": {
      "post": {
        "tags": ["Orders"],
        "summary": "Submit authorization",
        "operationId": "v2SubmitAuthorization",
        "description": "Approves the order's pending `authorization` and returns the order. Sign the UTF-8 bytes of `authorization.method.message` with the P-256 key registered as the embedded wallet's `signerPublicKey`, using ECDSA with SHA-256, and send the signature hex-encoded, as DER or as the 128-character `r||s` form WebCrypto produces.\n\nWe verify the signature, then move the funds (`reason` `funding`) or claim them (`reason` `claim`). `authorization` is null on the returned order, and `orderUpdated` follows as the order progresses.\n\nAn authorization expires at `expiresAt`. We then replace it with a new one with a new `id`. Read the order again and sign the new `message`.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/OrderId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubmitAuthorizationRequest" },
              "example": { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "signature": "3045022100..." }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Order" } } }
          },
          "400": {
            "description": "`invalid-request`: the body is malformed. `invalid-signature`: the signature is not valid hex or does not verify over `message` with the wallet's signer key.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "`authorization-expired`: `id` is not the order's current authorization, or it expired. Read the order and sign the new one. `authorization-not-required`: the order has no pending authorization.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/order/{id}/fiat_received": {
      "post": {
        "tags": ["Sandbox"],
        "summary": "Simulate fiat received",
        "operationId": "v2SimulateFiatReceived",
        "description": "Sandbox only. Returns `403` `forbidden` in production.\n\nRecords a simulated fiat deposit for an order whose source is a bank account on `spei` or `pix`, so you can test an onramp without sending money. The order then continues as it would after a real deposit. Returns `200` with an empty body.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/OrderId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SimulateFiatReceivedRequest" },
              "example": { "amount": "1000" }
            }
          }
        },
        "responses": {
          "200": { "description": "The simulated deposit was recorded. Empty body." },
          "400": {
            "description": "`invalid-request`: the body is malformed, or `amount` is not a positive decimal.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": {
            "description": "`forbidden`: called outside sandbox.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "`conflict`: the simulated deposit was rejected.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "422": {
            "description": "`unsupported`: the order's source does not receive fiat, or its provider does not allow simulation.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/webhook_subscription": {
      "post": {
        "tags": ["Webhook Subscriptions"],
        "summary": "Create webhook subscription",
        "operationId": "v2CreateWebhookSubscription",
        "description": "Subscribes a URL to one v2 event. The response includes the signing `secret`. We return it only here, so store it. See [Verifying Webhooks](/guides/verifying-webhooks).\n\nIn production the URL must use `https`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateWebhookSubscriptionRequest" },
              "example": { "url": "https://example.com/etherfuse/webhooks", "eventType": "orderUpdated" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created subscription, with its secret.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CreatedWebhookSubscription" }
              }
            }
          },
          "400": {
            "description": "`invalid-request`: the body is malformed, has an unknown `eventType`, the `url` is not a valid URL, or it does not use `https` in production.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": {
            "description": "`conflict`: a subscription for this URL and event already exists.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/webhook_subscription/{id}": {
      "get": {
        "tags": ["Webhook Subscriptions"],
        "summary": "Get webhook subscription",
        "operationId": "v2GetWebhookSubscription",
        "description": "Returns one of your organization's webhook subscriptions. The secret is not included.",
        "parameters": [{ "$ref": "#/components/parameters/WebhookSubscriptionId" }],
        "responses": {
          "200": {
            "description": "The subscription.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WebhookSubscription" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "delete": {
        "tags": ["Webhook Subscriptions"],
        "summary": "Delete webhook subscription",
        "operationId": "v2DeleteWebhookSubscription",
        "description": "Deletes one of your organization's webhook subscriptions. Deliveries to it stop.",
        "parameters": [{ "$ref": "#/components/parameters/WebhookSubscriptionId" }],
        "responses": {
          "204": { "description": "Deleted. Empty body." },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/webhook_subscriptions": {
      "get": {
        "tags": ["Webhook Subscriptions"],
        "summary": "List webhook subscriptions",
        "operationId": "v2ListWebhookSubscriptions",
        "description": "Lists all of your organization's webhook subscriptions, oldest first. The list is not paged. Secrets are not included.",
        "responses": {
          "200": {
            "description": "Every subscription.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WebhookSubscriptionList" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/auth/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Exchange a partner JWT",
        "description": "OAuth 2.0 token endpoint ([RFC 6749 §3.2](https://www.rfc-editor.org/rfc/rfc6749#section-3.2)). Exchanges a partner-signed JWT for an Etherfuse access/refresh token pair, or refreshes an existing session.\n\nCall this from your backend to provision a user ahead of a launch, or to prefetch a `refresh_token` that speeds up the launch redirect. Your backend signs a JWT with the key behind your registered JWKS and posts it here. To drop a user straight into the app in a browser, use [POST /auth/launch](/v2/api-reference/authentication/launch-a-user-into-the-app) instead.\n\n## Grant types\n\n- `urn:ietf:params:oauth:grant-type:jwt-bearer` ([RFC 7523](https://www.rfc-editor.org/rfc/rfc7523)). Put the partner JWT in `assertion`.\n- `refresh_token` ([RFC 6749 §6](https://www.rfc-editor.org/rfc/rfc6749#section-6)). Put a prior `refresh_token` in `refresh_token`.\n\n## The JWT\n\nSign it with the key behind your registered JWKS (see [Sign a user JWT](/v2/guides/jwt-authentication#sign-a-user-jwt) for full detail). Claims:\n\n- **`iss`**: your registered issuer.\n- **`sub`**: the person signing in; a UUID is _strongly recommended_ (for an individual customer it's the same value as their organization's `id`, and for a business member it's their `memberId`). Must name a person, never a business: a business org id is rejected with `invalid_grant`, and a `sub` can't be reused as a business org id ([POST /organization](/v2/api-reference/organizations/create-organization) returns 409). A non-UUID `sub` still signs in, but can't be addressed through the Ramp API.\n- **`aud`**: the token endpoint URL, `https://api.etherfuse.com/auth/token` (`https://api.sand.etherfuse.com/auth/token` in sandbox).\n- **`scope`**: required. Names the flow the user may complete (see [User Launch Flows](/v2/guides/user-launch-flows#scopes)); an unrecognized scope is rejected with `invalid_scope`.\n- **`jti`** / **`nonce`**: a fresh value, unique per token, for replay protection. You can send it as `jti` (the standard JWT ID) or `nonce` (the standard OIDC claim).\n- **`exp`, `iat`**: required; keep tokens short-lived.\n- **`email`, `name`**: required; populate the user's profile. `picture` is optional.\n\nThis endpoint is unauthenticated: the signed JWT (or refresh token) is the credential. Do **not** send an API key.",
        "operationId": "auth_token",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/AuthTokenRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuthTokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A bearer access token and refresh token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "OAuth error: `invalid_request`, `invalid_grant`, `invalid_scope`, or `unsupported_grant_type` ([RFC 6749 §5.2](https://www.rfc-editor.org/rfc/rfc6749#section-5.2))",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "tags": ["Organizations"],
        "summary": "Get your organization",
        "operationId": "v2GetMe",
        "description": "Returns the organization that owns your API key. Use its `id` as `organizationId` when a call acts for your own organization. A `200` also confirms the key works.",
        "responses": {
          "200": {
            "description": "Your organization.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Me" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "patch": {
        "tags": ["Organizations"],
        "summary": "Set your default partner fee",
        "operationId": "v2UpdateMe",
        "description": "Sets the partner fee, in basis points, that quotes add when the request sends no `partnerFeeBpsOverride`. See [Fees](/v2/fees).",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateMeRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Your organization with the new default.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Me" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organizations": {
      "get": {
        "tags": ["Organizations"],
        "summary": "List customer organizations",
        "operationId": "v2ListOrganizations",
        "description": "Lists the customer organizations you created, newest first. Removed organizations are not listed. Your own organization is not in the list; read it with [`GET /me`](/v2/api-reference/organizations/get-me).",
        "parameters": [
          { "$ref": "#/components/parameters/PageSize" },
          { "$ref": "#/components/parameters/PageNumber" }
        ],
        "responses": {
          "200": {
            "description": "One page of customer organizations.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationPage" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization": {
      "post": {
        "tags": ["Organizations"],
        "summary": "Create customer organization",
        "operationId": "v2CreateOrganization",
        "description": "Creates a customer organization under yours. It starts unapproved; it is approved once its KYC or KYB is. Register its bank accounts and wallets afterwards with [Register payment account](/v2/api-reference/payment-accounts/register-payment-account).\n\nAll fields are optional, so `{}` creates a bare personal organization. If `id` is already in use the request returns `409` `conflict`.\n\n## Account types\n\n- `personal`: an individual who completes KYC. Send `userInfo` so we can email them about their verification.\n- `business`: a company that completes KYB. `userInfo` is rejected. Send `country` to set up KYB at creation, and add the people who complete it with [Add organization member](/v2/api-reference/members/add-organization-member).\n\n`accountType` defaults to `personal` today and will become required. Send it explicitly.\n\n## Signing customers in\n\nFor a **personal** organization whose customer signs in with a JWT (see [JWT User Authentication](/v2/guides/jwt-authentication)), send the JWT `sub` as `id`. A **business** organization has its own id; never reuse a customer's `sub` as one, which returns `409`.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateOrganizationRequest" } } }
        },
        "responses": {
          "201": {
            "description": "The new organization.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Organization" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}": {
      "get": {
        "tags": ["Organizations"],
        "summary": "Get organization",
        "operationId": "v2GetOrganization",
        "description": "Returns your organization or one of your customer organizations.",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "responses": {
          "200": {
            "description": "The organization.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Organization" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "patch": {
        "tags": ["Organizations"],
        "summary": "Rename organization",
        "operationId": "v2UpdateOrganization",
        "description": "Changes the organization's `displayName`. A business organization's legal name does not change.",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateOrganizationRequest" } } }
        },
        "responses": {
          "200": {
            "description": "The renamed organization.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Organization" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/member": {
      "post": {
        "tags": ["Members"],
        "summary": "Add organization member",
        "operationId": "v2AddMember",
        "description": "Adds a person to a customer business organization, by `email` or `memberId`. A `memberId` is the `sub` you sign into the person's JWT. Members sign in with that JWT and complete the organization's KYB.\n\n- **By `email`**: if the email belongs to someone who already signed in through your JWT, they become a member at once. Otherwise we create a pending invite, accepted the first time that email signs in with your `kyb` JWT. Send `emailInviteLink` to have us email them that link, or leave it out and tell them yourself.\n- **By `memberId`**: adds someone by their `sub`, and sends no email. The `sub` exists once that person has exchanged a JWT with [POST /auth/token](/v2/api-reference/authentication/exchange-a-partner-jwt) or been launched. An unknown `sub` returns `404`, and a business organization's id is rejected.\n\nAdding someone who was removed earlier brings them back. Adding a current member returns `409` `conflict`. The response lists the members and invites: someone added at once is under `members`, an invite is under `invited`.\n\nYour own organization's members are managed in the Etherfuse portal, so naming it returns `403` `forbidden`. A personal organization has no members and returns `400` `invalid-request`.",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddMemberRequest" } } }
        },
        "responses": {
          "200": {
            "description": "The organization's members and pending invites after the change.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MembersResponse" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/members": {
      "get": {
        "tags": ["Members"],
        "summary": "List organization members",
        "operationId": "v2ListMembers",
        "description": "Lists a customer business organization's members and pending invites. The organization's own service account is never listed.",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "responses": {
          "200": {
            "description": "The members and pending invites.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MembersResponse" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/member/{memberId}": {
      "delete": {
        "tags": ["Members"],
        "summary": "Remove organization member",
        "operationId": "v2RemoveMember",
        "description": "Removes a member from a customer business organization.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/MemberId" }
        ],
        "responses": {
          "204": { "description": "The member was removed." },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/member_invite/{email}": {
      "delete": {
        "tags": ["Members"],
        "summary": "Revoke organization invite",
        "operationId": "v2RevokeMemberInvite",
        "description": "Cancels a pending invite to a customer business organization, so the link in the invite email stops working.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          {
            "name": "email",
            "in": "path",
            "required": true,
            "description": "The email the invite was sent to.",
            "schema": { "type": "string", "format": "email" }
          }
        ],
        "responses": {
          "204": { "description": "The invite was revoked." },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/member/{memberId}/organizations": {
      "get": {
        "tags": ["Members"],
        "summary": "List a member's organizations",
        "operationId": "v2ListMemberOrganizations",
        "description": "Lists the customer business organizations a person belongs to, by their `memberId` (the JWT `sub`). This is the reverse of [List organization members](/v2/api-reference/members/list-organization-members).\n\nOnly your own customer organizations are searched. A `memberId` that belongs to none of them returns an empty `items`.",
        "parameters": [{ "$ref": "#/components/parameters/MemberId" }],
        "responses": {
          "200": {
            "description": "The organizations the person belongs to.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MemberOrganizationList" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/kyc": {
      "get": {
        "tags": ["KYC"],
        "summary": "Get KYC status",
        "description": "Returns the organization's KYC status. The overall `status` uses the same logic as orders, so `approved` is the only value that lets the customer transact. Submission details such as rejection reasons and timestamps are visible only to the partner that submitted the data.\n\n## The requirements breakdown\n\nPass `?requirements=true` to add a `requirements` array describing, step by step, what this customer's verification still needs. Each entry carries a `type`, a `status`, and a `requiresLaunch` flag, which separates \"I still owe data over the API\" from \"it is time to launch the customer\". It is opt-in because it calls the verification provider; without it the response leaves `requirements` out.\n\nThe breakdown covers personal verification only. Requesting it for a business organization returns `400` `invalid-request`; track a business with [`GET /organization/{organizationId}/kyb`](/v2/api-reference/kyb/get-kyb-status) instead.\n\n`requirements` is left out until the customer has been routed to their country's requirements. After that the list holds the customer's current stage, plus requirements already satisfied. It never lists a later stage's requirements, so it grows as they advance: `proofOfAddress` appears only when the identity document carried no address, and `customerAgreement` appears last. Send only what is listed as `pending` or `actionRequired`; a requirement sent before it is listed is accepted and then refused in the background. Read the list again after every `kycUpdated`; no single response is the full list. See [Requirements unlock in stages](/v2/guides/onboarding#requirements-unlock-in-stages).\n\n## Prefer the webhook\n\nSubscribe to `kycUpdated` to learn that something changed. Read this endpoint on demand, or after that webhook when you need to know what is outstanding. With `?requirements=true` it calls the verification provider every time, so do not poll it tightly. See [Tracking verification status](/v2/guides/onboarding#tracking-verification-status).",
        "operationId": "v2GetKyc",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          {
            "name": "requirements",
            "in": "query",
            "description": "Include the per-requirement `requirements` breakdown. Off by default. Personal organizations only; a business organization returns `400`.",
            "required": false,
            "schema": { "type": "boolean", "default": false }
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's KYC status.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Kyc" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/verification": {
      "post": {
        "tags": ["KYC"],
        "summary": "Submit verification data",
        "description": "First call in the [KYC API](/v2/guides/kyc-api) flow. Nothing else is accepted before it: `country` is what routes the customer to their country's requirements.\n\n**Answers `202`, not `200`.** We validate the request before we answer and record it with the verification provider after. A `202` means accepted, not satisfied. Read the result from the `kycUpdated` webhook or [`GET /organization/{organizationId}/kyc?requirements=true`](/v2/api-reference/kyc/get-kyc-status).\n\nSubmitting again overwrites what was sent before, and never moves the customer's review status on its own. Personal organizations only.",
        "operationId": "v2SubmitVerification",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitVerificationRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and being applied. The customer's current verification status is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid-request`: validation failed. `detail` lists every missing or invalid field at once, including a `country` that is not ISO 3166-1 alpha-3. Also returned for a business organization, which verifies through [KYB](/v2/guides/kyb).",
            "content": {
              "application/problem+json": {
                "schema": { "$ref": "#/components/schemas/Problem" },
                "example": {
                  "type": "https://docs.etherfuse.com/ramp/v2/errors/invalid-request",
                  "title": "Invalid request",
                  "status": 400,
                  "detail": "dateOfBirth must be a date in YYYY-MM-DD format; country 'MX' is not a recognized ISO 3166-1 alpha-3 country code (e.g. MEX); address.city is required",
                  "instance": "tag:etherfuse.com,2026-08-28:R2-70010/9b2f4c1e-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": {
            "description": "`conflict`: this customer's verification is already approved or denied and cannot be modified. A denied customer resubmits in the hosted step, not over the API.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/verification/documents": {
      "post": {
        "tags": ["KYC"],
        "summary": "Upload verification documents",
        "description": "Upload the customer's documents as `multipart/form-data`. Send at least one of `id_front`, `id_back`, `proof_of_address` or `tax_document`.\n\n**Submit the customer's personal data first.** Until [`POST /organization/{organizationId}/verification`](/v2/api-reference/kyc/submit-verification-data) has run the customer has no stages to upload against, so this answers `409`.\n\n**Answers `202`, not `200`.** We read and check every part before we answer, and send the upload to the verification provider after. Watch [`GET /organization/{organizationId}/kyc?requirements=true`](/v2/api-reference/kyc/get-kyc-status) for the requirement to go satisfied.\n\n**Upload what the current stage asks for.** Send a document once its requirement is listed. The exception is `proof_of_address`: you may send it with the identity document, before its own stage, as long as the identity stage has not been submitted yet. After that, wait for `proofOfAddress` to be listed. See [Requirements unlock in stages](/v2/guides/onboarding#requirements-unlock-in-stages).",
        "operationId": "v2UploadVerificationDocuments",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "id_front": {
                    "type": "string",
                    "format": "binary",
                    "description": "Front of the identity document, or its only side. JPEG, PNG or PDF, up to 10 MB. At least one of `id_front`, `id_back`, `proof_of_address` or `tax_document` must be sent."
                  },
                  "id_back": {
                    "type": "string",
                    "format": "binary",
                    "description": "Back of the identity document, where it has two sides. JPEG, PNG or PDF, up to 10 MB. Required whenever `id_type` is `id_card` or `drivers_license`, and refused for `passport`, which is single-sided. Both sides go in the same request: sending this without `id_front` is a 400, so a back cannot be added to an earlier upload on its own."
                  },
                  "id_type": {
                    "type": "string",
                    "description": "Which identity document the `id_*` parts are. Required whenever one is sent. Case-sensitive: send the value exactly as listed.",
                    "enum": [
                      "ID_CARD",
                      "PASSPORT",
                      "DRIVERS"
                    ]
                  },
                  "proof_of_address": {
                    "type": "string",
                    "format": "binary",
                    "description": "A document evidencing the customer's address. JPEG, PNG or PDF, up to 10 MB. Only accepted once the proof-of-address requirement is outstanding: sent earlier it is accepted with a 202 and then dropped, leaving the requirement outstanding."
                  },
                  "proof_of_address_type": {
                    "type": "string",
                    "description": "Which kind of address document it is. Case-sensitive: send the value exactly as listed.",
                    "default": "UTILITY_BILL",
                    "enum": [
                      "UTILITY_BILL",
                      "BANK_STATEMENT",
                      "TAX_BILL",
                      "EMPLOYMENT_CERTIFICATE",
                      "PROPERTY_DEED",
                      "INVOICE"
                    ]
                  },
                  "tax_document": {
                    "type": "string",
                    "format": "binary",
                    "description": "A tax document, where the customer's country requires one; in Mexico, the Constancia de Situación Fiscal. JPEG, PNG or PDF, up to 10 MB. Not recorded for a customer whose verification does not collect one. See [Country-specific requirements](/v2/guides/onboarding#country-specific-requirements)."
                  },
                  "country": {
                    "type": "string",
                    "description": "Issuing country of these documents, as an ISO 3166-1 alpha-3 code. Only needed when the customer's profile has no country yet.",
                    "example": "MEX"
                  }
                }
              },
              "encoding": {
                "id_front": {
                  "contentType": "image/jpeg, image/png, application/pdf"
                },
                "id_back": {
                  "contentType": "image/jpeg, image/png, application/pdf"
                },
                "proof_of_address": {
                  "contentType": "image/jpeg, image/png, application/pdf"
                },
                "tax_document": {
                  "contentType": "image/jpeg, image/png, application/pdf"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and being applied. The customer's current verification status is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid-request`: validation failed. An unrecognized `id_type` or `proof_of_address_type` is refused with the valid values listed. Otherwise `detail` names the first problem: a two-sided `id_type` without `id_back`, `id_back` without `id_front`, `id_back` for a single-sided `id_type`, a missing `id_type` alongside an `id_*` part, an unsupported content type, an empty or oversized file, an unrecognized `country`, or a body that is not valid `multipart/form-data`. Also returned for a business organization.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": {
            "description": "`conflict`, in one of three cases. Nothing has been submitted for this customer yet, so submit their personal data first. Or their personal data landed but they have not been routed to their country's requirements yet; `kycUpdated` fires when routing completes, or watch `GET /organization/{organizationId}/kyc?requirements=true` for the requirements list to appear. Or their verification is already approved or denied and cannot be modified.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/verification/questionnaire": {
      "post": {
        "tags": ["KYC"],
        "summary": "Submit a verification questionnaire",
        "description": "Submit the customer's answers to one questionnaire, named by `type`.\n\n| `type` | Answers | Applies to |\n|---|---|---|\n| `occupation` | `jobTitle`, `industry` | Every customer |\n| `agreement` | `acceptTerms`, `signatureName`, `signatureDate` | Customers who accepted under your terms |\n\nDocuments are uploaded through [`POST /organization/{organizationId}/verification/documents`](/v2/api-reference/kyc/upload-verification-documents), including a region's tax document.\n\n**`agreement` is a legal declaration, affirmed by `acceptTerms: true`.** By default the customer accepts the agreement themselves in `/idv`. Send `agreement` only if your own terms incorporate ours and you already collected their acceptance. [The customer agreement](/v2/guides/kyc-api#the-customer-agreement) sets out what you are declaring.\n\n**Submit the customer's personal data first**, or this answers `409`. As with document upload, the stage that asks these questions doesn't exist on the customer until then.\n\n**Send `agreement` only once `customerAgreement` is in the requirements list.** It belongs to the last stage, after the identity stage and any proof of address, so do not send it together with `occupation`. An early `agreement` answers `202`, then is refused in the background, and a `kycUpdated` webhook arrives with `needsWork: true`. See [Requirements unlock in stages](/v2/guides/onboarding#requirements-unlock-in-stages).\n\n**Answers `202`, not `200`.** `industry` is resolved against the live category list in the background, so an unrecognized value doesn't fail the request: it leaves the occupation requirement outstanding.",
        "operationId": "v2SubmitVerificationQuestionnaire",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "title": "occupation",
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "type",
                      "jobTitle",
                      "industry"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "description": "Which questionnaire these answers are for. New values may be added. If this names a questionnaire that does not exist, the request is rejected and the error tells you which names are valid.",
                        "enum": [
                          "occupation"
                        ],
                        "example": "occupation"
                      },
                      "jobTitle": {
                        "type": "string",
                        "description": "The customer's occupation, free text. Up to 255 characters.",
                        "example": "Software Engineer"
                      },
                      "industry": {
                        "type": "string",
                        "description": "Occupation category: a `code` from [GET /verification/occupations](/v2/api-reference/kyc/list-occupation-categories). The `label` is not accepted; it is display copy and can be reworded.",
                        "example": "4430200"
                      }
                    }
                  },
                  {
                    "title": "agreement",
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "type",
                      "acceptTerms",
                      "signatureName",
                      "signatureDate"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "description": "Which questionnaire these answers are for. New values may be added. If this names a questionnaire that does not exist, the request is rejected and the error tells you which names are valid.",
                        "enum": [
                          "agreement"
                        ],
                        "example": "agreement"
                      },
                      "acceptTerms": {
                        "type": "boolean",
                        "enum": [
                          true
                        ],
                        "description": "Your affirmation of [the declaration](/v2/guides/kyc-api#the-customer-agreement). Must be `true`.",
                        "example": true
                      },
                      "signatureName": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The name the customer typed to sign, in their own words. Printed on their agreement in a cursive face, so it appears as the signature on the executed document.",
                        "example": "Ana Ejemplo"
                      },
                      "signatureDate": {
                        "type": "string",
                        "format": "date",
                        "description": "When the customer accepted, `YYYY-MM-DD`. Printed on their agreement as the date of signature. Should be within one day of today's date.",
                        "example": "2026-08-24"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and being applied. The customer's current verification status is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid-request`: validation failed. A missing or unrecognized `type`; a blank `jobTitle` or `industry`; an `acceptTerms` that is not `true`; a `signatureName` that is blank or over 255 characters; or a `signatureDate` that is not `YYYY-MM-DD` or is not within a day of today. Also returned for a business organization.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": {
            "description": "`conflict`, in one of three cases. Nothing has been submitted for this customer yet, so submit their personal data first. Or their personal data landed but they have not been routed to their country's requirements yet; `kycUpdated` fires when routing completes, and a retry after it succeeds. Or their verification is already approved or denied and cannot be modified.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/verification/import": {
      "post": {
        "tags": ["KYC"],
        "summary": "Import a verified session",
        "description": "Import a verification the customer already passed on the partner's own Sumsub level, handed over as a share token. Prerequisites, and what does and doesn't transfer: [KYC Sumsub Shared Token](/v2/guides/kyc-sumsub-shared-token).\n\nAnswers **`200`**, not `202`: the import is committed before the response, which carries the same requirements breakdown as [`GET /organization/{organizationId}/kyc?requirements=true`](/v2/api-reference/kyc/get-kyc-status).\n\nThat breakdown covers the identity stage only. Send what it lists, then wait for `kycUpdated` and read the requirements again: `proofOfAddress` and `customerAgreement` appear only when the customer reaches their stages. See [Requirements unlock in stages](/v2/guides/onboarding#requirements-unlock-in-stages).\n\n**Only `shareToken` is accepted.** The verification level and the customer's identity are ours to decide. The token is a credential; it is never logged and appears in no response.\n\nNo country is sent: it is read from the shared verification and used to route the customer. The customer's registered `userInfo.email` is required and is set on the imported applicant.\n\n**Nothing is trusted wholesale.** Everything imported is re-checked against Etherfuse's own requirements, so a session approved in the partner's account is not automatically approved here.",
        "operationId": "v2ImportVerification",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportVerificationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session was imported. `requirements` describes what is still outstanding.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportVerificationResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid-request`; `detail` says why. Any of: `shareToken` missing; the share token is expired, already spent, created for someone else, or shares nothing our requirements ask for; the shared verification has no country on file, or one we cannot verify (nothing is created and the share token is not spent); the customer has no email on file; or the organization is a business.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "409": {
            "description": "`conflict`: this customer's verification has already started. A session can only be imported before verification begins.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/verification/occupations": {
      "get": {
        "tags": ["KYC"],
        "summary": "List occupation categories",
        "description": "The values [`POST /organization/{organizationId}/verification/questionnaire`](/v2/api-reference/kyc/submit-a-verification-questionnaire) accepts for `industry`. Send the `code`.\n\nThe codes are opaque, so this is the only way to know them. We read them live from the same definition the submission validates against, so the two always agree.",
        "operationId": "v2ListOccupationCategories",
        "responses": {
          "200": {
            "description": "The accepted occupation categories.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "occupations"
                  ],
                  "properties": {
                    "occupations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "code",
                          "label"
                        ],
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "The value to send as `industry`."
                          },
                          "label": {
                            "type": "string",
                            "description": "What the code means. Also accepted as `industry`."
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "occupations": [
                    {
                      "code": "1000000",
                      "label": "NOT APPLICABLE"
                    },
                    {
                      "code": "4430200",
                      "label": "INFORMATION TECHNOLOGY - IT PROGRAMMING ENGINEERS OR TECHNICIANS"
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/kyb": {
      "get": {
        "tags": ["KYB"],
        "summary": "Get KYB status",
        "operationId": "v2GetKyb",
        "description": "Returns a business organization's KYB status: the same `status` and `approvedAt` the `kybUpdated` webhook sends. Subscribe to `kybUpdated` to hear about changes, and read this to reconcile after downtime. A personal organization returns `400` `invalid-request`; read its KYC with [Get KYC status](/v2/api-reference/kyc/get-kyc-status).",
        "parameters": [{ "$ref": "#/components/parameters/OrganizationId" }],
        "responses": {
          "200": {
            "description": "The organization's KYB status.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Kyb" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/OrganizationNotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/supported_assets": {
      "get": {
        "tags": ["Assets"],
        "summary": "List supported assets",
        "operationId": "v2ListSupportedAssets",
        "description": "Lists the markets and tokens a quote can use. Each item's `asset` is the value to send as `sourceAsset` or `destinationAsset` on [Create quote](/v2/api-reference/quotes/create-quote).\n\n- **Markets** are a currency in a country, such as MXN in Mexico, with the rails that move it (`spei`, `pix`).\n- **Tokens** are the stablebonds and stablecoins on each chain.\n\nWith no filters you get every market and every token we support. Each filter narrows the lists, and filters combine:\n\n- `chain` keeps tokens on that chain. Markets have no chain, so none come back.\n- `currency` keeps markets in that currency and tokens backed by it.\n- `paymentAccountId` keeps what that account can send or receive. A bank account gives its market and the rails it is registered on; a wallet gives the tokens on each chain it is registered on.\n- `organizationId` does the same for every payment account the organization holds.\n\nA payment account or organization your API key cannot act for returns `404` `not-found`. With both ids, the account must belong to that organization.",
        "parameters": [
          {
            "name": "chain",
            "in": "query",
            "required": false,
            "description": "Only tokens on this chain.",
            "schema": { "$ref": "#/components/schemas/Chain" }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Only markets in this currency and tokens backed by it, as an ISO 4217 code. Not case sensitive.",
            "schema": { "type": "string", "example": "MXN" }
          },
          {
            "name": "paymentAccountId",
            "in": "query",
            "required": false,
            "description": "Only what this payment account can send or receive.",
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "organizationId",
            "in": "query",
            "required": false,
            "description": "Only what this organization's payment accounts can send or receive. An organization with no payment accounts returns two empty lists.",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "The supported markets and tokens.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SupportedAssets" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/organization/{organizationId}/payment_account/{id}/balances": {
      "get": {
        "tags": ["Assets"],
        "summary": "Get payment account balances",
        "operationId": "v2GetPaymentAccountBalances",
        "description": "Reads the balances of one of the organization's wallets: every supported Etherfuse asset it holds, with the value of each in the asset's own fiat currency and in USD.\n\nWe read the address the wallet was registered with, on the chain it was registered on. Send `chain` to read an EVM wallet on another EVM chain. A bank account has no address and returns `422` `unsupported`. An address that is not valid on `chain` returns `400` `invalid-request`.\n\nAssets with a zero balance are left out, so an empty `balances` is a successful read of a wallet holding nothing. `currency` is per balance, because one wallet can hold CETES in MXN and TESOURO in BRL at once. `balance` carries the asset's own decimal precision; the two value fields carry six decimal places. Interest-bearing assets accrue all the time, so two reads seconds apart can differ for an unchanged balance.\n\nBalance reads are rate limited per organization. Only calls that reach the chain count, so a `400`, `404` or `422` does not use the limit. Over the limit, the call returns `429` `rate-limit-exceeded` with a `Retry-After` header.",
        "parameters": [
          { "$ref": "#/components/parameters/OrganizationId" },
          { "$ref": "#/components/parameters/PaymentAccountId" },
          {
            "name": "chain",
            "in": "query",
            "required": false,
            "description": "The chain to read the wallet on. Defaults to the chain it was registered on.",
            "schema": { "$ref": "#/components/schemas/Chain" }
          }
        ],
        "responses": {
          "200": {
            "description": "The wallet's balances.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaymentAccountBalances" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": {
            "description": "`unsupported`: the payment account is not a wallet with an address, for example a bank account.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/partner_statements": {
      "get": {
        "tags": ["Statements"],
        "summary": "List partner statements",
        "operationId": "v2ListPartnerStatements",
        "description": "Returns your monthly revenue share statements, newest first. They belong to the organization that owns your API key, which must be a partner organization; a customer organization returns `403` `forbidden`.\n\nOnly closed months are listed. A month still open does not appear and its CSV and PDF return `404`. A closed month carries no amount until a payout is recorded, because the payout is converted at the rate on that day.",
        "responses": {
          "200": {
            "description": "Your closed and paid statement months.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PartnerStatementList" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/partner_statement/{year}-{month}/csv": {
      "get": {
        "tags": ["Statements"],
        "summary": "Download statement CSV",
        "description": "Downloads one closed month's statement as CSV, for example `/partner_statement/2026-6/csv`. The year and month are one path segment; the month may be zero padded or not. Returns a `text/csv` attachment named `partner-statement-2026-06.csv`.",
        "operationId": "v2DownloadPartnerStatementCsv",
        "parameters": [
          {
            "name": "year",
            "in": "path",
            "description": "The four digit statement year, for example 2026",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "month",
            "in": "path",
            "description": "The statement month, 1 through 12",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The statement as a CSV attachment.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": {
            "description": "`not-found`: the month is still open, or has no statement.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/partner_statement/{year}-{month}/pdf": {
      "get": {
        "tags": ["Statements"],
        "summary": "Download statement PDF",
        "description": "Downloads one closed month's statement as PDF, for example `/partner_statement/2026-6/pdf`. The year and month are one path segment; the month may be zero padded or not. Returns an `application/pdf` attachment named `partner-statement-2026-06.pdf`.",
        "operationId": "v2DownloadPartnerStatementPdf",
        "parameters": [
          {
            "name": "year",
            "in": "path",
            "description": "The four digit statement year, for example 2026",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "month",
            "in": "path",
            "description": "The statement month, 1 through 12",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The statement as a PDF attachment.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": {
            "description": "`not-found`: the month is still open, or has no statement.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/lookup/exchange_rate": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "List exchange rates",
        "description": "Returns current USD exchange rates for all supported currencies, each keyed by\n`usd_to_<currency>` with a per-provider `sources` breakdown.\n\nPublic. No API key required.",
        "operationId": "exchange_rates",
        "parameters": [
          {
            "name": "max_age",
            "in": "query",
            "description": "Maximum age of a source in seconds. Sources whose `updated_at` timestamp\nis older than `now - max_age` are excluded from the `sources` map.\nWhen omitted, all sources are returned regardless of age.",
            "required": false,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "All exchange rates keyed by `usd_to_<currency>`",
            "content": {
              "application/json": {
                "schema": {},
                "example": {
                  "usd_to_brl": {
                    "rate": "4.97",
                    "sources": {
                      "provider_1": {
                        "rate": "4.97",
                        "updated_at": 1709510400
                      }
                    },
                    "updated_at": 1709510400
                  },
                  "usd_to_mxn": {
                    "rate": "17.15",
                    "sources": {
                      "provider_1": {
                        "rate": "17.15",
                        "updated_at": 1709510400
                      }
                    },
                    "updated_at": 1709510400
                  }
                }
              }
            }
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/lookup/exchange_rate/usd_to_{currency}": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "Get exchange rate",
        "description": "Returns the current USD-to-specified-currency exchange rate, with a\nper-provider `sources` breakdown.\n\nPublic. No API key required.",
        "operationId": "usd_to_currency",
        "parameters": [
          {
            "name": "currency",
            "in": "path",
            "description": "The target currency code (e.g. \"mxn\", \"brl\", \"eur\")",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "max_age",
            "in": "query",
            "description": "Maximum age of a source in seconds. Sources whose `updated_at` timestamp\nis older than `now - max_age` are excluded from the `sources` map.\nWhen omitted, all sources are returned regardless of age.",
            "required": false,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Exchange rate with per-provider source breakdown",
            "content": {
              "application/json": {
                "schema": {},
                "example": {
                  "rate": "17.15",
                  "sources": {
                    "provider_1": {
                      "rate": "17.15",
                      "updated_at": 1709510400
                    }
                  },
                  "updated_at": 1709510400
                }
              }
            }
          },
          "404": {
            "description": "Currency is not supported"
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/lookup/stablebonds": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "List stablebonds",
        "description": "Returns all stablebonds with current prices, supplies, and multi-chain\nbreakdown. Data is cached for ~5 minutes.\n\nPublic. No API key required.",
        "operationId": "get_stablebonds",
        "responses": {
          "200": {
            "description": "All stablebonds with pricing and supply",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StablebondsCachable"
                }
              }
            }
          },
          "503": {
            "description": "Stablebonds data not yet available"
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/lookup/stablebond/{identifier}": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "Get stablebond",
        "description": "Returns one stablebond with its current price, supply, and per-chain breakdown, from the same snapshot as `GET /lookup/stablebonds`.\n\n`identifier` can be any of:\n\n- the bond symbol or name, matched case-insensitively (`CETES`, `cetes`, `Etherfuse CETES`)\n- the Solana mint address\n- a chain's `tokenIdentifier` from the `blockchains` array (EVM addresses are matched case-insensitively)\n- a Stellar asset as `CODE-ISSUER` or `CODE:ISSUER`, or the issuer address alone\n\nWhen more than one bond matches, the first bond in `GET /lookup/stablebonds` order is returned. Etherfuse bonds on Stellar share an issuer, so an issuer address alone does not identify a specific bond. Use the symbol or the full `CODE-ISSUER` asset instead.\n\nPublic, no API key required.",
        "operationId": "get_stablebond",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "description": "Bond symbol or name, Solana mint address, chain token identifier, or Stellar asset (e.g. \"CETES\", \"CETES7CKqqKQizuSN6iWQwmTeFRjbJR6Vw2XRKfEDR8f\").",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The matching stablebond",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StablebondInfoCachable"
                }
              }
            }
          },
          "404": {
            "description": "No stablebond matches the identifier"
          },
          "503": {
            "description": "Stablebonds data not yet available"
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/lookup/tokens/cost": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "List token costs",
        "description": "Returns current pricing for every Etherfuse token: stablebonds (CETES,\nTESOURO, USTRY, ...) and principal tokens (MEXe, ...). Each entry carries the\ntoken's cost in its denomination currency and in USD, plus a `sources` map\ngiving the exchange rate and derived USD cost from each FX provider, so you\ncan run your own oracle-safety checks.\n\nKeyed by uppercased token symbol (`CETES`, `MEXE`). New tokens appear here\nautomatically once they are registered; nothing needs to change on your side.\n\nThis replaces `GET /lookup/bonds/cost`, which is deprecated and covers only\nstablebonds.\n\nPublic, no API key required.",
        "operationId": "get_token_costs",
        "parameters": [
          {
            "name": "max_age",
            "in": "query",
            "description": "Maximum age of a source in seconds. Sources older than this are excluded.",
            "required": false,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pricing keyed by uppercased token symbol",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "$ref": "#/components/schemas/TokenCost"
                  },
                  "propertyNames": {
                    "type": "string"
                  }
                },
                "example": {
                  "CETES": {
                    "token_symbol": "CETES",
                    "token_mint": "CETES7CKqqKQizuSN6iWQwmTeFRjbJR6Vw2XRKfEDR8f",
                    "token_kind": "bond",
                    "currency": "MXN",
                    "token_cost_in_fiat": "1.177336",
                    "token_cost_in_usd": "0.069404",
                    "fiat_exchange_rate_with_usd": "16.96343",
                    "current_basis_points": 554,
                    "current_time": "2026-08-20T16:30:52.382508961+00:00",
                    "payment_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                    "sources": {
                      "d2ce2919-082f-4d60-8fbb-a15b3b570e57": {
                        "exchange_rate": "16.96343",
                        "token_cost_in_usd": "0.069404",
                        "updated_at": 1787243387
                      },
                      "8e1e2967-0283-42dd-bb06-e49193c368d2": {
                        "exchange_rate": "16.96425",
                        "token_cost_in_usd": "0.069401",
                        "updated_at": 1787243417
                      }
                    }
                  },
                  "MEXE": {
                    "token_symbol": "MEXe",
                    "token_mint": "MEXeQE5UjuusUoyS2KxbUXp6o21YbbBJaNvPMMEfr4y",
                    "token_kind": "principal_token",
                    "underlying_bond_symbol": "CETES",
                    "currency": "MXN",
                    "token_cost_in_fiat": "1.000000",
                    "token_cost_in_usd": "0.058950",
                    "fiat_exchange_rate_with_usd": "16.96343",
                    "current_basis_points": 0,
                    "current_time": "2026-08-20T16:30:52.382508961+00:00",
                    "sources": {
                      "d2ce2919-082f-4d60-8fbb-a15b3b570e57": {
                        "exchange_rate": "16.96343",
                        "token_cost_in_usd": "0.058950",
                        "updated_at": 1787243387
                      },
                      "8e1e2967-0283-42dd-bb06-e49193c368d2": {
                        "exchange_rate": "16.96425",
                        "token_cost_in_usd": "0.058947",
                        "updated_at": 1787243417
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Token costs not yet available"
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/lookup/tokens/cost/{identifier}": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "Get token cost",
        "description": "Returns current pricing for one Etherfuse token, either a stablebond or a\nprincipal token.\n\n`identifier` is the token symbol or its Solana mint address. Symbols are\nmatched case-insensitively, so `mexe`, `MEXE` and `MEXe` all resolve to the\nsame token.\n\nThis replaces `GET /lookup/bonds/cost/{identifier}`, which is deprecated and\ncovers only stablebonds.\n\nPublic, no API key required.",
        "operationId": "get_token_cost",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "description": "Token symbol or Solana mint address (e.g. \"MEXe\", \"CETES\", \"MEXeQE5UjuusUoyS2KxbUXp6o21YbbBJaNvPMMEfr4y\"). Symbols are case-insensitive.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "max_age",
            "in": "query",
            "description": "Maximum age of a source in seconds. Sources older than this are excluded.",
            "required": false,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pricing for the requested token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenCost"
                }
              }
            }
          },
          "404": {
            "description": "Token not found for identifier"
          },
          "503": {
            "description": "Token cost not currently available"
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/lookup/country-codes": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "List country codes",
        "description": "Returns supported countries with ISO 3166-1 alpha-2 codes, alpha-3 codes, and\nEnglish names. Use the alpha-2 code for `registrationInfo.countryOfBirth` and\n`registrationInfo.countryOfIncorporation` when registering a payment account.\n\nPublic. No API key required.",
        "operationId": "country_codes",
        "responses": {
          "200": {
            "description": "Supported countries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Country"
                  }
                }
              }
            }
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/lookup/restricted-countries": {
      "get": {
        "tags": [
          "Lookup"
        ],
        "summary": "List restricted countries",
        "description": "Returns the countries that are effectively restricted for KYC or KYB based on\nexternal consensus and internal overrides. Each entry shows whether KYC, KYB, or both\nare blocked.\n\nPublic. No API key required.",
        "operationId": "restricted_countries",
        "responses": {
          "200": {
            "description": "Restricted countries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestrictedCountriesResponse"
                }
              }
            }
          }
        },
        "security": [
          {}
        ]
      }
    },
    "/auth/launch": {
      "servers": [
        {
          "url": "https://sandbox.etherfuse.com",
          "description": "Sandbox app (NOT the API host)"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Launch a user into the app",
        "description": "> ⚠️ **Different host.** Unlike every other endpoint in this reference, `/auth/launch` lives on the **app host** (`app.etherfuse.com` / `sandbox.etherfuse.com`), **not** the API host (`api.etherfuse.com`). It is a browser-facing page, not a JSON API.\n\nDrops one of your users into the Etherfuse app with an authenticated session. Use it for **browser** flows, such as sending a user into KYB. Your server POSTs an HTML form (or redirects the browser) to this URL; the app establishes the session and redirects the user to `target`. To embed the flow in an iframe or popup instead, use the [postMessage variant](/v2/api-reference/authentication/launch-a-user-via-postmessage).\n\n## Targets\n\nThe `target` and `scope` for every flow you can launch a user into (and what to know about each) are documented in [User Launch Flows](/v2/guides/user-launch-flows). A `target` must be one of those allowed paths (optionally with a query string such as `?org=<org_id>`); any other path is rejected with `invalid_target`.\n\nThe JWT's `sub` is _strongly recommended_ to be a UUID identifying the person signing in (see [Sign a user JWT](/v2/guides/jwt-authentication#sign-a-user-jwt) for how `sub` relates to an individual's organization `id` and a business member's `memberId`), and its `scope` follows the same rules as [`/auth/token`](/v2/api-reference/authentication/exchange-a-partner-jwt). The endpoint is unauthenticated: the JWT or refresh token is the credential.",
        "operationId": "auth_launch",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/AuthLaunchRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuthLaunchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An HTML page that establishes the session and redirects the browser to `target`."
          }
        }
      },
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "Launch a user via postMessage",
        "description": "> ⚠️ **Different host.** Like [POST /auth/launch](/v2/api-reference/authentication/launch-a-user-into-the-app), this lives on the **app host** (`app.etherfuse.com` / `sandbox.etherfuse.com`), **not** the API host. It is a browser-facing page, not a JSON API.\n\nThe `postMessage` variant of launch, for embedding the flow in an **iframe or popup**. Open `/auth/launch` with no body; on load it asks its embedder (your page) for credentials over `postMessage`, and you reply. Use this when you don't want to server-render a [form POST](/v2/api-reference/authentication/launch-a-user-into-the-app). It also lets you handle session expiration and launch into different targets seamlessly, without a redirect page.\n\n## postMessage contract\n\n1. On load, the launch frame posts an `etherfuse:auth:request` message to its embedder.\n2. Your page replies with an `etherfuse:auth:response` carrying the fields below.\n3. If you need a moment to sign a JWT, send `etherfuse:auth:await` first so the frame doesn't time out.\n\n## Fields\n\n- **`grant_type`** and **`assertion`**: a raw partner JWT (`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`) or a `refresh_token` from a prior [server-side exchange](/v2/api-reference/authentication/exchange-a-partner-jwt) (`grant_type=refresh_token`).\n- **`target`**: the app path to land on. Must be an allowed target. See [User Launch Flows](/v2/guides/user-launch-flows#targets). Any other path is rejected.\n- **`return_url`** *(optional)*: where to send the user when they leave.\n\nThe JWT's `sub` and `scope` follow the same rules as [POST /auth/launch](/v2/api-reference/authentication/launch-a-user-into-the-app).",
        "operationId": "auth_launch_postmessage",
        "security": [],
        "responses": {
          "200": {
            "description": "An HTML page that requests credentials from the embedder over `postMessage`, establishes the session, and redirects the browser to `target`."
          }
        }
      }
    }
  },
  "webhooks": {
    "orderUpdated": {
      "post": {
        "tags": ["Webhook Events"],
        "summary": "orderUpdated",
        "operationId": "v2OrderUpdatedWebhook",
        "description": "Sent to `orderUpdated` subscriptions when an order's status changes, and when its funding instructions arrive or are replaced. `order` is the same object `GET /organization/{organizationId}/order/{id}` returns; its `organizationId` is the organization it belongs to.\n\nThe `X-Signature` header contains an HMAC-SHA256 signature over the [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785)-canonicalized JSON body. See [Verifying Webhooks](/guides/verifying-webhooks).",
        "parameters": [{ "$ref": "#/components/parameters/XSignature" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/OrderUpdatedWebhook" }
            }
          }
        },
        "responses": {
          "200": { "description": "Return any 2xx to acknowledge the delivery." }
        }
      }
    },
    "paymentAccountUpdated": {
      "post": {
        "tags": ["Webhook Events"],
        "summary": "paymentAccountUpdated",
        "operationId": "v2PaymentAccountUpdatedWebhook",
        "description": "Sent to `paymentAccountUpdated` subscriptions when a payment account's `status` or the `status` of any rail direction changes. `paymentAccount` is the same object `GET /organization/{organizationId}/payment_account/{id}` returns, and `organizationId` is the organization that holds it.\n\nThe `X-Signature` header contains an HMAC-SHA256 signature over the [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785)-canonicalized JSON body. See [Verifying Webhooks](/guides/verifying-webhooks).",
        "parameters": [{ "$ref": "#/components/parameters/XSignature" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PaymentAccountUpdatedWebhook" }
            }
          }
        },
        "responses": {
          "200": { "description": "Return any 2xx to acknowledge the delivery." }
        }
      }
    },
    "kycUpdated": {
      "post": {
        "summary": "kycUpdated",
        "operationId": "v2KycUpdatedWebhook",
        "description": "Sent to `kycUpdated` subscriptions when a customer's KYC status changes, and when a write you sent was accepted but could not be applied. `organizationId` is the customer organization.\n\nWhen `kyc.needsWork` is `true`, the customer can correct and resubmit, and `kyc.userMessage` says what to fix. `needsWork` also arrives **without** a status change, when one of the verification endpoints answered `202` and the work then failed; `status` is then the customer's current status, and `userMessage` names what to send again. See [KYC API error handling](/v2/guides/kyc-api#error-handling).\n\nThe `X-Signature` header contains an HMAC-SHA256 signature over the [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785)-canonicalized JSON body. See [Verifying Webhooks](/guides/verifying-webhooks).",
        "parameters": [
          {
            "name": "X-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 signature: `sha256={hex}`. Computed over the canonicalized (RFC 8785) request body using your webhook secret.",
            "schema": {
              "type": "string"
            },
            "example": "sha256=a1b2c3d4e5f6..."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycUpdatedWebhook"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any `2xx` to acknowledge receipt."
          }
        },
        "tags": [
          "Webhook Events"
        ]
      }
    },
    "kybUpdated": {
      "post": {
        "tags": ["Webhook Events"],
        "summary": "kybUpdated",
        "operationId": "v2KybUpdatedWebhook",
        "description": "Sent to `kybUpdated` subscriptions when a business customer's KYB status changes. `organizationId` is the business organization, and `kyb` is the same status `GET /organization/{organizationId}/kyb` returns.\n\nThe `X-Signature` header contains an HMAC-SHA256 signature over the [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785)-canonicalized JSON body. See [Verifying Webhooks](/guides/verifying-webhooks).",
        "parameters": [{ "$ref": "#/components/parameters/XSignature" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/KybUpdatedWebhook" }
            }
          }
        },
        "responses": {
          "200": { "description": "Return any 2xx to acknowledge the delivery." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "API key sent in the Authorization header, with no `Bearer` prefix."
      }
    },
    "parameters": {
      "OrganizationId": {
        "name": "organizationId",
        "in": "path",
        "required": true,
        "description": "The organization this call acts for: your API key's organization or one of its customer organizations. Any other id returns `404` `not-found`.",
        "schema": { "type": "string", "format": "uuid" }
      },
      "PaymentAccountId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The payment account `id`.",
        "schema": { "type": "string", "format": "uuid" }
      },
      "OrderId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The order `id`, which is the `quoteId` it was created from.",
        "schema": { "type": "string", "format": "uuid" }
      },
      "WebhookSubscriptionId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The webhook subscription `id`.",
        "schema": { "type": "string", "format": "uuid" }
      },
      "MemberId": {
        "name": "memberId",
        "in": "path",
        "required": true,
        "description": "The person's `sub`: the value you put in the JWT `sub` when they sign in.",
        "schema": { "type": "string" }
      },
      "PageSize": {
        "name": "pageSize",
        "in": "query",
        "required": false,
        "description": "Items per page. Default 30, maximum 100.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 30 }
      },
      "PageNumber": {
        "name": "pageNumber",
        "in": "query",
        "required": false,
        "description": "Page to return, starting at 0.",
        "schema": { "type": "integer", "minimum": 0, "default": 0 }
      },
      "XSignature": {
        "name": "X-Signature",
        "in": "header",
        "required": true,
        "description": "HMAC-SHA256 signature: `sha256={hex}`. Computed over the canonicalized (RFC 8785) request body using your subscription's secret.",
        "schema": { "type": "string" }
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "`invalid-request`: a path, query or body value is malformed, or `pageSize` is 0 or over 100.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "Unauthorized": {
        "description": "`unauthorized`: the `Authorization` header is missing, not ASCII, or does not hold a valid API key.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "NotFound": {
        "description": "`not-found`: your API key cannot act for this organization, or no such resource belongs to it.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "OrganizationNotFound": {
        "description": "`not-found`: your API key cannot act for this organization.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "PayloadTooLarge": {
        "description": "`payload-too-large`: the request body is too large.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "Forbidden": {
        "description": "`forbidden`: your API key's organization may not do this.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "Conflict": {
        "description": "`conflict`: the request conflicts with the resource's current state. `detail` says how.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "RateLimitExceeded": {
        "description": "`rate-limit-exceeded`: too many requests. Wait the number of seconds in `Retry-After` and try again.",
        "headers": {
          "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "ServiceUnavailable": {
        "description": "`service-unavailable`: a provider or chain we depend on did not answer in time. Retry with backoff.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "InternalError": {
        "description": "`internal-error`: an unexpected error on our side. Retry with backoff, and quote `instance` to support if it persists.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. Every v2 error uses this body with content type `application/problem+json`. `errors` is present only on `validation-error`. `required` and `missing` are present only on `registration-info-required`.",
        "required": ["type", "title", "status", "detail", "instance"],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "`https://docs.etherfuse.com/ramp/v2/errors/` followed by the problem slug, for example `https://docs.etherfuse.com/ramp/v2/errors/quote-expired`. Switch on this value.",
            "example": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired"
          },
          "title": {
            "type": "string",
            "description": "Short summary of the problem type.",
            "example": "Quote expired"
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code.",
            "example": 400
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation of this occurrence. Do not parse it.",
            "example": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist"
          },
          "instance": {
            "type": "string",
            "description": "Unique identifier for this occurrence. Quote it to support.",
            "example": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d"
          },
          "errors": {
            "type": "array",
            "description": "Present on `validation-error`. One entry per invalid field.",
            "items": { "$ref": "#/components/schemas/FieldViolation" }
          },
          "required": {
            "type": "array",
            "description": "Present on `registration-info-required`. Every registration field the route requires, as JSON Pointers into the request, for example `#/registrationInfo/taxId`.",
            "items": { "type": "string" }
          },
          "missing": {
            "type": "array",
            "description": "Present on `registration-info-required`. The required fields you must still send, as JSON Pointers into the request.",
            "items": { "type": "string" }
          }
        }
      },
      "FieldViolation": {
        "type": "object",
        "required": ["pointer", "detail"],
        "properties": {
          "pointer": {
            "type": "string",
            "description": "JSON Pointer to the field in the request, for example `#/accountDetails/clabe`.",
            "example": "#/accountDetails/clabe"
          },
          "detail": {
            "type": "string",
            "description": "What is wrong with the field.",
            "example": "This field is required."
          }
        }
      },
      "PaymentAccountKind": {
        "type": "string",
        "enum": ["bank", "wallet", "embeddedWallet", "debitCard", "cash"],
        "description": "The kind of account. `embeddedWallet` is a wallet we create on a chain; see [Embedded wallets](/v2/guides/embedded-wallets). `debitCard` and `cash` return `422` `unsupported`."
      },
      "PaymentAccountStatus": {
        "type": "string",
        "enum": ["pending", "ready", "rejected", "disabled"],
        "description": "The account as a whole. `ready` when any rail direction is `ready`. `rejected` when every rail direction is `rejected`; register the account again with corrected `registrationInfo` to retry. `disabled` when we turned the account off; it cannot be registered or quoted. Otherwise `pending`. Readiness per rail and direction is in `rails`."
      },
      "PaymentRail": {
        "type": "string",
        "enum": ["spei", "pix", "solana", "base", "polygon", "monad", "stellar", "avalanche"],
        "description": "The network that moves the money. For a wallet, the rail is its chain."
      },
      "RouteRegistrationStatus": {
        "type": "string",
        "enum": ["pending", "ready", "rejected", "disabled"],
        "description": "The account's status on one rail in one direction. Only `ready` can be quoted."
      },
      "Chain": {
        "type": "string",
        "enum": ["stellar", "solana", "base", "polygon", "monad", "avalanche"],
        "description": "The chain a wallet is on."
      },
      "AccountDetailsKind": {
        "type": "string",
        "enum": ["clabe", "pixKey", "address", "signerPublicKey"],
        "description": "The kind of identifier that names the account. `signerPublicKey` names an `embeddedWallet` account by the P-256 key that controls it."
      },
      "AccountDetails": {
        "description": "The identifier that names the account, tagged by `kind`. The kind decides the field set: send exactly the fields your kind declares. A missing field or a field the kind does not declare returns `400` `validation-error` with a pointer per field.",
        "oneOf": [
          { "$ref": "#/components/schemas/ClabeAccountDetails" },
          { "$ref": "#/components/schemas/PixKeyAccountDetails" },
          { "$ref": "#/components/schemas/AddressAccountDetails" },
          { "$ref": "#/components/schemas/SignerPublicKeyAccountDetails" }
        ],
        "discriminator": {
          "propertyName": "kind",
          "mapping": {
            "clabe": "#/components/schemas/ClabeAccountDetails",
            "pixKey": "#/components/schemas/PixKeyAccountDetails",
            "address": "#/components/schemas/AddressAccountDetails",
            "signerPublicKey": "#/components/schemas/SignerPublicKeyAccountDetails"
          }
        }
      },
      "ClabeAccountDetails": {
        "type": "object",
        "title": "clabe",
        "required": ["kind", "clabe"],
        "additionalProperties": false,
        "properties": {
          "kind": { "type": "string", "const": "clabe" },
          "clabe": {
            "type": "string",
            "description": "An 18-digit Mexican CLABE. We check its length and check digit.",
            "example": "012180000000000002"
          }
        }
      },
      "PixKeyAccountDetails": {
        "type": "object",
        "title": "pixKey",
        "required": ["kind", "pixKey"],
        "additionalProperties": false,
        "properties": {
          "kind": { "type": "string", "const": "pixKey" },
          "pixKey": {
            "type": "string",
            "description": "A Brazilian PIX key: an email, a phone, a CPF, a CNPJ or a random key. 1 to 77 characters.",
            "maxLength": 77
          }
        }
      },
      "AddressAccountDetails": {
        "type": "object",
        "title": "address",
        "required": ["kind", "address", "chain"],
        "additionalProperties": false,
        "properties": {
          "kind": { "type": "string", "const": "address" },
          "address": {
            "type": "string",
            "description": "The wallet address. We validate it for `chain`.",
            "example": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"
          },
          "chain": { "$ref": "#/components/schemas/Chain" }
        }
      },
      "SignerPublicKeyAccountDetails": {
        "type": "object",
        "title": "signerPublicKey",
        "description": "Register with `kind` `embeddedWallet`. We create a wallet on `chain` that only the holder of `signerPublicKey` can move funds from. Stellar is the only chain today.",
        "required": ["kind", "chain", "signerPublicKey"],
        "additionalProperties": false,
        "properties": {
          "kind": { "type": "string", "const": "signerPublicKey" },
          "chain": { "$ref": "#/components/schemas/Chain" },
          "signerPublicKey": {
            "type": "string",
            "description": "The holder's P-256 public key, hex-encoded SEC1, compressed or uncompressed. The holder signs order authorizations with the matching private key.",
            "example": "02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc"
          }
        }
      },
      "RegistrationInfo": {
        "type": "object",
        "additionalProperties": false,
        "description": "Account holder information a bank account route requires. The route and the organization's account type (personal or business) decide which fields are required: `accountHolderName` and `taxId` for bank accounts, plus the personal or business fields on routes that need them. We fill a field from the organization's verified identity when we hold it, so you only need to send the fields a `registration-info-required` problem lists in `missing`. You may send other registration fields too: we check their format and ignore the ones the route does not use. A name that is not listed here returns `400` `validation-error`. Wallets require no fields.",
        "properties": {
          "accountHolderName": { "type": "string", "description": "Name of the account holder." },
          "taxId": { "type": "string", "description": "Tax ID of the account holder. Must match the verified tax ID when we hold one.", "example": "XAXX010101000" },
          "curp": { "type": "string", "description": "Mexican CURP (personal accounts).", "example": "XEXX010101HNEXXXA4" },
          "firstName": { "type": "string", "description": "First name (personal accounts)." },
          "paternalLastName": { "type": "string", "description": "Paternal last name (personal accounts)." },
          "maternalLastName": { "type": "string", "description": "Maternal last name (personal accounts)." },
          "dateOfBirth": { "type": "string", "format": "date", "description": "`YYYY-MM-DD` (personal accounts)." },
          "countryOfBirth": { "type": "string", "description": "Two-letter country code (personal accounts).", "example": "MX" },
          "dateOfIncorporation": { "type": "string", "format": "date", "description": "`YYYY-MM-DD` (business accounts)." },
          "countryOfIncorporation": { "type": "string", "description": "Two-letter country code (business accounts).", "example": "MX" }
        }
      },
      "CreatePaymentAccountRequest": {
        "type": "object",
        "required": ["kind", "label", "accountDetails"],
        "properties": {
          "kind": { "$ref": "#/components/schemas/PaymentAccountKind" },
          "label": {
            "type": "string",
            "description": "Your organization's name for the account. Must not be blank. We trim it.",
            "example": "Operating account"
          },
          "country": {
            "type": "string",
            "description": "Two-letter country code of the account's market. Send with `currency` for a bank account. Omit for a wallet.",
            "example": "MX"
          },
          "currency": {
            "type": "string",
            "description": "Three-letter currency code of the account's market. Send with `country` for a bank account. Omit for a wallet.",
            "example": "MXN"
          },
          "accountDetails": { "$ref": "#/components/schemas/AccountDetails" },
          "registrationInfo": { "$ref": "#/components/schemas/RegistrationInfo" }
        }
      },
      "UpdatePaymentAccountRequest": {
        "type": "object",
        "required": ["label"],
        "properties": {
          "label": { "type": "string", "description": "The new label." }
        }
      },
      "RailTransferDetails": {
        "type": "object",
        "description": "What the account can do on this rail in one direction.",
        "required": ["isDefault", "status"],
        "properties": {
          "isDefault": {
            "type": "boolean",
            "description": "True when a quote that does not name a rail for this side uses this rail."
          },
          "status": { "$ref": "#/components/schemas/RouteRegistrationStatus" },
          "minAmount": {
            "type": "string",
            "description": "Minimum amount on this rail, as a decimal string in the rail's `currency`. Absent when we know of no minimum."
          },
          "maxAmount": {
            "type": "string",
            "description": "Maximum amount on this rail, as a decimal string in the rail's `currency`. Absent when we know of no maximum; that does not mean unlimited."
          }
        }
      },
      "AccountRail": {
        "type": "object",
        "required": ["source", "destination"],
        "properties": {
          "currency": {
            "type": "string",
            "description": "The fiat currency of this rail. Absent for a wallet rail.",
            "example": "MXN"
          },
          "source": {
            "description": "The account as the source of a transfer on this rail. Null when the rail cannot carry money from this account.",
            "anyOf": [{ "$ref": "#/components/schemas/RailTransferDetails" }, { "type": "null" }]
          },
          "destination": {
            "description": "The account as the destination of a transfer on this rail. Null when the rail cannot carry money to this account.",
            "anyOf": [{ "$ref": "#/components/schemas/RailTransferDetails" }, { "type": "null" }]
          }
        }
      },
      "PaymentAccount": {
        "type": "object",
        "required": ["id", "status", "label", "rails", "createdAt"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "status": { "$ref": "#/components/schemas/PaymentAccountStatus" },
          "label": { "type": "string", "description": "Your organization's name for the account." },
          "rails": {
            "type": "object",
            "description": "The rails this account can move money on, keyed by rail name (a `PaymentRail` value such as `spei` or `stellar`). A rail the account cannot move money on is absent. The account identifier you registered is not returned.",
            "additionalProperties": { "$ref": "#/components/schemas/AccountRail" }
          },
          "createdAt": { "type": "string", "format": "date-time" }
        },
        "example": {
          "id": "3f9a2c1e-5b7d-4e8f-9a01-2b3c4d5e6f70",
          "status": "pending",
          "label": "Operating account",
          "rails": {
            "spei": {
              "currency": "MXN",
              "source": { "isDefault": true, "status": "ready" },
              "destination": { "isDefault": true, "status": "pending" }
            }
          },
          "createdAt": "2026-09-28T17:04:11.482Z"
        }
      },
      "PaymentAccountPage": {
        "type": "object",
        "required": ["items", "totalItems", "pageSize", "pageNumber", "totalPages"],
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/PaymentAccount" } },
          "totalItems": { "type": "integer" },
          "pageSize": { "type": "integer" },
          "pageNumber": { "type": "integer" },
          "totalPages": { "type": "integer" }
        }
      },
      "PaymentAccountRequirements": {
        "type": "object",
        "required": ["accountDetails", "registrationInfo"],
        "properties": {
          "accountDetails": {
            "type": "object",
            "required": ["kind", "required"],
            "properties": {
              "kind": { "type": "string", "description": "The `accountDetails.kind` you asked about." },
              "required": { "type": "array", "items": { "type": "string" }, "description": "The fields `accountDetails` must carry for this kind, as JSON Pointers such as `#/accountDetails/clabe`." }
            }
          },
          "registrationInfo": {
            "type": "object",
            "required": ["required", "missing"],
            "properties": {
              "required": { "type": "array", "items": { "type": "string" }, "description": "Every registration field the route requires, as JSON Pointers such as `#/registrationInfo/taxId`. The same lists as the `registration-info-required` error." },
              "missing": { "type": "array", "items": { "type": "string" }, "description": "The required fields we hold no verified value for, as JSON Pointers. Ask the customer for these. This list is for registering a new account. Registering an account the organization already holds may need fewer fields." }
            }
          }
        }
      },
      "PaymentAccountRef": {
        "description": "A payment account in a quote, tagged by `type`. Use `stored` with a registered account's `id`. `inline` is not supported yet and returns `400` `invalid-request`.",
        "oneOf": [
          { "$ref": "#/components/schemas/StoredPaymentAccountRef" },
          { "$ref": "#/components/schemas/InlinePaymentAccountRef" }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "stored": "#/components/schemas/StoredPaymentAccountRef",
            "inline": "#/components/schemas/InlinePaymentAccountRef"
          }
        }
      },
      "StoredPaymentAccountRef": {
        "type": "object",
        "title": "stored",
        "required": ["type", "id"],
        "properties": {
          "type": { "type": "string", "const": "stored" },
          "id": { "type": "string", "format": "uuid", "description": "A payment account `id` registered to the organization in the route." }
        }
      },
      "InlinePaymentAccountRef": {
        "type": "object",
        "title": "inline",
        "description": "Not supported yet. Returns `400` `invalid-request`.",
        "required": ["type"],
        "properties": {
          "type": { "type": "string", "const": "inline" }
        }
      },
      "CreateQuoteRequest": {
        "type": "object",
        "required": ["source", "destination", "sourceAmount", "sourceAsset", "destinationAsset"],
        "properties": {
          "source": { "$ref": "#/components/schemas/PaymentAccountRef" },
          "destination": { "$ref": "#/components/schemas/PaymentAccountRef" },
          "sourceAmount": {
            "type": "string",
            "description": "Amount the source sends, as a positive decimal string.",
            "example": "1000"
          },
          "sourceAsset": {
            "type": "string",
            "description": "An `asset` from `GET /supported_assets`: a currency code such as `MXN` for a fiat source, or a token's `asset` for a crypto source.",
            "example": "MXN"
          },
          "destinationAsset": {
            "type": "string",
            "description": "An `asset` from `GET /supported_assets`: a currency code for a fiat destination, or a token's `asset` for a crypto destination.",
            "example": "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
          },
          "sourceRail": {
            "$ref": "#/components/schemas/PaymentRail",
            "description": "The rail the source moves on. Omit to use the source account's default rail."
          },
          "destinationRail": {
            "$ref": "#/components/schemas/PaymentRail",
            "description": "The rail the destination moves on. Omit to use the destination account's default rail."
          },
          "partnerFeeBpsOverride": {
            "type": "integer",
            "minimum": 0,
            "description": "Partner fee for this quote in basis points, in place of your organization's default partner fee. Values above 500 are capped at 500."
          }
        }
      },
      "FeeType": {
        "type": "string",
        "enum": ["transactionFee", "partnerFee", "walletSponsor", "bridge"]
      },
      "Fee": {
        "type": "object",
        "required": ["type", "amount", "asset", "bps"],
        "properties": {
          "type": { "$ref": "#/components/schemas/FeeType" },
          "amount": { "type": "string", "description": "Decimal string." },
          "asset": { "type": "string", "description": "The asset the fee is charged in." },
          "bps": { "type": ["integer", "null"], "description": "The fee rate in basis points, when the fee has one. Always null on a quote." }
        }
      },
      "Quote": {
        "type": "object",
        "required": [
          "quoteId",
          "sourceAmount",
          "destinationAmount",
          "sourceAsset",
          "destinationAsset",
          "sourceRail",
          "destinationRail",
          "rateAfterFees",
          "rateBeforeFees",
          "feeBps",
          "feeAmount",
          "fees",
          "createdAt",
          "expiresAt"
        ],
        "properties": {
          "quoteId": { "type": "string", "format": "uuid", "description": "Pass to `POST /organization/{organizationId}/order`. Becomes the order's `id`." },
          "sourceAmount": { "type": "string", "description": "Decimal string." },
          "destinationAmount": { "type": "string", "description": "Decimal string. What the destination receives after fees. On an onramp into a stablebond, we price the bond at the end of the order's funding window, so this is slightly less than today's price gives. See [Fees](/v2/fees)." },
          "sourceAsset": { "type": "string" },
          "destinationAsset": { "type": "string" },
          "sourceRail": { "$ref": "#/components/schemas/PaymentRail" },
          "destinationRail": { "$ref": "#/components/schemas/PaymentRail" },
          "rateAfterFees": { "type": "string", "description": "The rate the customer gets after fees, as a decimal string." },
          "rateBeforeFees": { "type": "string", "description": "The rate before our fee, as a decimal string." },
          "midMarketRate": { "type": "string", "description": "The mid-market FX rate, as a decimal string. Present only on quotes that convert between currencies." },
          "feeBps": { "type": "integer", "description": "Total fee in basis points." },
          "feeAmount": { "type": "string", "description": "Total fee as a decimal string, in the source asset, rounded down to that asset's decimals. Each line in `fees` is in its own `asset`, so the lines do not add up to this total when their assets differ." },
          "fees": {
            "type": "array",
            "description": "The fee lines. The order created from this quote returns the same lines.",
            "items": { "$ref": "#/components/schemas/Fee" }
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "expiresAt": { "type": "string", "format": "date-time", "description": "Create the order before this time. 2 minutes after `createdAt`." }
        }
      },
      "CreateOrderRequest": {
        "type": "object",
        "required": ["quoteId"],
        "properties": {
          "quoteId": { "type": "string", "format": "uuid" },
          "memo": { "type": "string", "maxLength": 255, "description": "Your note for the order. Up to 255 characters; longer returns `400`." }
        }
      },
      "OrderStatus": {
        "type": "string",
        "enum": ["created", "funded", "claimable", "completed", "failed", "canceled", "refunded"],
        "description": "`created`: waiting for the source funds. `funded`: source funds received. `claimable`: delivered, waiting for the customer to claim; see `claimInstructions`. `completed`: delivered. `failed`: could not be completed; see `failure`. `canceled`: canceled before completion. `refunded`: the source funds were returned."
      },
      "OrderFailure": {
        "type": "object",
        "required": ["code", "message"],
        "properties": {
          "code": { "type": "string", "enum": ["fulfillmentFailed"] },
          "message": { "type": "string", "example": "The order could not be completed." }
        }
      },
      "OrderAmount": {
        "type": "object",
        "required": ["paymentAccountId", "amount", "asset", "rail", "accountLabel"],
        "properties": {
          "paymentAccountId": { "type": "string", "format": "uuid" },
          "amount": { "type": "string", "description": "Decimal string." },
          "asset": { "type": "string" },
          "rail": { "$ref": "#/components/schemas/PaymentRail" },
          "accountLabel": { "type": "string", "description": "Your organization's label for the account." }
        }
      },
      "FundingInstructions": {
        "type": "object",
        "description": "How the source funds the order.",
        "required": ["rail", "reference", "fundingDeadline"],
        "properties": {
          "rail": { "$ref": "#/components/schemas/PaymentRail" },
          "reference": { "type": "string", "description": "Etherfuse's reference for this order's funding. Set when the order is created.", "example": "1000042" },
          "destination": {
            "$ref": "#/components/schemas/FundingDestination",
            "description": "Where to send the funds. Absent until the funding details are available."
          },
          "fundingDeadline": { "type": "string", "format": "date-time", "description": "Fund the order before this time, 25 minutes after the order is created. An order with no funds by then moves to `canceled`, and we send `orderUpdated`." }
        }
      },
      "FundingDestination": {
        "description": "Where to send the funds. The fields follow `fundingInstructions.rail`: `spei` gives a CLABE, `pix` gives a BR Code, and a chain gives an address. When the source is an embedded wallet there is nowhere to send funds: the destination names the order's `authorization`, and we move the funds once the holder approves it.",
        "oneOf": [
          { "$ref": "#/components/schemas/SpeiFundingDestination" },
          { "$ref": "#/components/schemas/PixFundingDestination" },
          { "$ref": "#/components/schemas/BlockchainFundingDestination" },
          { "$ref": "#/components/schemas/AuthorizationFundingDestination" }
        ]
      },
      "AuthorizationFundingDestination": {
        "type": "object",
        "title": "authorization",
        "required": ["expiresAt", "authorizationId"],
        "properties": {
          "expiresAt": { "type": ["string", "null"], "format": "date-time", "description": "Always null. The authorization carries its own `expiresAt`." },
          "authorizationId": { "type": "string", "format": "uuid", "description": "The order `authorization` to approve." }
        }
      },
      "OrderAuthorization": {
        "type": "object",
        "description": "A transfer we built that waits for the account holder's approval. One is pending at a time. When it expires we replace it with a new one, with a new `id`, and send `orderUpdated`.",
        "required": ["id", "reason", "method", "expiresAt"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "reason": {
            "type": "string",
            "enum": ["funding", "claim"],
            "description": "`funding`: we move the source funds from an embedded wallet once approved. `claim`: approving accepts the funds of a `claimable` order into an embedded wallet."
          },
          "method": { "$ref": "#/components/schemas/AuthorizationMethod" },
          "expiresAt": { "type": "string", "format": "date-time", "description": "Approve before this time." }
        },
        "example": {
          "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
          "reason": "funding",
          "method": { "kind": "signature", "algorithm": "es256", "message": "eyJvcmRlcklkIjoiNWU2ZjdhOGIifQ" },
          "expiresAt": "2026-09-28T17:20:00Z"
        }
      },
      "AuthorizationMethod": {
        "description": "How the holder approves, tagged by `kind`. `signature` is the only kind today.",
        "oneOf": [{ "$ref": "#/components/schemas/SignatureAuthorizationMethod" }],
        "discriminator": {
          "propertyName": "kind",
          "mapping": { "signature": "#/components/schemas/SignatureAuthorizationMethod" }
        }
      },
      "SignatureAuthorizationMethod": {
        "type": "object",
        "title": "signature",
        "required": ["kind", "algorithm", "message"],
        "properties": {
          "kind": { "type": "string", "const": "signature" },
          "algorithm": { "type": "string", "const": "es256", "description": "ECDSA over P-256 with SHA-256 (the JOSE `ES256` algorithm), using the key registered as `signerPublicKey`. We accept the signature as DER or as the 64-byte `r||s` form, hex-encoded." },
          "message": { "type": "string", "description": "Sign the UTF-8 bytes of this string." }
        }
      },
      "SubmitAuthorizationRequest": {
        "type": "object",
        "required": ["id", "signature"],
        "properties": {
          "id": { "type": "string", "format": "uuid", "description": "The `authorization.id` you signed." },
          "signature": {
            "type": "string",
            "description": "The hex-encoded signature over `method.message`: DER, or the 128-character `r||s` form WebCrypto produces."
          }
        }
      },
      "SpeiFundingDestination": {
        "type": "object",
        "title": "spei",
        "required": ["expiresAt", "clabe", "bankName", "recipientName"],
        "properties": {
          "expiresAt": { "type": ["string", "null"], "format": "date-time", "description": "When this destination stops accepting funds, or null." },
          "clabe": { "type": "string", "example": "646180000000000009" },
          "bankName": { "type": "string" },
          "recipientName": { "type": "string" }
        }
      },
      "PixFundingDestination": {
        "type": "object",
        "title": "pix",
        "required": ["expiresAt", "brCode"],
        "properties": {
          "expiresAt": { "type": ["string", "null"], "format": "date-time", "description": "When this destination stops accepting funds, or null." },
          "brCode": { "type": "string", "description": "The PIX BR Code to pay." }
        }
      },
      "BlockchainFundingDestination": {
        "type": "object",
        "title": "blockchain",
        "required": ["expiresAt", "address"],
        "properties": {
          "expiresAt": { "type": ["string", "null"], "format": "date-time", "description": "When this destination stops accepting funds, or null." },
          "address": { "type": "string", "description": "The address to send to." },
          "memo": { "type": "string", "description": "The memo to include. Absent when none is needed." },
          "preparedTransaction": { "$ref": "#/components/schemas/PreparedTransaction" }
        }
      },
      "PreparedTransaction": {
        "type": "object",
        "description": "A transaction we built for the customer to sign and submit. Absent when there is none.",
        "required": ["transaction", "refreshAfter"],
        "properties": {
          "transaction": { "type": "string", "description": "The unsigned transaction, for the customer to sign and submit. Solana: base64 of a v0 transaction in wire format. Stellar: base64 transaction envelope XDR. EVM chains: `0x`-prefixed hex of an EIP-1559 transaction. The customer's wallet pays the network fee." },
          "refreshAfter": { "type": ["string", "null"], "format": "date-time", "description": "After this time the transaction can no longer land. On Solana it is about 60 seconds after we build it, when the blockhash expires. On Stellar it is the order's funding deadline. Null on EVM chains, where the transaction has no time limit. A Stellar or EVM transaction also goes stale when the wallet's sequence number or nonce moves." }
        }
      },
      "ClaimInstructions": {
        "description": "What the customer needs to accept the funds while the order is `claimable`, tagged by `kind`. Prepare the claim with `POST /organization/{organizationId}/order/{id}/claim`.",
        "oneOf": [{ "$ref": "#/components/schemas/StellarClaimableBalance" }],
        "discriminator": {
          "propertyName": "kind",
          "mapping": { "stellarClaimableBalance": "#/components/schemas/StellarClaimableBalance" }
        }
      },
      "StellarClaimableBalance": {
        "type": "object",
        "title": "stellarClaimableBalance",
        "required": ["kind", "balanceId", "asset", "amount", "claimant", "expiresAt"],
        "properties": {
          "kind": { "type": "string", "const": "stellarClaimableBalance" },
          "balanceId": { "type": "string", "description": "The Stellar claimable balance ID." },
          "asset": { "type": "string" },
          "amount": { "type": "string", "description": "Decimal string." },
          "claimant": { "type": "string", "description": "The Stellar account that can claim the balance." },
          "expiresAt": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "Order": {
        "type": "object",
        "required": [
          "id",
          "organizationId",
          "status",
          "memo",
          "source",
          "destination",
          "fees",
          "fundingInstructions",
          "claimInstructions",
          "createdAt",
          "updatedAt",
          "completedAt"
        ],
        "properties": {
          "id": { "type": "string", "format": "uuid", "description": "The order id. Equal to the `quoteId` it was created from." },
          "organizationId": { "type": "string", "format": "uuid", "description": "The organization the order belongs to." },
          "failure": { "$ref": "#/components/schemas/OrderFailure", "description": "Present only when `status` is `failed`." },
          "status": { "$ref": "#/components/schemas/OrderStatus" },
          "memo": { "type": ["string", "null"] },
          "source": { "$ref": "#/components/schemas/OrderAmount" },
          "destination": { "$ref": "#/components/schemas/OrderAmount" },
          "fees": { "type": "array", "items": { "$ref": "#/components/schemas/Fee" } },
          "fundingInstructions": { "$ref": "#/components/schemas/FundingInstructions" },
          "claimInstructions": {
            "description": "Set while the order is `claimable`. Otherwise null.",
            "anyOf": [{ "$ref": "#/components/schemas/ClaimInstructions" }, { "type": "null" }]
          },
          "authorization": {
            "description": "Set while the order waits for the account holder to approve a transfer we built. Otherwise null. Approve it with `POST /organization/{organizationId}/order/{id}/authorization`.",
            "anyOf": [{ "$ref": "#/components/schemas/OrderAuthorization" }, { "type": "null" }]
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "completedAt": { "type": ["string", "null"], "format": "date-time" }
        },
        "example": {
          "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
          "organizationId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
          "status": "created",
          "memo": "Invoice 1042",
          "source": {
            "paymentAccountId": "3f9a2c1e-5b7d-4e8f-9a01-2b3c4d5e6f70",
            "amount": "1000",
            "asset": "MXN",
            "rail": "spei",
            "accountLabel": "Operating account"
          },
          "destination": {
            "paymentAccountId": "8c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
            "amount": "54.120000",
            "asset": "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
            "rail": "stellar",
            "accountLabel": "Treasury wallet"
          },
          "fees": [
            { "type": "transactionFee", "amount": "2.50", "asset": "MXN", "bps": 25 }
          ],
          "fundingInstructions": {
            "rail": "spei",
            "reference": "1000042",
            "destination": {
              "expiresAt": null,
              "clabe": "646180000000000009",
              "bankName": "Ejemplo Banco",
              "recipientName": "Ejemplo Receptor"
            },
            "fundingDeadline": "2026-09-29T17:05:02Z"
          },
          "claimInstructions": null,
          "createdAt": "2026-09-28T17:05:02.114Z",
          "updatedAt": "2026-09-28T17:05:02.114Z",
          "completedAt": null
        }
      },
      "OrderPage": {
        "type": "object",
        "required": ["items", "totalItems", "pageSize", "pageNumber", "totalPages"],
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/Order" } },
          "totalItems": { "type": "integer" },
          "pageSize": { "type": "integer" },
          "pageNumber": { "type": "integer" },
          "totalPages": { "type": "integer" }
        }
      },
      "ClaimResponse": {
        "description": "What the customer does next to claim, tagged by `kind`.",
        "oneOf": [
          { "$ref": "#/components/schemas/StellarClaimTransaction" },
          { "$ref": "#/components/schemas/AuthorizationClaim" }
        ],
        "discriminator": {
          "propertyName": "kind",
          "mapping": {
            "stellarTransaction": "#/components/schemas/StellarClaimTransaction",
            "authorization": "#/components/schemas/AuthorizationClaim"
          }
        }
      },
      "StellarClaimTransaction": {
        "type": "object",
        "title": "stellarTransaction",
        "required": ["kind", "xdr"],
        "properties": {
          "kind": { "type": "string", "const": "stellarTransaction" },
          "xdr": { "type": "string", "description": "The transaction for a self-held wallet to sign and submit." }
        }
      },
      "AuthorizationClaim": {
        "type": "object",
        "title": "authorization",
        "required": ["kind", "authorizationId"],
        "properties": {
          "kind": { "type": "string", "const": "authorization" },
          "authorizationId": { "type": "string", "format": "uuid", "description": "The order `authorization` an embedded wallet's holder approves to claim." }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": ["orderUpdated", "paymentAccountUpdated", "kycUpdated", "kybUpdated"]
      },
      "CreateWebhookSubscriptionRequest": {
        "type": "object",
        "required": ["url", "eventType"],
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "Where we send the event. Must use `https` in production." },
          "eventType": { "$ref": "#/components/schemas/WebhookEventType" }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "required": ["id", "eventType", "url", "createdAt", "updatedAt"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "eventType": { "$ref": "#/components/schemas/WebhookEventType" },
          "url": { "type": "string" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "CreatedWebhookSubscription": {
        "type": "object",
        "required": ["id", "eventType", "url", "createdAt", "updatedAt", "secret"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "eventType": { "$ref": "#/components/schemas/WebhookEventType" },
          "url": { "type": "string" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "secret": { "type": "string", "description": "HMAC key for verifying deliveries, encoded as URL-safe base64 with padding. Returned only in this response." }
        }
      },
      "WebhookSubscriptionList": {
        "type": "object",
        "required": ["items"],
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookSubscription" } }
        }
      },
      "OrderUpdatedWebhook": {
        "type": "object",
        "required": ["event", "eventId", "occurredAt", "order"],
        "properties": {
          "event": { "type": "string", "const": "orderUpdated" },
          "eventId": { "type": "string", "format": "uuid", "description": "Unique per event. Use it to drop duplicate deliveries." },
          "occurredAt": { "type": "string", "format": "date-time" },
          "order": { "$ref": "#/components/schemas/Order" }
        }
      },
      "PaymentAccountUpdatedWebhook": {
        "type": "object",
        "required": ["event", "eventId", "occurredAt", "organizationId", "paymentAccount"],
        "properties": {
          "event": { "type": "string", "const": "paymentAccountUpdated" },
          "eventId": { "type": "string", "format": "uuid", "description": "Unique per event. Use it to drop duplicate deliveries." },
          "occurredAt": { "type": "string", "format": "date-time" },
          "organizationId": { "type": "string", "format": "uuid", "description": "The organization that holds the account." },
          "paymentAccount": { "$ref": "#/components/schemas/PaymentAccount" }
        }
      },
      "SimulateFiatReceivedRequest": {
        "type": "object",
        "required": ["amount"],
        "properties": {
          "amount": { "type": "string", "description": "The simulated deposit amount, as a positive decimal string.", "example": "1000" }
        }
      },
      "Me": {
        "type": "object",
        "required": ["id", "displayName", "approvedAt", "partnerFeeDefaultBps"],
        "properties": {
          "id": { "type": "string", "format": "uuid", "description": "Your organization's id. Send it as `organizationId` when a call acts for your own organization." },
          "displayName": { "type": "string" },
          "approvedAt": { "type": ["string", "null"], "format": "date-time", "description": "When your organization was approved. Null until then." },
          "partnerFeeDefaultBps": { "type": "integer", "minimum": 0, "maximum": 500, "description": "The partner fee, in basis points, quotes add when the request sends no `partnerFeeBpsOverride`." }
        },
        "example": {
          "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "displayName": "Acme Inc.",
          "approvedAt": "2026-04-15T12:00:00Z",
          "partnerFeeDefaultBps": 100
        }
      },
      "UpdateMeRequest": {
        "type": "object",
        "required": ["partnerFeeDefaultBps"],
        "properties": {
          "partnerFeeDefaultBps": { "type": "integer", "minimum": 0, "maximum": 500, "description": "The new default partner fee in basis points, from 0 to 500 (5.00%)." }
        },
        "example": { "partnerFeeDefaultBps": 100 }
      },
      "AccountType": {
        "type": "string",
        "enum": ["personal", "business"],
        "description": "`personal`: an individual who completes KYC. `business`: a company that completes KYB."
      },
      "Organization": {
        "type": "object",
        "required": ["id", "displayName", "accountType", "approvedAt", "createdAt", "updatedAt"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "displayName": { "type": "string" },
          "accountType": { "$ref": "#/components/schemas/AccountType" },
          "approvedAt": { "type": ["string", "null"], "format": "date-time", "description": "When the organization's KYC or KYB was approved. Null until then." },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        },
        "example": {
          "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "displayName": "Ana Ejemplo",
          "accountType": "personal",
          "approvedAt": null,
          "createdAt": "2026-10-01T14:30:00Z",
          "updatedAt": "2026-10-01T14:30:00Z"
        }
      },
      "OrganizationPage": {
        "type": "object",
        "required": ["items", "totalItems", "pageSize", "pageNumber", "totalPages"],
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/Organization" } },
          "totalItems": { "type": "integer" },
          "pageSize": { "type": "integer" },
          "pageNumber": { "type": "integer" },
          "totalPages": { "type": "integer" }
        }
      },
      "UpdateOrganizationRequest": {
        "type": "object",
        "required": ["displayName"],
        "properties": {
          "displayName": { "type": "string", "maxLength": 200, "description": "The new name. Blank names and names over 200 characters return `400`." }
        },
        "example": { "displayName": "Acme Inc." }
      },
      "MemberOrganizationList": {
        "type": "object",
        "required": ["items"],
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/MemberOrganizationRecord" } }
        }
      },
      "Kyc": {
        "type": "object",
        "required": ["organizationId", "status", "currentRejectionReason", "selfies", "documents", "approvedAt", "needsWork", "userMessage"],
        "properties": {
          "organizationId": { "type": "string", "format": "uuid" },
          "status": { "$ref": "#/components/schemas/VerificationStatus" },
          "currentRejectionReason": { "type": ["string", "null"], "description": "Why the last review rejected the customer, when it did. Visible only to the partner that submitted the data." },
          "selfies": { "type": "array", "items": { "$ref": "#/components/schemas/ProofOfIdentityContent" } },
          "documents": { "type": "array", "items": { "$ref": "#/components/schemas/ProofOfIdentityDocument" } },
          "approvedAt": { "type": ["string", "null"], "format": "date-time" },
          "needsWork": { "type": "boolean", "description": "True when the customer has to correct something and resubmit." },
          "userMessage": { "type": ["string", "null"], "description": "What the customer has to fix, when `needsWork` is true." },
          "requirements": {
            "type": "array",
            "description": "What the customer's verification still needs, step by step. Present only with `?requirements=true`, and only once the customer has been routed to their country's requirements.",
            "items": { "$ref": "#/components/schemas/RequirementState" }
          }
        },
        "example": {
          "organizationId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "status": "inProgress",
          "currentRejectionReason": null,
          "selfies": [],
          "documents": [],
          "approvedAt": null,
          "needsWork": false,
          "userMessage": null,
          "requirements": [
            { "type": "personalData", "status": "satisfied", "requiresLaunch": false },
            { "type": "identityDocument", "status": "pending", "requiresLaunch": false },
            { "type": "selfie", "status": "pending", "requiresLaunch": true }
          ]
        }
      },
      "Kyb": {
        "type": "object",
        "required": ["organizationId", "status", "approvedAt"],
        "properties": {
          "organizationId": { "type": "string", "format": "uuid" },
          "status": { "$ref": "#/components/schemas/VerificationStatus" },
          "approvedAt": { "type": ["string", "null"], "format": "date-time", "description": "When the business was approved. Null until then." }
        },
        "example": {
          "organizationId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "status": "submitted",
          "approvedAt": null
        }
      },
      "PaymentAccountBalances": {
        "type": "object",
        "required": ["paymentAccountId", "chain", "address", "observedAt", "balances"],
        "properties": {
          "paymentAccountId": { "type": "string", "format": "uuid" },
          "chain": { "$ref": "#/components/schemas/Chain" },
          "address": { "type": "string", "description": "The address we read." },
          "observedAt": { "type": "string", "format": "date-time", "description": "When we read the balances." },
          "balances": {
            "type": "array",
            "description": "One entry per held asset. Empty when the wallet holds none.",
            "items": { "$ref": "#/components/schemas/WalletBalance" }
          }
        },
        "example": {
          "paymentAccountId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
          "chain": "solana",
          "address": "3XnhVzKqNTz2wpNjyhbCsSFvz4Y6VNSVaVNhFTNGmqxr",
          "observedAt": "2026-10-07T12:00:00Z",
          "balances": [
            {
              "symbol": "cetes",
              "identifier": "CETES7CKqqKQizuSN6iWQwmTeFRjbJR6Vw2XRKfEDR8f",
              "balance": "1500.123456",
              "currency": "mxn",
              "amountInFiat": "1932.551274",
              "amountInUsd": "105.214877"
            }
          ]
        }
      },
      "PartnerStatementList": {
        "type": "object",
        "required": ["items"],
        "properties": {
          "items": {
            "type": "array",
            "description": "Closed and paid months, newest first.",
            "items": { "$ref": "#/components/schemas/PartnerStatementEntry" }
          }
        },
        "example": {
          "items": [
            {
              "periodYear": 2026,
              "periodMonth": 6,
              "status": "closed",
              "closedAt": "2026-07-04T17:42:10.427124+00:00",
              "paidAt": null,
              "payoutTotal": null,
              "payoutCurrency": null
            },
            {
              "periodYear": 2026,
              "periodMonth": 5,
              "status": "paid",
              "closedAt": "2026-06-04T17:42:10.427124+00:00",
              "paidAt": "2026-06-09T17:42:10.427124+00:00",
              "payoutTotal": "1543.22",
              "payoutCurrency": "USDC"
            }
          ]
        }
      },
      "SupportedAssets": {
        "type": "object",
        "required": ["markets", "tokens"],
        "properties": {
          "markets": { "type": "array", "items": { "$ref": "#/components/schemas/MarketAsset" } },
          "tokens": { "type": "array", "items": { "$ref": "#/components/schemas/TokenAsset" } }
        },
        "example": {
          "markets": [
            { "asset": "MXN", "country": "MX", "currency": "MXN", "rails": ["spei"] }
          ],
          "tokens": [
            {
              "asset": "CETES:GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4",
              "chain": "stellar",
              "currency": "MXN",
              "symbol": "CETES",
              "name": "Etherfuse CETES",
              "image": "https://assets.etherfuse.com/cetes.png"
            },
            {
              "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
              "chain": "solana",
              "currency": "USD",
              "symbol": "USDC",
              "name": "USD Coin",
              "image": null
            }
          ]
        }
      },
      "MarketAsset": {
        "type": "object",
        "description": "A currency in a country, and the rails that move it.",
        "required": ["asset", "country", "currency", "rails"],
        "properties": {
          "asset": { "type": "string", "description": "The value to send as `sourceAsset` or `destinationAsset`: the currency code." },
          "country": { "type": "string", "description": "ISO 3166-1 alpha-2 country code." },
          "currency": { "type": "string", "description": "ISO 4217 currency code." },
          "rails": { "type": "array", "items": { "$ref": "#/components/schemas/PaymentRail" }, "description": "The rails that move this currency in this country." }
        }
      },
      "TokenAsset": {
        "type": "object",
        "description": "A stablebond or stablecoin on one chain.",
        "required": ["asset", "chain", "currency", "symbol", "name", "image"],
        "properties": {
          "asset": { "type": "string", "description": "The value to send as `sourceAsset` or `destinationAsset`: the mint on Solana, the contract on EVM chains, `CODE:ISSUER` on Stellar." },
          "chain": { "$ref": "#/components/schemas/Chain" },
          "currency": { "type": ["string", "null"], "description": "The fiat currency the token is backed by, as an ISO 4217 code." },
          "symbol": { "type": "string" },
          "name": { "type": "string" },
          "image": { "type": ["string", "null"], "format": "uri" }
        }
      },
      "AddMemberRequest": {
        "type": "object",
        "required": [
          "role"
        ],
        "properties": {
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Invite or add a member by email address. If the email matches a user who has already signed in through your JWT, they're added as a member immediately; otherwise a pending invite is created and accepted automatically the first time that email signs in via your `kyb` JWT. Use `email` when you don't have the person's `memberId` yet, or when you want Etherfuse to email them an invite (see `emailInviteLink`). Send exactly one of `email` or `memberId`."
          },
          "memberId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Add someone directly by their `sub` (the value you put in the JWT `sub` when they sign in); no email is sent. The person must already exist, which happens the first time they exchange a JWT via [POST /auth/token](/v2/api-reference/authentication/exchange-a-partner-jwt) (or are launched); an unknown `sub` returns 404. A business organization's id is rejected. Send exactly one of `email` or `memberId`."
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "member"
            ],
            "description": "The member's role in the organization."
          },
          "emailInviteLink": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional. When adding by `email` and a pending invite is created (the email isn't already a member), Etherfuse emails them this URL **as-is** (you control the full link, including any query params) as the call to action. Omit it to create the invite silently and notify the user yourself. Ignored when the email already matches a member who is added immediately."
          }
        },
        "example": {
          "email": "alice@acme.com",
          "role": "admin",
          "emailInviteLink": "https://partner.example.com/kyb-invite?org=a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        }
      },
      "AuthErrorResponse": {
        "type": "object",
        "description": "OAuth 2.0 error response ([RFC 6749 §5.2](https://www.rfc-editor.org/rfc/rfc6749#section-5.2)).",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "invalid_scope",
              "unauthorized_client",
              "unsupported_grant_type",
              "server_error"
            ]
          },
          "error_description": {
            "type": "string"
          },
          "error_uri": {
            "type": "string"
          }
        }
      },
      "AuthTokenRequest": {
        "type": "object",
        "required": [
          "grant_type"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "urn:ietf:params:oauth:grant-type:jwt-bearer",
              "refresh_token"
            ],
            "description": "The OAuth 2.0 grant type."
          },
          "assertion": {
            "type": "string",
            "description": "The partner-signed JWT. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:jwt-bearer`."
          },
          "refresh_token": {
            "type": "string",
            "description": "A refresh token from a prior exchange. Required when `grant_type` is `refresh_token`."
          }
        },
        "example": {
          "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
          "assertion": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imt..."
        }
      },
      "AuthTokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "refresh_token"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Bearer token for the Ramp API. Send it as `Authorization: Bearer <access_token>`."
          },
          "token_type": {
            "type": "string",
            "example": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Access token lifetime, in seconds.",
            "example": 3600
          },
          "refresh_token": {
            "type": "string",
            "description": "Use with the `refresh_token` grant to obtain a fresh access token without re-signing a JWT."
          },
          "scope": {
            "type": "string",
            "description": "The granted scope, echoed from the JWT's `scope` claim. Omitted for unrestricted (non-partner) sessions."
          }
        }
      },
      "BlockchainSupplyCachable": {
        "type": "object",
        "description": "Per-chain supply breakdown. Uses string blockchain identifier\n(same pattern as BridgeConfig; the API converts it to a typed enum at the boundary).",
        "required": [
          "blockchain",
          "tokenIdentifier",
          "totalSupply"
        ],
        "properties": {
          "blockchain": {
            "type": "string",
            "description": "Chain identifier (e.g. `solana`, `stellar`)."
          },
          "notCountedVaultedAmount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Supply held in vaults and excluded from circulating totals, if any."
          },
          "tokenIdentifier": {
            "type": "string",
            "description": "Token identifier on this chain (mint address or asset code)."
          },
          "totalSupply": {
            "type": "string",
            "description": "Total supply on this chain."
          }
        }
      },
      "Country": {
        "type": "object",
        "description": "A known country with its ISO 3166-1 alpha-2 code and common English name.\nThis list is focused on Plaid-supported countries and countries relevant to\nour geo-blocking rules. Plaid returns uppercase alpha-2 codes, but manual\nadmin submissions may use full names or mixed case.",
        "required": [
          "code",
          "code3",
          "name"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 code (e.g. `MX`)."
          },
          "code3": {
            "type": "string",
            "description": "ISO 3166-1 alpha-3 code (e.g. `MEX`)."
          },
          "name": {
            "type": "string",
            "description": "Common English name (e.g. `Mexico`)."
          }
        },
        "example": {
          "code": "MX",
          "code3": "MEX",
          "name": "Mexico"
        }
      },
      "CreateOrganizationRequest": {
        "type": "object",
        "properties": {
          "accountType": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "personal",
              "business"
            ],
            "description": "Whether this org is an individual or a company:\n\n- `personal`: an individual customer completing KYC. Send `userInfo`.\n- `business`: a company completing KYB. `userInfo` is rejected.\n\nOptional today and defaults to `personal`. It will become required, so send it explicitly now.",
            "example": "personal"
          },
          "displayName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The organization's display name: the friendly label shown in UIs and API responses. For a `business` org completing KYB, the legal entity name is captured separately via `legalName`."
          },
          "id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "userInfo": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/UserInfo",
                "description": "Optional info about the end user. Required eventually for personal\norgs. Rejected on business orgs (those don't have a single end user)."
              }
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 3166-1 alpha-2 country code (e.g. `MX`). Business orgs only; rejected on personal orgs. When provided, Etherfuse generates the KYB onboarding at creation, seeding the company entity with this country so the user lands straight in KYB. Omit it and the user is prompted for their country when they begin KYB.",
            "example": "MX"
          },
          "legalName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Legal entity name for the KYB company entity (e.g. the incorporated name on the company's registry documents). Pass it together with `country` when generating the KYB onboarding at creation; rejected without `country`. Independent of `displayName`: renaming the org does not change the legal name, and vice versa.",
            "example": "Acme Corporation S.A. de C.V."
          }
        },
        "example": {
          "accountType": "personal",
          "displayName": "Ana Ejemplo",
          "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "userInfo": {
            "displayName": "Ana Ejemplo",
            "email": "ana@example.com"
          }
        }
      },
      "ImageInfo": {
        "type": "object",
        "required": [
          "imageURL",
          "label"
        ],
        "properties": {
          "imageURL": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        }
      },
      "ImportVerificationRequest": {
        "type": "object",
        "description": "A verification session the partner already completed with the same provider, handed over as a share token they created for us. No other field is accepted.",
        "required": [
          "shareToken"
        ],
        "properties": {
          "shareToken": {
            "type": "string",
            "description": "The single-use, short-lived share token created for the verified applicant, targeting Etherfuse's client id.",
            "example": "_act-sbx-jwt-eyJhbGciOi..."
          }
        }
      },
      "ImportVerificationResponse": {
        "type": "object",
        "description": "The outcome of importing a verification session.",
        "required": [
          "organizationId",
          "status",
          "requirements"
        ],
        "properties": {
          "organizationId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/VerificationStatus"
          },
          "requirements": {
            "type": "array",
            "description": "What is still outstanding after the import: the same breakdown `GET /organization/{organizationId}/kyc?requirements=true` returns.",
            "items": {
              "$ref": "#/components/schemas/RequirementState"
            }
          }
        },
        "example": {
          "organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "status": "inProgress",
          "requirements": [
            {
              "type": "personalData",
              "status": "satisfied",
              "requiresLaunch": false
            },
            {
              "type": "identityDocument",
              "status": "satisfied",
              "requiresLaunch": false
            },
            {
              "type": "occupation",
              "status": "pending",
              "requiresLaunch": false
            },
            {
              "type": "selfie",
              "status": "satisfied",
              "requiresLaunch": true
            },
            {
              "type": "customerAgreement",
              "status": "pending",
              "requiresLaunch": false
            }
          ]
        }
      },
      "InvitedRecord": {
        "type": "object",
        "required": [
          "email",
          "role",
          "invitedAt"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "member"
            ]
          },
          "invitedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VerificationStatus": {
        "type": "string",
        "enum": ["notStarted", "inProgress", "submitted", "approved", "denied"],
        "description": "- `notStarted`: verification has not started.\n- `inProgress`: the customer started but has not submitted everything.\n- `submitted`: submitted and waiting for review.\n- `approved`: cleared.\n- `denied`: rejected, or a previous approval was revoked."
      },
      "KycUpdatedWebhook": {
        "type": "object",
        "required": ["event", "eventId", "occurredAt", "organizationId", "kyc"],
        "properties": {
          "event": { "type": "string", "const": "kycUpdated" },
          "eventId": { "type": "string", "format": "uuid", "description": "Unique per event. Use it to drop duplicate deliveries." },
          "occurredAt": { "type": "string", "format": "date-time" },
          "organizationId": { "type": "string", "format": "uuid", "description": "The customer organization whose KYC changed." },
          "kyc": {
            "type": "object",
            "required": ["status", "approved", "needsWork", "userMessage"],
            "properties": {
              "status": { "$ref": "#/components/schemas/VerificationStatus" },
              "approved": { "type": "boolean", "description": "True when the customer can transact. It stays true while an approved customer re-verifies, so `status` can show `inProgress` or `submitted` at the same time." },
              "needsWork": { "type": "boolean", "description": "True when the customer has to correct something and resubmit." },
              "userMessage": { "type": ["string", "null"], "description": "What the customer has to fix, when `needsWork` is true. Otherwise null." }
            }
          }
        },
        "example": {
          "event": "kycUpdated",
          "eventId": "0d6f1a2b-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
          "occurredAt": "2026-09-29T19:00:00Z",
          "organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "kyc": {
            "status": "inProgress",
            "approved": false,
            "needsWork": true,
            "userMessage": "Please upload a current government-issued ID."
          }
        }
      },
      "KybUpdatedWebhook": {
        "type": "object",
        "required": ["event", "eventId", "occurredAt", "organizationId", "kyb"],
        "properties": {
          "event": { "type": "string", "const": "kybUpdated" },
          "eventId": { "type": "string", "format": "uuid", "description": "Unique per event. Use it to drop duplicate deliveries." },
          "occurredAt": { "type": "string", "format": "date-time" },
          "organizationId": { "type": "string", "format": "uuid", "description": "The business organization whose KYB changed." },
          "kyb": {
            "type": "object",
            "required": ["status", "approvedAt"],
            "properties": {
              "status": { "$ref": "#/components/schemas/VerificationStatus" },
              "approvedAt": { "type": ["string", "null"], "format": "date-time", "description": "When the business was approved. Null until then." }
            }
          }
        },
        "example": {
          "event": "kybUpdated",
          "eventId": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
          "occurredAt": "2026-09-29T19:00:00Z",
          "organizationId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "kyb": { "status": "approved", "approvedAt": "2026-09-29T19:00:00Z" }
        }
      },
      "MemberOrganizationRecord": {
        "type": "object",
        "description": "One organization a customer belongs to, with their role in it.",
        "required": [
          "organizationId",
          "displayName",
          "accountType",
          "role",
          "joinedAt"
        ],
        "properties": {
          "organizationId": {
            "type": "string",
            "format": "uuid",
            "description": "The organization's id."
          },
          "displayName": {
            "type": "string",
            "description": "The organization's name."
          },
          "accountType": {
            "type": "string",
            "enum": [
              "personal",
              "business"
            ],
            "description": "Whether the organization completes KYC (`personal`) or KYB (`business`)."
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "member"
            ],
            "description": "The customer's role in this organization."
          },
          "joinedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the customer joined this organization."
          }
        },
        "example": {
          "organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "displayName": "Acme Inc.",
          "accountType": "business",
          "role": "admin",
          "joinedAt": "2026-06-17T00:00:00Z"
        }
      },
      "MemberRecord": {
        "type": "object",
        "required": [
          "memberId",
          "role",
          "joinedAt"
        ],
        "properties": {
          "memberId": {
            "type": "string",
            "description": "The member's `sub`. Empty if the member does not yet have a `sub` in your namespace."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member's email, if known."
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "member"
            ]
          },
          "joinedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MembersResponse": {
        "type": "object",
        "required": [
          "members",
          "invited"
        ],
        "properties": {
          "members": {
            "type": "array",
            "description": "Active members. The org's own service account is never listed.",
            "items": {
              "$ref": "#/components/schemas/MemberRecord"
            }
          },
          "invited": {
            "type": "array",
            "description": "Pending invites that have not been accepted yet.",
            "items": {
              "$ref": "#/components/schemas/InvitedRecord"
            }
          }
        },
        "example": {
          "members": [
            {
              "memberId": "9f1c4b2a-7d3e-4c5f-8a6b-1e2d3c4b5a6f",
              "email": "alice@acme.com",
              "role": "admin",
              "joinedAt": "2026-06-17T00:00:00Z"
            }
          ],
          "invited": [
            {
              "email": "bob@acme.com",
              "role": "member",
              "invitedAt": "2026-06-17T00:00:00Z"
            }
          ]
        }
      },
      "PartnerStatementEntry": {
        "type": "object",
        "description": "One closed month of revenue share. Open months are never returned.",
        "required": [
          "periodYear",
          "periodMonth",
          "status",
          "closedAt",
          "paidAt",
          "payoutTotal",
          "payoutCurrency"
        ],
        "properties": {
          "periodYear": {
            "type": "integer",
            "description": "Four digit statement year."
          },
          "periodMonth": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "Statement month, 1 through 12, not zero padded."
          },
          "status": {
            "type": "string",
            "enum": [
              "closed",
              "paid"
            ],
            "description": "`closed` once the month is frozen, `paid` once a payout is recorded. Open months are not listed. Treat as an open set; new values may be added."
          },
          "closedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the month was frozen, RFC 3339."
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the payout was recorded, RFC 3339. Null until then."
          },
          "payoutTotal": {
            "type": "string",
            "nullable": true,
            "description": "Amount paid out. A decimal string with two fractional digits, truncated toward zero, matching the CSV and PDF. Null until a payout is recorded, and for a month that settled at zero."
          },
          "payoutCurrency": {
            "type": "string",
            "enum": [
              "USDC",
              "USDT",
              "USD",
              "MXN"
            ],
            "nullable": true,
            "description": "Currency the payout settled in. Null until a payout is recorded."
          }
        }
      },
      "ProofOfIdentityAddress": {
        "type": "object",
        "required": [
          "country"
        ],
        "properties": {
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "country": {
            "type": "string"
          },
          "postalCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "street": {
            "type": [
              "string",
              "null"
            ]
          },
          "street2": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ProofOfIdentityContent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "status"
        ],
        "properties": {
          "approvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "contentLabel": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "images": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/ImageInfo"
            }
          },
          "ipAddress": {
            "type": [
              "string",
              "null"
            ]
          },
          "rejectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "rejectedReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ProofOfIdentityStatus"
          },
          "type": {
            "type": "string"
          },
          "videoURL": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ProofOfIdentityDocument": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProofOfIdentityContent"
          },
          {
            "type": "object",
            "properties": {
              "extractedData": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/ProofOfIdentityUserInfo"
                  }
                ]
              }
            }
          }
        ]
      },
      "ProofOfIdentityIdNumber": {
        "type": "object",
        "required": [
          "value",
          "type"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        }
      },
      "ProofOfIdentityName": {
        "type": "object",
        "required": [
          "givenName",
          "familyName"
        ],
        "properties": {
          "familyName": {
            "type": "string"
          },
          "givenName": {
            "type": "string"
          },
          "middleName": {
            "type": [
              "string",
              "null"
            ]
          },
          "motherMaidenName": {
            "type": [
              "string",
              "null"
            ]
          },
          "preferredName": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ProofOfIdentityStatus": {
        "type": "string",
        "enum": [
          "approved",
          "rejected",
          "proposed"
        ]
      },
      "ProofOfIdentityUserInfo": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "address": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ProofOfIdentityAddress"
              }
            ]
          },
          "dateOfBirth": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "idNumbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProofOfIdentityIdNumber"
            }
          },
          "name": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ProofOfIdentityName"
              }
            ]
          },
          "occupation": {
            "type": [
              "string",
              "null"
            ]
          },
          "organizationId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "phoneNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "sourceOrganizationId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "useEmailForMarketing": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "walletId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RequirementState": {
        "type": "object",
        "description": "One thing a customer's verification still needs, and where it stands.",
        "required": [
          "type",
          "status",
          "requiresLaunch"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Which requirement this is. Only the ones that apply to this customer are listed; their country decides which those are.\n\n- `personalData`: name, date of birth, tax id, country and address\n- `identityDocument`: a government-issued ID\n- `proofOfAddress`: an address document; appears only when the identity document carried no address\n- `occupation`: job title and occupation category\n- `taxDocument`: a region-specific tax document. In Mexico, the Constancia de Situación Fiscal\n- `emailConfirmation`: the customer confirming their email address\n- `selfie`: a live face capture; cannot be uploaded\n- `customerAgreement`: the customer accepting the required agreements\n\nWe may add requirements, and will update these docs when we do. A country may also introduce one of its own; see [Country-specific requirements](/v2/guides/onboarding#country-specific-requirements).",
            "enum": [
              "personalData",
              "identityDocument",
              "proofOfAddress",
              "occupation",
              "taxDocument",
              "emailConfirmation",
              "selfie",
              "customerAgreement"
            ]
          },
          "status": {
            "type": "string",
            "description": "How far along this requirement is.\n\n- `satisfied`: accepted, nothing more to do\n- `pending`: nothing has been provided for this yet, so it is still owed\n- `awaitingReview`: provided, not yet reviewed\n- `actionRequired`: **reviewed and rejected**, so it must be provided again, corrected\n\nTell `pending` and `awaitingReview` apart: a shared verification whose identity document and selfie transferred reads `awaitingReview` for both, so do not read `pending` into it and send them again. More values may be added, so act on the two that carry an instruction (`satisfied` means done, `actionRequired` means send again) and treat anything else as in progress.",
            "enum": [
              "satisfied",
              "pending",
              "awaitingReview",
              "actionRequired"
            ]
          },
          "requiresLaunch": {
            "type": "boolean",
            "description": "True when only the customer can satisfy this in the hosted step, such as the liveness capture. A property of the requirement rather than of its progress, so it does not change as the customer advances. `selfie` reads true whether or not the selfie has been taken. A requirement with status `satisfied` needs no further action even when this flag is true.\n\nFalse does not mean the requirement is yours to submit. It means the API *can* submit it. The customer can always enter it in the hosted step instead, which is why launching them satisfies everything. So this flag says what the API cannot reach, not when to launch: an outstanding requirement reading true is one no API call will ever clear."
          }
        },
        "example": {
          "type": "identityDocument",
          "status": "pending",
          "requiresLaunch": false
        }
      },
      "RestrictedCountriesResponse": {
        "type": "object",
        "required": [
          "countries"
        ],
        "properties": {
          "countries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RestrictedCountry"
            }
          }
        },
        "example": {
          "countries": [
            {
              "code": "AF",
              "name": "Afghanistan",
              "kyc": true,
              "kyb": true
            }
          ]
        }
      },
      "RestrictedCountry": {
        "type": "object",
        "description": "A country with effective KYC or KYB risk marked as restricted/black.",
        "required": [
          "code",
          "name",
          "kyc",
          "kyb"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code (e.g., `AF`)."
          },
          "name": {
            "type": "string",
            "description": "Common English country name (e.g., `Afghanistan`)."
          },
          "kyc": {
            "type": "boolean",
            "description": "Whether KYC is restricted for this country."
          },
          "kyb": {
            "type": "boolean",
            "description": "Whether KYB is restricted for this country."
          }
        }
      },
      "StablebondInfoCachable": {
        "type": "object",
        "description": "A single stablebond's price, net amounts, and per-chain supply.",
        "required": [
          "name",
          "symbol",
          "netAmountDecimal",
          "netValueDecimal",
          "bondCurrency",
          "tokenPriceDecimal",
          "purchaseOrderAmount",
          "redeemOrderAmount",
          "blockchains",
          "solanaMintAddress"
        ],
        "properties": {
          "blockchains": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BlockchainSupplyCachable"
            },
            "description": "Per-chain supply breakdown."
          },
          "bondCurrency": {
            "type": "string",
            "description": "Bond's settlement currency (ISO 4217)."
          },
          "name": {
            "type": "string",
            "description": "Bond name (e.g. `Etherfuse CETES`)."
          },
          "netAmountDecimal": {
            "type": "string",
            "description": "Net token amount outstanding."
          },
          "netValueDecimal": {
            "type": "string",
            "description": "Net value outstanding, in the bond's currency."
          },
          "purchaseOrderAmount": {
            "type": "string",
            "description": "Open purchase-order amount."
          },
          "redeemOrderAmount": {
            "type": "string",
            "description": "Open redeem-order amount."
          },
          "solanaMintAddress": {
            "type": "string",
            "description": "Canonical Solana mint address."
          },
          "symbol": {
            "type": "string",
            "description": "Bond symbol (e.g. `CETES`)."
          },
          "tokenPriceDecimal": {
            "type": "string",
            "description": "Current price of one whole token in the bond's currency, from BondPrice. The returned price is rounded to the mint's decimal precision."
          }
        }
      },
      "StablebondsCachable": {
        "type": "object",
        "description": "Full stablebonds lookup response, cached as a unit.\nUses string blockchain keys (same pattern as BridgeConfig).",
        "required": [
          "calculatedAt",
          "stablebonds"
        ],
        "properties": {
          "calculatedAt": {
            "type": "string",
            "description": "Timestamp (RFC 3339) the snapshot was computed."
          },
          "stablebonds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StablebondInfoCachable"
            },
            "description": "All stablebonds in the snapshot."
          }
        },
        "example": {
          "calculatedAt": "2024-01-15T12:00:00Z",
          "stablebonds": [
            {
              "blockchains": [
                {
                  "blockchain": "stellar",
                  "tokenIdentifier": "CETES-GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4",
                  "totalSupply": "106595.334047"
                },
                {
                  "blockchain": "solana",
                  "tokenIdentifier": "AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q",
                  "totalSupply": "916496.867925"
                }
              ],
              "bondCurrency": "MXN",
              "name": "Etherfuse CETES",
              "netAmountDecimal": "1309439.053036",
              "netValueDecimal": "1467139.953242",
              "purchaseOrderAmount": "283617.373522",
              "redeemOrderAmount": "0",
              "solanaMintAddress": "AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q",
              "symbol": "CETES",
              "tokenPriceDecimal": "1.120434"
            }
          ]
        }
      },
      "SubmitVerificationRequest": {
        "type": "object",
        "description": "Personal data a partner submits on the customer's behalf.",
        "required": [
          "firstName",
          "lastName",
          "dateOfBirth",
          "taxId",
          "country",
          "address"
        ],
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "dateOfBirth": {
            "type": "string",
            "description": "`YYYY-MM-DD`. Any other format is a 400.",
            "example": "1990-01-01"
          },
          "taxId": {
            "type": "string",
            "description": "The customer's tax identification number. The RFC for a Mexican customer.",
            "example": "XAXX010101000"
          },
          "country": {
            "type": "string",
            "description": "The customer's country, as an ISO 3166-1 alpha-3 code. Alpha-2 codes and country names are rejected. This is the one country field for the customer (there is no separate nationality), and its value routes them to their country's requirements.",
            "example": "MEX"
          },
          "address": {
            "$ref": "#/components/schemas/VerificationAddress"
          }
        },
        "example": {
          "firstName": "Ana",
          "lastName": "Ejemplo",
          "dateOfBirth": "1990-01-01",
          "taxId": "XAXX010101000",
          "country": "MEX",
          "address": {
            "street": "Av. Reforma 100",
            "city": "Ciudad de Mexico",
            "region": "CDMX",
            "postalCode": "06600",
            "country": "MEX"
          }
        }
      },
      "TokenCost": {
        "type": "object",
        "description": "Current pricing for one Etherfuse token: its cost in the denomination\ncurrency and in USD, plus the per-provider exchange-rate `sources` used to\nderive it.",
        "required": [
          "token_symbol",
          "token_mint",
          "token_kind",
          "currency",
          "token_cost_in_fiat",
          "token_cost_in_usd",
          "fiat_exchange_rate_with_usd",
          "current_basis_points",
          "current_time",
          "sources"
        ],
        "properties": {
          "token_symbol": {
            "type": "string",
            "description": "Display symbol (e.g. `CETES`, `MEXe`). The map key on the list endpoint is this value uppercased."
          },
          "token_mint": {
            "type": "string",
            "description": "The token's own Solana mint address."
          },
          "token_kind": {
            "type": "string",
            "enum": [
              "bond",
              "principal_token"
            ],
            "description": "`bond` for a stablebond, `principal_token` for a token that wraps one (e.g. MEXe wraps CETES)."
          },
          "underlying_bond_symbol": {
            "type": "string",
            "description": "Principal tokens only: the symbol of the bond this token wraps. Absent on bonds."
          },
          "currency": {
            "type": "string",
            "description": "Denomination currency (ISO 4217, e.g. `MXN`)."
          },
          "token_cost_in_fiat": {
            "type": "string",
            "description": "Token cost denominated in `currency`. A principal token holds par with its currency, so 1 MEXe reads as 1.000000 MXN."
          },
          "token_cost_in_usd": {
            "type": "string",
            "description": "Token cost denominated in USD."
          },
          "fiat_exchange_rate_with_usd": {
            "type": "string",
            "description": "FX rate between the denomination currency and USD."
          },
          "current_basis_points": {
            "type": "integer",
            "format": "int32",
            "description": "Annualized rate of the bond, in basis points (e.g. `554` = 5.54%). Read from BondPrice. Always `0` on a principal token: the coupon accrues to the underlying bond, not to the principal-token holder."
          },
          "current_time": {
            "type": "string",
            "description": "Timestamp the price was computed (RFC 3339)."
          },
          "payment_mint": {
            "type": "string",
            "description": "Bonds only: mint address of the token a purchase settles in. Absent on principal tokens."
          },
          "sources": {
            "type": "object",
            "description": "Per-FX-provider exchange rate and derived USD cost, for oracle safety checks. Keys are Etherfuse FX provider ids.",
            "additionalProperties": {
              "$ref": "#/components/schemas/TokenCostSource"
            },
            "propertyNames": {
              "type": "string"
            }
          }
        },
        "example": {
          "token_symbol": "MEXe",
          "token_mint": "MEXeQE5UjuusUoyS2KxbUXp6o21YbbBJaNvPMMEfr4y",
          "token_kind": "principal_token",
          "underlying_bond_symbol": "CETES",
          "currency": "MXN",
          "token_cost_in_fiat": "1.000000",
          "token_cost_in_usd": "0.058950",
          "fiat_exchange_rate_with_usd": "16.96343",
          "current_basis_points": 0,
          "current_time": "2026-08-20T16:30:52.382508961+00:00",
          "sources": {
            "d2ce2919-082f-4d60-8fbb-a15b3b570e57": {
              "exchange_rate": "16.96343",
              "token_cost_in_usd": "0.058950",
              "updated_at": 1787243387
            },
            "8e1e2967-0283-42dd-bb06-e49193c368d2": {
              "exchange_rate": "16.96425",
              "token_cost_in_usd": "0.058947",
              "updated_at": 1787243417
            }
          }
        }
      },
      "TokenCostSource": {
        "type": "object",
        "description": "A single FX provider's exchange rate and the token cost it implies. The\n`sources` map on a token lets clients run oracle-safety checks across\nproviders.",
        "required": [
          "exchange_rate",
          "token_cost_in_usd",
          "updated_at"
        ],
        "properties": {
          "exchange_rate": {
            "type": "string",
            "description": "This provider's USD to currency exchange rate. Reported as the provider gives it, never adjusted by the wrap rate on a principal token."
          },
          "token_cost_in_usd": {
            "type": "string",
            "description": "Token cost in USD derived from this provider's rate."
          },
          "updated_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds) of the provider's last update.",
            "minimum": 0
          }
        },
        "example": {
          "exchange_rate": "16.96343",
          "token_cost_in_usd": "0.058950",
          "updated_at": 1787243387
        }
      },
      "UserInfo": {
        "type": "object",
        "description": "Info about the end user, collected at org/customer creation time. The\nuser record is pre-populated with the partner's display name and email so\nthe customer's eventual Firebase sign-in attaches to the correct user, so\nwe can send status-change emails, and so the verification record has an\nemail address.",
        "required": [
          "email",
          "displayName"
        ],
        "properties": {
          "displayName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        }
      },
      "VerificationAddress": {
        "type": "object",
        "description": "The customer's residential address. Required, and it must match the identity document you upload; the provider cross-checks the two.",
        "required": [
          "street",
          "city",
          "country"
        ],
        "properties": {
          "street": {
            "type": "string",
            "example": "Av. Reforma 100"
          },
          "city": {
            "type": "string",
            "example": "Ciudad de Mexico"
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "description": "State or province.",
            "example": "CDMX"
          },
          "postalCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional, because some countries issue none. Send it when the country has one.",
            "example": "06600"
          },
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-3.",
            "example": "MEX"
          },
          "buildingNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "flatNumber": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "VerificationStatusResponse": {
        "type": "object",
        "description": "Where the customer's verification stands after a write. `status` is the same value `GET /organization/{organizationId}/kyc` reports.",
        "required": [
          "organizationId",
          "status"
        ],
        "properties": {
          "organizationId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/VerificationStatus"
          }
        },
        "example": {
          "organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "status": "inProgress"
        }
      },
      "WalletBalance": {
        "type": "object",
        "required": [
          "symbol",
          "identifier",
          "balance",
          "currency",
          "amountInFiat",
          "amountInUsd"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Asset symbol (e.g. \"cetes\")"
          },
          "identifier": {
            "type": "string",
            "description": "Chain-specific asset identifier: the mint on Solana, the contract on EVM chains, \"CODE:ISSUER\" on Stellar"
          },
          "balance": {
            "type": "string",
            "description": "Held amount, at the asset's own decimal precision"
          },
          "currency": {
            "type": "string",
            "description": "The fiat currency this asset is denominated in (e.g. \"mxn\" for CETES, \"brl\" for TESOURO)"
          },
          "amountInFiat": {
            "type": "string",
            "description": "Value of the held amount in `currency`, to six decimal places. Stablebond valuations use the price from BondPrice."
          },
          "amountInUsd": {
            "type": "string",
            "description": "Value of the held amount in USD, to six decimal places, using the same asset price as amountInFiat and the current FX rate."
          }
        }
      },
      "AuthLaunchRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "target"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "urn:ietf:params:oauth:grant-type:jwt-bearer",
              "refresh_token"
            ]
          },
          "assertion": {
            "type": "string",
            "description": "The partner-signed JWT. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:jwt-bearer`."
          },
          "refresh_token": {
            "type": "string",
            "description": "A `refresh_token` from a prior `POST /auth/token` exchange. Required when `grant_type` is `refresh_token`."
          },
          "target": {
            "type": "string",
            "description": "App path to land the user on after sign-in. Must be an allowed target. See [User Launch Flows](/v2/guides/user-launch-flows#targets). Any other path is rejected.",
            "example": "/kyb"
          },
          "return_url": {
            "type": "string",
            "description": "Optional URL to return the user to when they leave the app."
          }
        },
        "example": {
          "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
          "assertion": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imt...",
          "target": "/kyb"
        }
      }
    }
  }
}
