# Changelog
Source: https://docs.capa.fi/changelog
## The `REGULAR` Flow Is Retired
Every on-ramp, off-ramp, and cross-ramp now runs the **OTC lifecycle** regardless of amount. The `flow` field on quote and transaction responses is always `OTC` — `REGULAR` is no longer returned.
The statuses that only existed in the `REGULAR` lifecycle are unreachable for new transactions:
| Retired status | New equivalent |
| - | - |
| `PENDING` (on-ramp and off-ramp start) | `PENDING_PAYMENT` |
| `FIAT_RECEIVED` (on-ramp) | `FIAT_PAYMENT_RECEIVED` |
Off-ramp also gains a `WAITING_FOR_FUNDS` step between `COMPLETED_CRYPTO_RECEIVAL` and `BANK_TRANSFER_PENDING`.
Cancellation windows have moved accordingly:
* **On-ramp** — cancellable in `PENDING_PAYMENT`
* **Off-ramp** — cancellable in `PENDING_PAYMENT` or `COMPLETED_CRYPTO_RECEIVAL`
* **Cross-ramp** — cancellable in `PENDING_FUNDS`
Things to know:
* If you branch on `flow === "REGULAR"`, that branch is now dead code.
* If you wait for `PENDING` or `FIAT_RECEIVED`, switch to `PENDING_PAYMENT` and `FIAT_PAYMENT_RECEIVED`.
* Historical transactions keep the status they were created with, so `PENDING` and `FIAT_RECEIVED` remain valid values when filtering past transactions.
* Webhook event names are unchanged — `FIAT_RECEIVED_ON_RAMP` still fires as before.
See the [On-Ramp](/docs/on-ramp#transaction-status-progression) and [Off-Ramp](/docs/off-ramp#transaction-status-progression) guides for the updated lifecycle diagrams.
## `10.01`, `10.02`, and `10.03` No Longer Trigger Mocks
The `10.0x` mock amounts have been removed. They collided with genuine small-value sandbox orders, so a real `10.01` transaction was silently completed by the mock path instead of following the normal flow.
Use the `1000.0x` amounts instead:
| Old Amount | Use Instead | Result |
| - | - | - |
| `10.01` | `1000.01` | `COMPLETED` |
| `10.02` | `1000.02` | `CANCELLED` with `NAME_VERIFICATION_FAILED` |
| `10.03` (off-ramp) | `1000.03` | `CRYPTO_NOT_RECEIVED` |
Things to know:
* A sandbox transaction created at `10.01`, `10.02`, or `10.03` now behaves like any other real transaction — it will not be driven to a mock status. Update any integration tests pinned to those amounts.
* Cross-ramp is unaffected: the `2000.0x` band still works exactly as before.
* Mock triggers now live only in the `1000.0x` (on-ramp and off-ramp) and `2000.0x` (cross-ramp) bands.
See the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide) for the current rules.
## Beneficial Owner Signature Requirement
Business users creating a cross-ramp transaction in **MXN** must now have every registered Ultimate Beneficial Owner (UBO) digitally sign a cédula (beneficial-owner declaration), per art. 95 Bis LGOAAC.
New endpoints under `/api/partner/v2/users/{userId}/associated-persons`:
* `POST /associated-persons/signatures` — request a cédula signing link for each `UBO`-role person who doesn't already have one
* `GET /associated-persons/signatures` — poll where every UBO stands on their cédula
Things to know:
* Only `country: "MX"` is currently supported.
* Requesting signatures is idempotent — an owner with an outstanding or completed cédula is left untouched.
* Capa sends no email to the owners; delivering each `signingUrl` is the partner's responsibility.
* A new `DOCUMENT_SIGNED` webhook (`type: "SIGNATURE"`) fires once a UBO signs.
* Cross-ramp creation for MXN now returns `403 SIGNATURE_REQUIREMENT_NOT_MET` while any UBO signature is outstanding.
See the [Associated Person Signature guide](/docs/associated-person-signature).
## Simulate Cross-Ramp Outcomes in Sandbox
Cross-ramp transactions can now be driven through their lifecycle in the sandbox environment using magic `sourceAmount` values, the same way on-ramp and off-ramp mocking already worked.
| Source Amount | Result |
| - | - |
| `2000.01` | Walks the full lifecycle to `COMPLETED` |
| `2000.02` | `CANCELLED` with `NAME_VERIFICATION_FAILED` |
| `2000.03` | Halts at `AWAITING_FUND_TRANSFER` so the payout leg can be exercised |
Things to know:
* Only `sourceAmount` triggers a cross-ramp mock — on the `targetAmount` path the source amount is fee-derived and never matches. If you create the transaction from a `quoteId`, the quote must have been created with the magic `sourceAmount`.
* The transaction advances through the real state machine, so every cross-ramp webhook fires in order.
* Cross-ramp uses its own `2000.0x` band so it does not collide with the on-ramp leg's `1000.0x` OTC amounts.
The on-ramp and off-ramp OTC amounts (`1000.01`, `1000.02`, `1000.03`) are now documented as well.
See the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide) and the [Cross-Ramp guide](/docs/cross-ramp#sandbox-testing).
## Named US Bank Accounts via Banking Onboarding
Partners can now request a **virtual account** for a US user — a bank account (account number + ABA routing number) issued in the **user's own name**, so funds arrive under their name instead of a shared account.
Two new endpoints under `/api/partner/v2/banks`:
* `GET /api/partner/v2/banks/users/{userId}/banking-onboarding?country=US` — eligibility and status: whether the user already has a virtual account, whether a request would be accepted (`canRequest`), the state of the latest onboarding, and whether their KYB is still outstanding.
* `POST /api/partner/v2/banks/users/{userId}/banking-onboarding` — starts onboarding. Responds with `SUBMITTED`, `ALREADY_IN_PROGRESS`, `ALREADY_COMPLETED`, or `REQUIREMENTS_MISSING`; the last one means the user's KYB is not complete, and returns a link they can use to finish it.
Things to know:
* **US only** — `country` must be `US`; any other value returns `400`.
* **Asynchronous** — there is no webhook for onboarding. Poll the status endpoint until `COMPLETED`, then read the account from `GET /api/partner/v2/banks/users/{userId}/accounts`.
* Provisioned virtual accounts appear as normal bank accounts with `rampType: "CROSS_RAMP"`.
See the [Virtual Accounts guide](/docs/virtual-accounts), [Request Virtual Account](/reference/bankingonboardingpartnerv2controller_requestonboarding), and [Get Virtual Account Status](/reference/bankingonboardingpartnerv2controller_getonboardingstatus).
## BSC / USDT for On-Ramp and Off-Ramp
On-ramp and off-ramp transactions now support **BSC (Binance Smart Chain)** with **USDT**.
* New `blockchainSymbol` value **`BSC`**, paired with `tokenSymbol` **`USDT`**.
* Available on `POST /api/partner/v2/on-ramp`, `POST /api/partner/v2/off-ramp`, and the quote endpoints (`POST /api/partner/v2/quote`, `GET /api/partner/v2/quote/rate`).
* **Limitation**: BSC is only available for transactions **over 50,000 MXN**.
* `BSC` is EVM-compatible — use the `EVM` `vmType` for BSC wallet addresses.
See the [On-Ramp guide](/docs/on-ramp), [Off-Ramp guide](/docs/off-ramp), and [SDK reference](/sdk#supported-blockchains).
## USD Payouts to China and Hong Kong
Cross-ramp transactions can now deliver **USD** to bank accounts in **China (CN)** and **Hong Kong (HK)**. These are **destination-only** corridors — CN/HK can be the target of a cross-ramp, never the source.
* **USD only** — CNY is not yet supported. Because source and target currencies must differ, the source currency must be **MXN, DOP, or EUR**.
* **New request fields** on `POST /api/partner/v2/cross-ramp`:
* `targetCountry` — set to `CN` or `HK` to route USD there (USD otherwise defaults to the US).
* `targetRail` — `LOCAL` (default) or `SWIFT`.
* `invoiceFile` — Base64-encoded PDF invoice, **required** when the target country is `CN`.
* `reference` — optional memo (max 140 chars), forwarded on USD→CN SWIFT payouts.
* **Limits**: min **10 USD**, max **10,000 USD** per transaction.
See the [Cross-Ramp guide](/docs/cross-ramp#china--hong-kong-destinations) and [Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp).
## Idempotency-Key Header
Transaction-creation `POST` endpoints now support opt-in idempotency via the `Idempotency-Key` HTTP header. Send a unique key (UUIDv4 recommended) with your request; if the request is retried with the same key and body, Capa replays the original response without creating a duplicate transaction.
**Covered endpoints:**
* `POST /api/partner/v2/on-ramp`
* `POST /api/partner/v2/off-ramp`
* `POST /api/partner/v2/cross-ramp`
The guarantee window is **48 hours**. Requests without the header are unaffected.
See the [Idempotency Guide](/docs/idempotency) for the full contract, behavior matrix, and best practices.
## Webhook Notification List, Detail, and Retry
Three new endpoints give partners full visibility into webhook delivery history and the ability to self-serve failed notifications without contacting support.
* **`GET /api/partner/v2/webhook-notifications`** — Paginated list of all webhook notifications for your account. Filter by `status` (PENDING, SENT, FAILED, NOT\_SENT) to surface failed deliveries.
* **`GET /api/partner/v2/webhook-notifications/:id`** — Full detail for a single notification: original payload, last error response, retry history, and delivery status.
* **`POST /api/partner/v2/webhook-notifications/:id/retry`** — Manually retry a `FAILED` notification. Resets the automatic retry cycle and queues the notification for immediate re-delivery.
**Retry limits**: Up to 3 manual retries per notification; up to 10 retries per partner per minute.
See the [Webhook Notifications reference](/reference/partnerwebhooknotificationv2controller_listwebhooknotifications).
## User Wallet Balance Endpoint
New endpoint `GET /api/partner/v2/users/{userId}/wallets/balance` retrieves the crypto balances for all wallets belonging to a user. Balances are grouped by blockchain, flow, and wallet address, with a nested `tokens` array containing each token's `symbol` and `balance`.
Supports optional query filters: `blockchain` and `token`.
See the [User Wallet Balance reference](/reference/userbalancepartnerv2controller_getuserwalletbalances).
***
## Forwarding Settlement Fields on Transaction Listing
The transaction listing response now includes three new fields for MXN OTC transactions:
* **`forwardingDays`** — The requested T+N forwarding days (0–4).
* **`effectiveForwardingDays`** — The actual forwarding days after accounting for holidays and weekends.
* **`forwardingSettlementDate`** — The calculated settlement date based on the effective forwarding days.
These fields appear on responses from `GET /api/partner/v2/transactions`.
***
## On-Ramp Required Fields Simplified
When creating an on-ramp transaction with a `quoteId`, the fields `fiatCurrency`, `blockchainSymbol`, and `tokenSymbol` are no longer required — their values are inherited from the quote. Only `userId` and `destinationWalletAddress` remain required.
See the [On-Ramp reference](/reference/onramppartnerv2controller_createpartneronramp).
***
## Optional Bank Alias
Bank account creation endpoints now accept an optional **`alias`** field — a friendly label for the account (e.g. `"My savings"`). The alias is returned in all bank account GET responses.
Applies to both user and receiver bank account endpoints.
See the [Bank Accounts guide](/docs/bank-accounts).
## V1 API Removal
All V1 endpoints and documentation have been fully removed. The API reference now exclusively covers V2 endpoints. If you are still referencing V1 routes, please migrate to the corresponding V2 endpoints immediately.
***
## Status Page
A new **Status** link has been added to the documentation navbar, pointing to [status.capa.fi](https://status.capa.fi). Partners can now monitor API uptime, ongoing incidents, and maintenance windows directly from the docs.
## Bank Accounts
Partners can now create, list, and delete bank accounts for both users and receivers. Supported countries include MX, DO, US, and SEPA regions. Mexican accounts include automatic penny-test verification to confirm account ownership.
Bank details can also be provided inline when creating off-ramp or cross-ramp transactions.
See the [Bank Accounts guide](/docs/bank-accounts).
***
## User Wallets
Partners can now save and manage destination wallet addresses for on-ramp crypto delivery. Supported VM types include EVM and SVM, covering blockchains like Polygon, Ethereum, and Solana.
See the [User Wallets guide](/docs/user-wallets).
***
## Receivers
Partners can register third-party individuals or businesses as receivers to accept off-ramp and cross-ramp payouts on behalf of a user. Pass the `receiverId` when creating a transaction to direct funds to a third party.
***
## FAQ Updates
The FAQ section has been expanded with answers sourced from common partner questions.
## New Quote Parameters: `country`, `rail`, and `forwardingDays`
The `POST /api/partner/v2/quotes` endpoint now supports three new optional fields:
* **`country`** — Override the destination country when a currency is shared across multiple regions (e.g. USD in US vs. DO).
* **`rail`** — Specify the payment rail for the transaction (SPEI, ACH, WIRE, SEPA, SWIFT).
* **`forwardingDays`** — Set T+N forwarding days (0–4) for MXN OTC settlement scheduling.
These fields give partners finer control over quote generation for multi-country and multi-rail flows.
## New Off-Ramp Execute Endpoint
Added `POST /api/partner/v2/off-ramp/execute` — a new endpoint that executes a previously created off-ramp transaction. After crypto funds are received at the `destinationWalletAddress`, call this endpoint to trigger the fiat payout to the user's bank account.
This enables partners to separate transaction creation from execution, supporting manual verification steps or automated workflow orchestration.
See the [Off-Ramp Execute reference](/reference/offramppartnerv2controller_executeofframp) for full details.
## Quote Locking Available for All MXN Transactions
Previously, quote locking was blocked for Mexico transactions exceeding **\$50,000 MXN**. This restriction has been removed — partners can now lock exchange rates on MXN transactions of **any amount**, just like any other currency.
## MCP Server for Documentation
Capa now offers a Model Context Protocol (MCP) server that connects AI tools directly to our documentation. Instead of relying on generic web searches, AI assistants can query Capa's API docs, integration guides, and feature references in real time — providing more accurate and up-to-date answers.
See the [MCP Server guide](/docs/mcp-server) for setup instructions.
# 🖋️ Associated Person Signature (UBO Cédula)
Source: https://docs.capa.fi/docs/associated-person-signature
Business users doing a cross-ramp in Mexico must have every beneficial owner (UBO) digitally sign a cédula. Learn when it's required, how to collect it, and how to know when it's done.
***
## Overview
For **business** users, Mexican regulation (art. 95 Bis LGOAAC) requires every Ultimate Beneficial Owner (UBO) — anyone with significant ownership or control over the business — to sign a **cédula**, a beneficial-owner declaration. Capa generates this document, hosts the digital signature session, and tracks its status per owner.
This applies specifically to **cross-ramp transactions in MXN**: a business user cannot create a cross-ramp transaction involving MXN until every UBO on record has signed their cédula.
This requirement is rolled out per environment. If your account doesn't yet require it, [Request Beneficial Owner Signatures](/reference/associatedpersonpartnercontrollerv2_requestuboformsignatures) and [Get Beneficial Owner Signature Status](/reference/associatedpersonpartnercontrollerv2_listuboformsignatures) still work — signatures collected ahead of time are honored once enforcement is turned on for your account.
## When Is It Required?
A cross-ramp transaction is blocked until every UBO has signed when **all** of the following hold:
* The user is of type `BUSINESS`.
* The cross-ramp involves **MXN** as either the source or target currency.
If the currency isn't MXN, or the user is an `INDIVIDUAL`, this requirement doesn't apply and cross-ramp creation proceeds normally.
## Integration Flow
[Create User](/reference/userpartnercontrollerv2_createuser) with `type: "BUSINESS"`, then complete [KYB verification](/reference/kybpartnercontrollerv2_submitbusinessprofile) for `country: "MX"`
Make sure every UBO is on record as an associated person with `UBO` included in `roles` and a valid `email` set
[Request Beneficial Owner Signatures](/reference/associatedpersonpartnercontrollerv2_requestuboformsignatures) with `country: "MX"`. Capa returns a `signingUrl` per owner.
Capa does not email the owners — share each `signingUrl` with the corresponding UBO through your own channel (email, SMS, in-app).
Poll [Get Beneficial Owner Signature Status](/reference/associatedpersonpartnercontrollerv2_listuboformsignatures), or listen for the [`DOCUMENT_SIGNED` webhook](#webhook-notification), until every UBO shows `SIGNED`
[Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp) now succeeds for MXN
## Beneficial Owners (Associated Persons)
A UBO is any associated person recorded for the business whose `roles` includes `UBO`. Only people with this role are asked to sign a cédula — other roles (`LEGAL_REP`, `SIGNER`, `CONTROL_PERSON`, `RESOURCE_PROVIDER`) are not.
Every UBO must have an `email` set — Capa's signature provider uses it to identify the signer, and there is no fallback to a shared business email. Whether someone counts as a UBO is entirely determined by whether `UBO` is included in their `roles`; there is no ownership-percentage threshold enforced by the API.
## Requesting Signatures
Call [Request Beneficial Owner Signatures](/reference/associatedpersonpartnercontrollerv2_requestuboformsignatures) to generate a cédula signing link for every `UBO`-role associated person who doesn't already have one:
```
POST /api/partner/v2/users/{userId}/associated-persons/signatures
```
```json theme={null}
{
"country": "MX"
}
```
Key behavior:
* **MX only.** `country` must be `MX`.
* **Idempotent.** An owner with an already-`PENDING` or `SIGNED` cédula is left alone — calling this again never issues a second link for the same person. This makes it safe to call after adding a new UBO: only the new owner gets a fresh link.
* **Per-owner failures don't block the batch.** A business must have at least one UBO on record (and no more than 25 in a single call); beyond that, an individual owner missing an `email` shows up as an `error` in the response rather than failing the whole request.
* The business must already be **KYB-verified for MX**.
* **Capa sends no email or SMS.** Your integration is responsible for delivering each `signingUrl` to its owner, and for treating it as sensitive — whoever holds the link can sign as that person.
## Checking Signature Status
Poll [Get Beneficial Owner Signature Status](/reference/associatedpersonpartnercontrollerv2_listuboformsignatures) to check progress:
```
GET /api/partner/v2/users/{userId}/associated-persons/signatures
```
The response lists **every** `UBO`-role associated person on record — including any who were never sent a cédula, whose entry simply carries no `status`. This keeps a "short list" from being mistaken for completion after a new owner is added.
| `status` | Meaning |
| - | - |
| *(absent)* | No cédula has been requested yet for this owner, or the request failed (see `error`/`errorCode`) |
| `PENDING` | Requested — the owner has not yet signed. `signingUrl` is included. |
| `SIGNED` | Complete. `signingUrl` is withheld once signed. |
The cross-ramp requirement is satisfied once **every UBO on record reports `SIGNED`**.
## Enforcement on Cross-Ramp
[Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp) rejects the request if any UBO signature is still outstanding for an MXN cross-ramp:
| HTTP Status | Code | Message |
| - | - | - |
| 403 | `SIGNATURE_REQUIREMENT_NOT_MET` | "User is not allowed to perform the operation because a required signature is outstanding for BENEFICIAL\_OWNER." |
If you see this error, call [Get Beneficial Owner Signature Status](/reference/associatedpersonpartnercontrollerv2_listuboformsignatures) to see which owner(s) are still pending, and make sure they've received their `signingUrl`.
## Webhook Notification
When a UBO signs their cédula, Capa sends a webhook notification once the signature is confirmed:
```json JSON theme={null}
{
"type": "SIGNATURE",
"event": "DOCUMENT_SIGNED",
"userId": "8374f327-38bd-4b0b-b8a7-2524599eb903",
"externalUserId": "customer_1753744084024",
"partnerId": "b1f2c3d4-5678-4abc-8def-1234567890ab",
"subject": "BENEFICIAL_OWNER",
"templateKey": "CEDULA",
"signature": {
"associatedPersonId": "a1b2c3d4-5678-4abc-8def-1234567890ab",
"status": "SIGNED",
"result": "SUCCESS",
"country": "MX",
"signedAt": "2026-09-08T14:32:00.000Z"
}
}
```
| Field | Description |
| - | - |
| `subject` | `BENEFICIAL_OWNER` for a UBO cédula. (The same event/`type` combination is also used when a business's own agreement is signed, with `subject: "USER"` — filter on `subject`, not on a separate event name.) |
| `signature.result` | `SUCCESS` or `PENDING_REVIEW`. **Treat both as signed** — `PENDING_REVIEW` means only Capa's internal compliance review is still outstanding, not that the owner needs to do anything further. |
| `signature.signedAt` | May be absent in rare cases where the signing provider's webhook couldn't be fully parsed. |
This event fires once per signature — a redelivered or duplicate provider webhook onto an already-`SIGNED` record does not re-fire it. As with all Capa webhooks, ensure your [Webhook Settings](/reference/partnerwebhooksettingsv2controller_update) are configured to receive it, and treat [Get Beneficial Owner Signature Status](/reference/associatedpersonpartnercontrollerv2_listuboformsignatures) as the source of truth if a webhook is ever missed — see [How Our Webhooks Work](/docs/how-our-webhooks-work).
***
## What's Next
* [Request Beneficial Owner Signatures](/reference/associatedpersonpartnercontrollerv2_requestuboformsignatures)
* [Get Beneficial Owner Signature Status](/reference/associatedpersonpartnercontrollerv2_listuboformsignatures)
* [Cross-Ramp](/docs/cross-ramp)
* [Submit Business Profile (KYB)](/reference/kybpartnercontrollerv2_submitbusinessprofile)
# 🏦 Bank Accounts
Source: https://docs.capa.fi/docs/bank-accounts
Manage bank accounts for users to enable fiat payouts in off-ramp and cross-ramp transactions.
***
## Overview
Bank accounts are used to receive fiat payouts in off-ramp and cross-ramp transactions. You can manage bank accounts for **users** directly. Each bank account is tied to a specific country and must meet that country's validation requirements.
Alternatively, bank details can be provided inline when creating a transaction, without saving them beforehand.
## Requirements by Country
Each country has specific required fields when creating a bank account:
| Country | Required Fields | Notes |
| - | - | - |
| **MX** | `accountIdentifier` (18-digit CLABE) | `bankName` is not allowed |
| **DO** | `accountIdentifier`, `bankName`, `accountType`, `documentIdentifier`, `documentType` | `accountType`: SAVINGS or CHECKING. `documentType`: PASSPORT, CEDULA, or RNC |
| **US** | `accountIdentifier`, `bankName`, `routingNumber`, `accountHolder`, `address` | `routingNumber`: 9-digit ABA. `address` requires `streetLine1`, `city`, `state`, `postalCode`, `country` |
| **SEPA** | `iban`, `bic`, `bankName`, `accountHolder` | `accountIdentifier` is not allowed. Covers AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, IS, LI, NO, CH, GB, MC, SM, AD, VA |
| **CN / HK** | `accountIdentifier`, `bankName`, `accountType`, `accountHolder`, `address` | Cross-ramp **destination only** (USD delivery). `bic` optional (used for SWIFT). Accounts are provider-managed. See [Cross-Ramp → China & Hong Kong destinations](/docs/cross-ramp#china--hong-kong-destinations) |
The `accountHolder` object requires a `type` field (`INDIVIDUAL` or `BUSINESS`). For individuals, provide `firstName` and `lastName`. For businesses, provide `businessName`.
All countries also accept an optional **`alias`** field — a friendly label for the bank account (e.g. `"My savings"`).
### Examples
```json theme={null}
{
"accountIdentifier": "014680260346007120",
"country": "MX"
}
```
```json theme={null}
{
"accountIdentifier": "1234567890",
"country": "DO",
"bankName": "Banco Popular",
"accountType": "SAVINGS",
"documentIdentifier": "40212345678",
"documentType": "CEDULA"
}
```
```json theme={null}
{
"accountIdentifier": "123456789012",
"country": "US",
"bankName": "Chase Bank",
"routingNumber": "021000021",
"accountHolder": {
"type": "INDIVIDUAL",
"firstName": "John",
"lastName": "Doe"
},
"address": {
"streetLine1": "123 Main St",
"city": "New York",
"state": "NY",
"postalCode": "10001",
"country": "US"
}
}
```
```json theme={null}
{
"country": "DE",
"bankName": "Deutsche Bank",
"iban": "DE89370400440532013000",
"bic": "DEUTDEFF",
"accountHolder": {
"type": "INDIVIDUAL",
"firstName": "Hans",
"lastName": "Mueller"
}
}
```
```json theme={null}
{
"country": "CN",
"accountIdentifier": "6222021234567890123",
"bankName": "Bank of China",
"accountType": "SAVINGS",
"accountHolder": {
"type": "INDIVIDUAL",
"firstName": "Wei",
"lastName": "Zhang"
},
"address": {
"streetLine1": "1 Jianguomenwai Ave",
"city": "Beijing",
"postalCode": "100005",
"country": "CN"
}
}
```
Cross-ramp destination only (USD delivery). Add `bic` when delivering via the SWIFT rail. Hong Kong (`HK`) uses the same fields.
## User Bank Accounts
Manage bank accounts linked directly to a user.
### Create
Use the [Create Bank Account](/reference/userbankinfopartnerv2controller_createuserbankinfobyuserid) endpoint to add a bank account for a user.
```
POST /api/partner/v2/banks/users/{userId}/accounts
```
### List
Use the [List Bank Accounts](/reference/userbankinfopartnerv2controller_getuserbankinfosbyuserid) endpoint to retrieve all bank accounts for a user. Supports filtering by `country`, `rampType`, and `id`, with pagination via `skip`, `limit`, and `sortBy`.
```
GET /api/partner/v2/banks/users/{userId}/accounts
```
### Delete
Use the [Delete Bank Account](/reference/userbankinfopartnerv2controller_deleteuserbankinfobyuserid) endpoint to soft-delete a bank account. The account will be disabled but existing transactions are not affected.
```
DELETE /api/partner/v2/banks/users/{userId}/accounts/{userBankInfoId}
```
## Verification (Mexico)
Mexican bank accounts go through a penny testing verification process to confirm account ownership. The verification flow:
1. When a bank account is created with `country: "MX"`, a small deposit (penny test) is initiated.
2. The `verificationStatus` field tracks the result:
| Status | Description |
| - | - |
| `PENDING` | Penny test has been initiated, awaiting result |
| `VERIFIED` | Account ownership confirmed |
| `REJECTED` | Penny test failed, account owner does not match the KYC'd user |
| `CEP_FAILURE` | Bank verification receipt could not be validated |
3. The `isVerified` boolean indicates the current verification state.
4. If a user attempts an off-ramp with an unverified MX account, the transaction will proceed but may be cancelled and the crypto reimbursed if verification fails.
## Virtual Accounts (US)
US users can also be given a **virtual account** — a bank account issued in their own name, provisioned by Capa through banking onboarding rather than added by you. Once provisioned it appears in the list above as a normal bank account record with `rampType: "CROSS_RAMP"`.
See the [Virtual Accounts guide](/docs/virtual-accounts) for the onboarding flow.
## Inline Bank Details
When creating off-ramp or cross-ramp transactions, bank information can be provided directly in the request body instead of saving a bank account beforehand:
* **Off-ramp**: Use the `userBankInformation` field in the [Create Off-Ramp](/reference/offramppartnerv2controller_createpartnerofframp) request.
* **Cross-ramp**: Use the `targetBankAccount` field in the [Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp) request.
The same country-specific field requirements apply whether using inline details or saved accounts.
***
## What's Next
* [Create Bank Account (User)](/reference/userbankinfopartnerv2controller_createuserbankinfobyuserid)
* [🏛️ Virtual Accounts](/docs/virtual-accounts)
* [💵 Off-Ramp](/docs/off-ramp)
* [🔄 Cross-Ramp](/docs/cross-ramp)
# 👤 User Creation and Verification
Source: https://docs.capa.fi/docs/create-and-verify-a-user-with-capa-api
Create users and complete identity verification through KYC links, direct KYC submission, or KYB for business users.
***
# Integration Flow
[Create User](/reference/userpartnercontrollerv2_createuser) with `externalUserId`, `email`, and `identifier`
**Option A**: [Generate KYC Link](/reference/userpartnercontrollerv2_createverificationlink) to redirect the user to our provider
**Option B**: [Submit KYC Data](/reference/userpartnercontrollerv2_createuserkyc) directly via API
**Option C**: [Generate KYB Link](/reference/userpartnercontrollerv2_generatekybverificationlink) for business users
Poll via [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails) or [Get KYB Details](/reference/kybpartnercontrollerv2_getuserkybdetails), or receive results through [Webhooks](/docs/how-our-webhooks-work)
# Create the User
To onboard a new user into your system, you first need to register them with the Capa API. Here's how the process works:
1. **Send a Request to Create a User**
* Make a **POST** request to the [User Endpoint](/reference/userpartnercontrollerv2_createuser) with the necessary user information, such as `externalUserId`, `email`, and `identifier`.
* The `externalUserId` is your own unique identifier for this user, ensuring we make your user as unique as possible.
* The `type` defines the type of user between the following options: `INDIVIDUAL`, `BUSINESS`.
2. **Receive the User ID**
* Upon successful creation, Capa will return a unique `userId` for the newly created user. This `userId` is required for generating the KYC verification link in the next step.
3. **Store the User ID**
* Save the returned `userId` in your system, linking it to your own `externalUserId`. This `userId` will be used whenever you need to perform actions related to this user, such as KYC verification.
# Complete User KYC
For individual users you have two verification options: **Generate KYC Verification Link** (redirect the user to our provider) or **Send KYC Information Directly** (submit identity data via API). For business users, use the **KYB Verification Link**.
## Generate KYC Verification Link with our Provider
Once you have successfully created a user, the next step is to generate a KYC verification link. This link allows the user to complete the Know Your Customer (KYC) process, which is required for regulatory compliance.
### 1. Generate Link
1. **Prepare the KYC Verification Request**
* Use the `userId` obtained from the user creation step.
* Determine the `country` in which the user resides or where the verification is needed. Ensure the country code is compliant with the supported countries in the Capa API.
* Use the `partnerRedirectUri` to determine the redirection after the KYC completion
2. **Send a Request to Generate the KYC Link**
* Make a **POST** request to the [Generate KYC Link Endpoint](/reference/userpartnercontrollerv2_createverificationlink) endpoint with the required parameters (`userId`, `country`, and `partnerRedirectUri` if necessary).
3. **Receive the KYC Link**
* Upon successful submission, Capa will return a direct `kycLink` for the user. This link directs the user to a secure Metamap webpage where they can complete the KYC process.
4. **Distribute the KYC Link to the User**
* Share the `kycLink` with the user via email, SMS, or within your app. Provide clear instructions on how to use the link to complete the KYC verification.
### 2. Implement the KYC Link on Your Platform
1. **Embed the KYC Link in Your User Interface**
* Use the `kycLink` in your website or application to create a button or hyperlink that users can click to start their KYC verification.
* Example:
```html theme={null}
```
2. **Guide the User Through the KYC Process**
* Ensure the user knows they will be redirected to a third-party webpage (Metamap) for KYC verification. Provide any necessary guidance to help them complete the process smoothly.
### 3. Get KYC Information
To get the KYC Information on demand use the [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails) endpoint to obtain all of the information from the user's KYC including the status. This is an optional step since you will be able to get the information through the webhook too.
### 4. Handle the Webhook Verification Response
**Follow the [📣 Webhooks](/docs/how-our-webhooks-work) for more information**
1. **Receive the Webhook Notification**
* After the user completes the KYC process, Capa will send a verification response to your system via a webhook. This response will contain the result of the KYC process.
2. **Process the Webhook Data**
* Set up your system to handle incoming webhook notifications. Verify the authenticity of the webhook by checking the signature included in the header and process the KYC status accordingly (e.g., update the user's verification status in your database).
3. **Act on the Verification Result**
* Based on the result from the webhook, take appropriate action:
* **If Verified:** Grant access to features or services that require a verified status.
* **If Rejected:** Notify the user and provide instructions on how to retry the verification process.
* **If Further Review is Needed:** Follow up as required to assist the user through any manual review processes.
## Send KYC Information Directly
For partners who already collect identity documents on their own platform, you can submit KYC data directly to Capa without redirecting the user. This avoids requiring users to go through the verification process a second time.
Use the [Submit KYC Data](/reference/userpartnercontrollerv2_createuserkyc) endpoint:
```
POST /api/partner/v2/users/{userId}/kyc
```
### Required Fields
| Field | Description |
| - | - |
| `country` | ISO 3166-1 alpha-2 country code (e.g., `MX`, `DO`, `US`, or supported EU countries) |
| `profession` | User's profession (e.g., `EMPLOYEE`, `ENTREPRENEUR`, `ENGINEER`, `STUDENT`) |
| `countryOfBirth` | Country of birth (ISO 3166-1 alpha-2 code, e.g., `MX`, `DO`, `US`) |
| `selfieUrl` | Pre-signed URL to the user's selfie image |
| `identity` | Object containing `type`, `frontIdUrl`, and `backIdUrl` (see identity types below) |
### Identity Document Types
The `identity.type` field accepts the following values:
| Type | Description |
| - | - |
| `GOVERNMENT_ID` | Government-issued photo ID (e.g., INE/IFE in Mexico) |
| `RESIDENT_CARD` | Resident or immigration card |
| `TAX_ID` | Tax identification document |
For the **direct KYC API** (this endpoint), the `identity.type` values above (`GOVERNMENT_ID`, `RESIDENT_CARD`, `TAX_ID`) are accepted for all countries. For Mexico, use `GOVERNMENT_ID` — Capa maps it internally to INE/IFE (Voter ID).
For the **KYC link flow** (provider-hosted verification via [Generate KYC Link](/reference/userpartnercontrollerv2_createverificationlink)), the accepted document types are `NATIONAL_ID`, `VOTER_ID`, `PASSPORT`, and `TAX_ID` for all countries including Mexico.
The `identity` object requires both `frontIdUrl` and `backIdUrl` — front and back must be uploaded as **separate images**, not merged into a single file.
### Additional Required Fields for Mexico (MX)
When `country` is `MX`, the following fields are also required:
| Field | Description |
| - | - |
| `taxId` | RFC tax identification number (format: `^[A-Z]{4}\d{6}[A-Z0-9]{3}$`, e.g., `LOZG7802117B9`). CURP is **not** accepted. |
| `economicActivity` | User's economic activity (see the [API reference](/reference/userpartnercontrollerv2_createuserkyc) for the full list of accepted values) |
| `documents` | Array of documents with `ADDRESS_PROOF` (`{ type: "ADDRESS_PROOF", fileUrl: "..." }`). Required only if the ID used is **not** an INE — if the user's INE address matches their current address, this can be omitted. |
| `address` | Object with `street`, `city`, `state`, `zipCode`, `country` |
### Optional Fields
| Field | Description | Availability |
| - | - | - |
| `phoneNumber` | User's phone number | All countries |
| `taxId` | Tax identification number | Optional for non-MX (required for MX) |
| `economicActivity` | User's economic activity | Optional for non-MX (required for MX) |
| `documents` | Array of additional documents | Optional for non-MX (conditionally required for MX — see above) |
| `address` | Object with `street`, `city`, `state`, `zipCode`, `country` | Optional for non-MX (required for MX) |
| `pep` | Politically Exposed Person information (`relation`, `position`, `period`) | MX only |
| `beneficiaryOwner` | Beneficial owner details (if different from the user) | MX only |
| `resourceProvider` | Resource provider details (if different from the user) | MX only |
| `realOwner` | Real owner details (if different from the user) | MX only |
### File URL Requirements
All document URLs (`frontIdUrl`, `backIdUrl`, `selfieUrl`, and any `fileUrl` in `documents`) must be accessible pre-signed URLs that Capa can download.
| File | Max Size | Allowed Formats |
| - | - | - |
| Selfie | 3 MB | JPEG, JPG, PNG |
| Front ID / Back ID | 5 MB each | JPEG, JPG, PNG, PDF |
| Additional documents (ADDRESS\_PROOF) | 10 MB | JPEG, JPG, PNG, PDF |
URLs must remain accessible for at least several minutes after submission. Short-lived signed URLs (e.g., GCS URLs with a few seconds TTL) will cause `FAILED_TO_DOWNLOAD_FILE_FROM_PRESIGNED_URL` errors. We recommend a minimum TTL of **30 minutes**.
### Sandbox Behavior
In the sandbox environment, KYC verification does not perform real identity checks. You can submit random or test data — documents do not need to be real.
### Example
```json theme={null}
{
"country": "MX",
"profession": "ENGINEER",
"countryOfBirth": "MX",
"selfieUrl": "https://storage.example.com/selfie.jpg?token=abc123",
"identity": {
"type": "GOVERNMENT_ID",
"frontIdUrl": "https://storage.example.com/id-front.jpg?token=abc123",
"backIdUrl": "https://storage.example.com/id-back.jpg?token=abc123"
},
"phoneNumber": "+524401234567",
"taxId": "LOZG7802117B9",
"economicActivity": "DESARROLLO DE SOFTWARE",
"documents": [
{ "type": "ADDRESS_PROOF", "fileUrl": "https://storage.example.com/proof.jpg?token=abc123" }
],
"address": {
"street": "Av. Reforma 123",
"city": "Mexico City",
"state": "CDMX",
"zipCode": "06600",
"country": "MX"
}
}
```
### Verification Flow
Verification is **asynchronous**. After submitting, results are delivered through:
* **Webhooks**: Receive a `VERIFIED_USER`, `REJECTED_KYC`, or `REVIEW_NEEDED_KYC` event (see [📣 Webhooks](/docs/how-our-webhooks-work))
* **Polling**: Use [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails) to check the current status
# KYB Verification for Business Users
KYB verification is only available for users created with `type: "BUSINESS"`. Individual users must use KYC verification (link or direct submission).
For business users, generate a KYB (Know Your Business) verification link using the [Generate KYB Link](/reference/userpartnercontrollerv2_generatekybverificationlink) endpoint:
```
POST /api/partner/v2/users/{userId}/kyb/verification-link
```
### Required Fields
* `country`: ISO 3166-1 alpha-2 country code
### Optional Fields
* `partnerRedirectUri`: URL to redirect the user after KYB completion (supports HTTPS and custom mobile URI schemes like `myapp://`)
### Response
The endpoint returns a `kybLink` that you can redirect the business user to for completing verification:
```json theme={null}
{
"success": true,
"data": {
"kybLink": "https://kyb-provider.example.com/verify/..."
}
}
```
Each link is tied to a unique session. If expired, generate a new one.
### Checking Results
Use the [Get KYB Details](/reference/kybpartnercontrollerv2_getuserkybdetails) endpoint to check the verification status, or listen for webhook events.
***
## What's Next
* [Create User](/reference/userpartnercontrollerv2_createuser)
* [Generate KYC Link](/reference/userpartnercontrollerv2_createverificationlink)
* [Submit KYC Data](/reference/userpartnercontrollerv2_createuserkyc)
* [Generate KYB Link](/reference/userpartnercontrollerv2_generatekybverificationlink)
* [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails)
* [Get KYB Details](/reference/kybpartnercontrollerv2_getuserkybdetails)
# 🔄 Cross-Ramp
Source: https://docs.capa.fi/docs/cross-ramp
The Cross-Ramp feature provided by Capa allows users to convert between different fiat currencies (e.g., MXN to USD). Capa supports cross-ramp operations between MXN, DOP, USD, and EUR.
***
## Overview
Cross-ramp transactions convert one fiat currency into another without the user needing to interact with cryptocurrency. Capa handles the conversion internally and delivers the target currency to the specified bank account.
Supported currencies: **MXN** (Mexican Peso), **DOP** (Dominican Peso), **USD** (US Dollar), and **EUR** (Euro). The source and target currencies must differ.
USD can also be delivered to bank accounts in **China (CN)** and **Hong Kong (HK)** as destination-only corridors. See [China & Hong Kong destinations](#china--hong-kong-destinations) for the additional requirements.
**Business users**: a cross-ramp involving MXN cannot be created until every beneficial owner (UBO) on record has digitally signed a cédula. See [Associated Person Signature](/docs/associated-person-signature).
## Integration Flow
[Create User](/reference/userpartnercontrollerv2_createuser), then [KYC verification](/reference/userpartnercontrollerv2_createverificationlink)
[Create Cross-Ramp Quote](/reference/crossramppartnerv2controller_createpartnercrossrampquote) to lock the exchange rate
[Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp)
The user deposits fiat in the source currency to the bank account returned in `sourceBankAccount`
## How to do a Cross-Ramp Operation
To successfully execute a cross-ramp operation, follow these steps:
1. ### Select Source and Target Currencies:
Choose the fiat currencies for the conversion. For example, MXN to USD or EUR to MXN. The source and target currencies must be different.
2. ### Quoting:
1. To show a real-time estimate of how much the user will receive, use the [Get Cross-Ramp Quote Rate](/reference/crossramppartnerv2controller_getpartnercrossrampquoterate) endpoint with `sourceCurrency`, `targetCurrency`, and either `sourceAmount` or `targetAmount`.
2. To lock a guaranteed exchange rate, use the [Create Cross-Ramp Quote](/reference/crossramppartnerv2controller_createpartnercrossrampquote) endpoint. This returns a `quoteId` with an expiration time (`expiresAt`). Pass the `quoteId` when creating the transaction to use the locked rate.
3. You can specify either `sourceAmount` or `targetAmount`, not both.
3. ### Target Bank Account:
Provide the bank account where the target currency will be delivered. You can either:
* Pass `targetBankAccount` inline in the request body with the bank details.
* Use a previously saved bank account ID (`targetBankAccountId`).
Bank account requirements vary by country:
| Country | Required Fields |
| - | - |
| **MX** | `accountIdentifier` (18-digit CLABE) |
| **DO** | `accountIdentifier`, `bankName`, `accountType`, `documentIdentifier`, `documentType` |
| **US** | `accountIdentifier`, `bankName`, `routingNumber` (9-digit ABA), `accountHolder` (`type`, `firstName`/`lastName` or `businessName`), `address` (`streetLine1`, `city`, `postalCode`, `country`) |
| **SEPA** | `iban`, `bic`, `bankName`, `accountHolder` (`type`, `firstName`/`lastName` or `businessName`) |
| **CN / HK** | `accountIdentifier`, `bankName`, `accountType`, `accountHolder`, `address`; `bic` optional (used for SWIFT). Destination only — see [China & Hong Kong destinations](#china--hong-kong-destinations) |
The `targetCurrency` must match the currency of the target bank account's country.
4. ### Create the Cross-Ramp Transaction:
With all required information gathered, create the transaction using the [Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp) endpoint.
Key parameters:
* `userId` (required): The user's Capa ID
* `sourceCurrency` (required): The fiat currency the user will deposit
* `targetCurrency` (required): The fiat currency to be delivered
* `sourceAmount` or `targetAmount`: The amount to convert (provide one, not both)
* `targetBankAccount`: Inline bank account details for the target
* `quoteId` (optional): A locked quote ID from the Create Cross-Ramp Quote endpoint
* `premiumSpread` (optional): A spread percentage to apply to the exchange rate
* `reference` (optional): A free-form reference string for your own records (max 140 characters)
* `targetCountry` (optional): Target country override — required to deliver USD to **China (CN)** or **Hong Kong (HK)** (see below)
* `targetRail` (optional): Target payment rail for CN/HK — `LOCAL` (default) or `SWIFT`
* `invoiceFile` (optional): Base64-encoded PDF invoice — **required** when the target country is `CN`
5. ### User Deposits Source Currency:
After the transaction is created, the response includes `sourceBankAccount` with the bank details where the user must deposit the source fiat currency. Share this information with the user so they can complete the deposit.
## China & Hong Kong destinations
USD can be delivered to bank accounts in **China (CN)** and **Hong Kong (HK)**. These are **destination-only** corridors — CN and HK can be the target of a cross-ramp, never the source.
* **Delivered currency is USD.** CNY is not yet supported. Because the source and target currencies must differ, the **source currency must be MXN, DOP, or EUR** (not USD).
* **Set `targetCountry`.** Pass `targetCountry: "CN"` (or `"HK"`) so USD is routed to China/Hong Kong — USD otherwise defaults to the US. If you pass an inline `targetBankAccount` with `country: "CN"`, the target country is resolved from it automatically. The same `targetCountry`/`targetRail` also apply when requesting a [quote](/reference/crossramppartnerv2controller_createpartnercrossrampquote).
* **Payment rail.** `targetRail` selects how funds are delivered: `LOCAL` (default) for a domestic transfer, or `SWIFT` for an international wire.
* **Invoice required for China.** `invoiceFile` — a Base64-encoded PDF invoice — is **required** whenever the target bank account country is `CN`.
* **Reference/memo.** The optional `reference` field (max 140 characters) is forwarded to the payment provider and is supported across all currencies.
* **Provider-managed accounts.** CN/HK payout accounts are provisioned through Capa's payment provider; provide the fields listed in the [target bank account requirements](#target-bank-account) above.
Amount limits for USD delivered to CN/HK are a minimum of **10 USD** and a maximum of **10,000 USD** per transaction.
## Transaction Limits
The same minimum amounts apply as for on-ramp and off-ramp transactions:
| Currency | Minimum | Maximum |
| - | - | - |
| MXN | 50 MXN | No maximum |
| DOP | 150 DOP | No maximum |
| USD | 20 USD | No maximum |
| EUR | 20 EUR | No maximum |
USD delivered to **China (CN)** or **Hong Kong (HK)** is limited to a minimum of **10 USD** and a maximum of **10,000 USD** per transaction.
## Funding a Cross-Ramp
After creating the transaction, the response includes `sourceBankAccount` with the bank details where the user must deposit the source fiat currency. The transaction starts in `PENDING_FUNDS` status.
* For **MXN source**: The user deposits via SPEI to Capa's bank account (same flow as on-ramp).
* For **non-MXN source** (USD, EUR, DOP): A deposit intent is created with instructions for the user to include in their transfer.
The source fiat deposit **must be made after** the cross-ramp transaction is created. The deposit is matched to the cross-ramp order internally — there is no concepto/reference matching mechanism for cross-ramps.
### Multiple Concurrent Orders
You can have multiple cross-ramp transactions open at the same time for a single user.
## Quoting and Premium Spread
When using `premiumSpread`, note that if a `quoteId` is provided in the cross-ramp request, the **quote's `premiumSpread` is used** and any `premiumSpread` value in the request body is ignored. To ensure consistent pricing, always set `premiumSpread` when creating the quote. The valid range is `0` to `1`.
A locked quote is valid for **10 seconds** after creation (`expiresAt` field in the response). Once the quote is used to create a transaction, the rate is locked for that transaction.
A quote is **optional** — if no `quoteId` is provided, provide `sourceAmount` or `targetAmount` along with `sourceCurrency` and `targetCurrency`. You cannot specify both `sourceAmount` and `targetAmount`.
When using `quoteId`, do not pass `sourceAmount`, `targetAmount`, `sourceCurrency`, or `targetCurrency` — these values are taken from the quote.
## Transaction Status Progression
```mermaid theme={null}
stateDiagram-v2
[*] --> PENDING_FUNDS
PENDING_FUNDS --> FUNDS_RECEIVED : Source fiat deposited
PENDING_FUNDS --> CANCELLED : Cancelled
FUNDS_RECEIVED --> IN_PROGRESS : Processing conversion
IN_PROGRESS --> AWAITING_FUND_TRANSFER : Conversion complete
AWAITING_FUND_TRANSFER --> COMPLETED : Target fiat delivered
```
| Status | Description |
| - | - |
| `PENDING_FUNDS` | Transaction created, waiting for the user to deposit source fiat |
| `FUNDS_RECEIVED` | Source fiat has been received |
| `IN_PROGRESS` | Conversion is being processed |
| `AWAITING_FUND_TRANSFER` | Conversion complete, awaiting delivery to target bank account |
| `COMPLETED` | Target fiat has been delivered to the bank account |
| `CANCELLED` | Transaction was cancelled (can occur at `PENDING_FUNDS` stage) |
### Sandbox Testing
In the sandbox environment you can drive a cross-ramp through this progression without making a real deposit, by creating it with a magic `sourceAmount`: `2000.01` walks the full lifecycle to `COMPLETED`, `2000.02` cancels with `NAME_VERIFICATION_FAILED`, and `2000.03` halts at `AWAITING_FUND_TRANSFER` so you can exercise the payout leg.
Only `sourceAmount` triggers a mock — the `targetAmount` path never matches. See the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide) for the full rules.
## Notifications
Capa sends webhook notifications for each status change. The following events are emitted during a cross-ramp transaction lifecycle:
| Event | Triggered When |
| - | - |
| `CREATED_CROSS_RAMP` | Transaction created |
| `FUNDS_RECEIVED_CROSS_RAMP` | Source fiat received |
| `STARTED_PROCESSING_CROSS_RAMP` | Conversion processing begins |
| `AWAITING_FUND_TRANSFER_CROSS_RAMP` | Awaiting delivery to target bank |
| `COMPLETED_CROSS_RAMP` | Target fiat delivered |
| `CANCELLED_CROSS_RAMP` | Transaction cancelled |
See the [Transaction Events](/docs/transaction-events#cross-ramp-events) guide for full payload examples. Ensure your [Webhook Settings](/reference/partnerwebhooksettingsv2controller_update) are configured to receive these notifications.
***
## What's Next
* [Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp)
* [Cross-Ramp Quote Rate](/reference/crossramppartnerv2controller_getpartnercrossrampquoterate)
* [Transaction Events](/docs/transaction-events)
# ❓ FAQ
Source: https://docs.capa.fi/docs/faq
Frequently asked questions about integrating with the Capa API.
***
# Frequently Asked Questions
## KYC & User Verification
1. **Create the User** via the [Create User](/reference/userpartnercontrollerv2_createuser) endpoint.
2. **Verify the User** using one of three options:
* **Option A** — Redirect to a KYC verification link via [Generate KYC Link](/reference/userpartnercontrollerv2_createverificationlink)
* **Option B** — Submit identity documents directly via [Submit KYC Data](/reference/userpartnercontrollerv2_createuserkyc)
* **Option C** — For business users, generate a KYB link via [Generate KYB Link](/reference/userpartnercontrollerv2_generatekybverificationlink)
3. **Get the result** via [webhooks](/docs/kyc-events) (`VERIFIED_USER`, `REJECTED_KYC`, `REVIEW_NEEDED_KYC`) or by [polling](/reference/kycpartnercontrollerv2_getuserverificationdetails).
The `documents` field in the KYC endpoint is for `ADDRESS_PROOF` only — government ID front/back goes in the `identity` object (`frontIdUrl`, `backIdUrl`). The selfie is a separate top-level `selfieUrl` field.
For the **direct KYC API** (`identity.type`), the accepted values are `GOVERNMENT_ID`, `RESIDENT_CARD`, and `TAX_ID` for all countries.
For the **file-based KYC upload** flow, the accepted document types are `NATIONAL_ID`, `VOTER_ID`, `PASSPORT`, and `TAX_ID` for all countries including Mexico.
The `taxId` field expects an **RFC** (Registro Federal de Contribuyentes), not a CURP. The format must match:
```
^[A-Z]{4}\d{6}[A-Z0-9]{3}$
```
Example: `LOZG7802117B9`
CURP is **not** accepted.
No. The `documents` array with `ADDRESS_PROOF` is required only if the identity document used is **not** an INE. If the user submits an INE and their address matches their current address, `ADDRESS_PROOF` can be omitted.
| File | Max Size | Allowed Formats |
| - | - | - |
| Selfie | 3 MB | JPEG, JPG, PNG |
| Front ID / Back ID | 5 MB each | JPEG, JPG, PNG, PDF |
| Additional documents | 10 MB | JPEG, JPG, PNG, PDF |
All URLs must be publicly accessible or pre-signed with a **minimum TTL of 30 minutes**. Short-lived signed URLs (a few seconds) will fail with `FAILED_TO_DOWNLOAD_FILE_FROM_PRESIGNED_URL`.
No. In the sandbox environment, KYC verification does not perform real identity checks. You can submit random or test data — documents do not need to be genuine.
There are approximately 150 valid values. See the full list in the [Submit KYC Data API reference](/reference/userpartnercontrollerv2_createuserkyc). Common examples include `DESARROLLO DE SOFTWARE`, `CONSULTORIA`, `FINANCIERA`, `EDUCACION`, `RESTAURANTE`.
These are optional MX-only fields. They are only needed when the person performing the transaction is **different from the user** — for example, if someone else is the beneficial owner of the funds. If the user is both the sender and the recipient, these fields can be omitted.
All countries listed in the API spec are live: MX, DO, US, and EU/SEPA countries (AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, IS, LI, NO, CH, GB, MC, SM, AD, VA). CN (China) is coming soon for off-ramps only.
## User Management
The API returns a `409 Conflict` with error code `USER_ALREADY_EXISTS_ERROR`. Check for this status code to avoid duplicate user creation.
```json theme={null}
{
"status": "failed",
"code": "USER_ALREADY_EXISTS_ERROR",
"message": "A user with this email or externalUserId already exists"
}
```
There are currently no API endpoints to update or delete users. For user deletion requests, contact [derechosarco@capa.fi](mailto:derechosarco@capa.fi).
## Webhooks
Failed webhook deliveries are retried up to **5 times** with the following schedule:
| Attempt | Delay |
| - | - |
| 1st retry | Immediate |
| 2nd retry | 1 minute |
| 3rd retry | 5 minutes |
| 4th retry | 10 minutes |
| 5th retry | 15 minutes |
If all attempts fail, the notification is marked as "Failed." Return a `2xx` status code promptly to acknowledge receipt.
There is currently no API endpoint or dashboard feature to replay missed webhook events. As a fallback, poll the [List Transactions](/reference/transactionpartnerv2controller_listpartnerusertransactions) or [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails) endpoints to check the latest status.
No. Each partner can register a **single webhook URL** per environment. Use the [Webhook Settings](/reference/partnerwebhooksettingsv2controller_update) endpoint to update it.
Yes. If a webhook URL is configured in the sandbox environment, webhooks will fire during mock testing, allowing you to test your webhook handling end-to-end.
Yes. When you receive the `COMPLETED_ON_RAMP` event, the crypto has been sent and is already in the user's destination wallet.
`FIAT_RECEIVED_ON_RAMP` means that fiat has been received and the conversion to crypto has **started** — but the crypto has **not been sent yet**. Wait for `COMPLETED_ON_RAMP` to confirm delivery.
No. If a user is already verified, re-submitting KYC does not trigger a new webhook event.
Two events fire when bank account verification completes:
* `VERIFIED_BANK_ACCOUNT` — The bank account was successfully verified.
* `REJECTED_BANK_ACCOUNT` — The bank account verification was rejected.
These use `type: "USER"` and include the `userBankInfoId` in the payload.
## Quotes & Transactions
A locked quote is valid for **10 seconds** after creation (see the `expiresAt` field in the response). Once a quote is used to create a transaction, the rate is **locked** for that transaction regardless of the 10-second window.
No. The `quoteId` parameter is optional when creating an on-ramp, off-ramp, or cross-ramp transaction. If no quote is provided, the exchange rate is calculated at the time of funding. A quote is useful for showing the user a guaranteed rate before they commit.
Yes. The [Create Quote](/reference/quotepartnerv2controller_createpartnerquote) endpoint accepts either `fiatAmount` or `cryptoAmount`. Use `fiatAmount` to get how much crypto the user will receive, or `cryptoAmount` to get how much fiat is needed.
When a `quoteId` is provided in the transaction request, the **quote's `premiumSpread`** is used and any `premiumSpread` in the transaction request body is ignored. Always set `premiumSpread` when creating the quote to ensure consistent pricing. The valid range is `0` to `1` (representing 0% to 100%).
| Currency | Minimum | Maximum |
| - | - | - |
| MXN | 50 MXN | No maximum |
| DOP | 150 DOP | No maximum |
| USD | 20 USD | No maximum |
| EUR | 20 EUR | No maximum |
These limits may change. There is no per-partner variation.
No. Once a transaction is created, it does not have an expiration time or `expires_at` field. Transactions can only be cancelled via the [Cancel Transaction](/reference/transactionpartnerv2controller_cancelpartnertransaction) endpoint or by an admin.
No. The API does not currently support idempotency keys. Sending the same request twice will create two separate transactions. Implement safeguards in your integration to prevent duplicate submissions.
Fiat amounts are rounded to **2 decimal places**. Crypto amounts are rounded to **6 decimal places**. If a partner sends a ceiling-rounded crypto amount on an off-ramp, the difference remains in the user's wallet.
## On-Ramp
For Mexico, a dedicated CLABE (bank account identifier) is **automatically created** when the user's KYC is verified — no separate API call is needed. The CLABE is returned in the on-ramp creation response under `bankAccount.accountIdentifier` and can also be retrieved via the [Get Bank Info](/reference/userbankinfopartnerv2controller_getuserbankinfosbyuserid) endpoint.
Yes. The CLABE is permanent and reusable across all on-ramp transactions for the user. It does not expire or change unless the user is deactivated or the partner migrates banks.
Yes. An on-ramp transaction **must be created before** making the fiat deposit. Deposits received without a matching active on-ramp order will be refunded at end of business day.
For MXN (SPEI) deposits, include the **transaction ID** (with or without dashes) in the SPEI **concepto** field. This matches the deposit to the correct order. If no transaction ID is in the concepto, the deposit is matched by **amount + CLABE**. If multiple orders share the same amount, the oldest order is matched first. If neither method matches an open order, the deposit is refunded at end of business day. For DOP, USD, and EUR deposits, matching is handled internally by Capa.
Yes. Multiple on-ramp transactions can be created simultaneously for a single user.
No. The CLABE alone is sufficient. The account holder name auto-populates in the sender's banking app when they enter the CLABE.
## Off-Ramp
The excess crypto remains in the user's wallet and is not included in the off-ramp transaction.
For MXN off-ramp payouts via SPEI:
* Under 50,000 MXN: \~2–3 minutes
* Over 50,000 MXN: 10–30 minutes
For forwarded OTC quotes using `forwardingDays` (1–4), the payout is scheduled on settlement day T+N; the 10–30 minute window applies once that day arrives.
Yes. The off-ramp fiat payout includes the **sanitized transaction ID** (with dashes, the `-` character, removed) in the SPEI concepto field, allowing you to match payouts to specific off-ramp orders. For example, a transaction ID of `f51f2332-c7d4-40eb-8fb2-11e1608bc24c` appears in the concepto as `f51f2332c7d440eb8fb211e1608bc24c`.
This error means the Execute Off-Ramp feature has not been enabled for your partner account. This feature is required for forwards-enabled partners. Contact the Capa development team to request enablement.
Forwards allow partners to lock a rate now and settle the transaction T+N days later. Use the `forwardingDays` parameter (0–4) when creating a locked quote for MXN OTC orders. This feature requires the manual off-ramp execution flow to be enabled — all off-ramps must be explicitly executed via the [Execute Off-Ramp](/reference/offramppartnerv2controller_executeofframp) endpoint.
## Rate Limits & Errors
All API endpoints are rate-limited to **10 requests per second** per partner. Exceeding this limit returns a `429 Too Many Requests` response. The limits are the same across all endpoints. Rate limit information is not included in response headers.
All errors follow this structure:
```json theme={null}
{
"status": "failed",
"code": "INVALID_USER_INPUT",
"message": "Description of what went wrong",
"errors": [
{ "field": "email", "message": "must be a valid email" }
]
}
```
Common HTTP status codes: `400` (validation), `401` (unauthorized), `403` (forbidden), `404` (not found), `409` (conflict/duplicate), `429` (rate limit).
## Sandbox & Testing
| Aspect | Sandbox | Production |
| - | - | - |
| Fiat transfers | Simulated (no real money) | Real SPEI/ACH/SEPA transfers |
| Crypto transfers | Real on-chain transactions | Real on-chain transactions |
| KYC verification | Not validated (random data accepted) | Full identity verification |
| Exchange rates | May differ from live market | Live market rates |
| Mock testing | Enabled | Disabled |
Use the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide) to simulate transaction flows with special amounts — `1000.0x` for on-ramp and off-ramp, and `2000.0x` for cross-ramp. Webhooks fire during mock testing if a webhook URL is configured.
## Refunds
No. There is no self-service refund endpoint. If a refund is needed (e.g., a deposit without a matching on-ramp order), Capa's FX agents will get in contact to handle the refund process.
## KYB & Third-Party Payments
KYB is available as a **link-based flow only**. Use the [Generate KYB Link](/reference/userpartnercontrollerv2_generatekybverificationlink) endpoint to create a verification link for business users. Full API-driven KYB is not available due to the complexity of MX regulations.
# 🚀 Getting Started
Source: https://docs.capa.fi/docs/getting-started
Crypto and fiat rails connecting users to global finance
***
## Overview
Capa's API enables fintechs and platforms to integrate seamless cryptocurrency on-ramping, off-ramping, and fiat-to-fiat cross-ramp transfers.
Users can exchange fiat currencies for cryptocurrencies and vice versa, or convert between different fiat currencies, with just a few simple steps. Capa currently supports MXN (Mexican Peso), DOP (Dominican Peso), USD (US Dollar), and EUR (Euro).
## Integration Flow
The following steps apply to every integration regardless of which transaction types you plan to support.
Sign up at [sandbox.capa.fi](https://sandbox.capa.fi), then go to **Settings > Dev Tools** and click **Generate API Key** to get your sandbox API key -- no onboarding required. For production, use the [partner dashboard](https://dashboard.capa.fi) and follow the same path. See the [API Key Guide](/docs/partner-api-key-generation) for details on usage and regeneration.
Register your webhook URL via the [Webhook Settings](/reference/partnerwebhooksettingsv2controller_update) endpoint and implement [HMAC signature validation](/docs/step-by-step-process-for-hmac-signature-validation) to verify incoming events. See [Webhooks](/docs/how-our-webhooks-work) for retry behavior and best practices.
[Create a user](/reference/userpartnercontrollerv2_createuser) with `externalUserId`, `email`, and `identifier`, then complete identity verification via [KYC link](/reference/userpartnercontrollerv2_createverificationlink), [direct KYC submission](/reference/userpartnercontrollerv2_createuserkyc), or [KYB link](/reference/userpartnercontrollerv2_generatekybverificationlink) for businesses. See the [User Creation and Verification](/docs/create-and-verify-a-user-with-capa-api) guide.
For off-ramp and cross-ramp transactions, provide destination bank details inline or save them via the [Bank Accounts](/docs/bank-accounts) API.
Use [Get Quote Rate](/reference/quotepartnerv2controller_getpartnerquoterate) for a real-time estimate, or [Create Quote](/reference/quotepartnerv2controller_createpartnerquote) to lock a guaranteed exchange rate for 10 seconds. Quoting works the same way across on-ramp, off-ramp, and cross-ramp transactions. For cross-ramp, use the dedicated [Cross-Ramp Quote](/reference/crossramppartnerv2controller_createpartnercrossrampquote) endpoints.
With a verified user (and optionally a locked quote), create the transaction for your use case:
* **On-Ramp** (fiat to crypto): [Create On-Ramp](/reference/onramppartnerv2controller_createpartneronramp) -- see the [On-Ramp Guide](/docs/on-ramp)
* **Off-Ramp** (crypto to fiat): [Create Off-Ramp](/reference/offramppartnerv2controller_createpartnerofframp) -- see the [Off-Ramp Guide](/docs/off-ramp)
* **Cross-Ramp** (fiat to fiat): [Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp) -- see the [Cross-Ramp Guide](/docs/cross-ramp)
To safely retry a failed or timed-out request without creating a duplicate transaction, include an `Idempotency-Key` header. See the [Idempotency Guide](/docs/idempotency).
Receive real-time status updates via [Transaction Events](/docs/transaction-events) and [KYC Events](/docs/kyc-events) webhooks. You can also [list transactions](/reference/transactionpartnerv2controller_listpartnerusertransactions) or [cancel a transaction](/reference/transactionpartnerv2controller_cancelpartnertransaction) as needed. See the [Transactions Guide](/docs/transactions-1).
### How It All Fits Together
```mermaid theme={null}
sequenceDiagram
participant Partner
participant Capa
participant User
Partner->>Capa: Authenticate (partner-api-key header)
Partner->>Capa: Create User
Partner->>Capa: Start KYC / KYB Verification
User->>Capa: Complete identity verification
Capa->>Partner: Webhook (VERIFIED_USER or REJECTED_KYC)
Partner->>Capa: Create Quote (optional)
Capa->>Partner: quoteId + locked rate (expires in 10s)
Partner->>Capa: Create Transaction (on/off/cross-ramp)
Capa->>Partner: Transaction details + bank/wallet info
User->>Capa: Deposit fiat or send crypto
Capa->>Partner: Webhook (status updates)
Capa->>Partner: Webhook (COMPLETED)
```
### Key Things to Know
* **KYC before transactions** -- A user must be fully verified before you can create any transaction for them.
* **Quotes are optional but short-lived** -- A locked quote expires after **10 seconds**. If no quote is used, the rate is calculated at the time of funding.
* **Premium spread** -- If a `quoteId` is provided, the quote's `premiumSpread` takes precedence over any value in the transaction request. Set it when creating the quote.
* **Webhook reliability** -- Return a `2xx` response promptly. Failed deliveries are retried up to 5 times. As a fallback, poll the [List Transactions](/reference/transactionpartnerv2controller_listpartnerusertransactions) or [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails) endpoints.
* **Idempotency keys** -- Use the `Idempotency-Key` header on transaction-creation `POST` requests to safely retry without creating duplicates. See the [Idempotency Guide](/docs/idempotency).
### Production Readiness Checklist
Before going live, make sure you have:
* [ ] Webhook URL registered and [HMAC signature validation](/docs/step-by-step-process-for-hmac-signature-validation) implemented
* [ ] User creation + KYC/KYB flow tested end-to-end in sandbox
* [ ] At least one transaction of each type you support tested in sandbox
* [ ] Error handling for common error codes (`400`, `401`, `403`, `404`, `409`, `429`)
* [ ] Safeguards against duplicate submissions (no idempotency key support)
* [ ] Rate limiting handled (10 requests per second per partner)
## API Environments
| Environment | Base URL | Purpose |
| - | - | - |
| **Sandbox** | `https://api.sandbox.capa.fi` | Testing and development |
| **Production** | `https://api.capa.fi` | Live transactions with real funds |
### Sandbox vs Production
| Aspect | Sandbox | Production |
| - | - | - |
| Fiat transfers | Simulated (no real money moves) | Real SPEI/ACH/SEPA transfers |
| Crypto transfers | Real on-chain transactions | Real on-chain transactions |
| KYC verification | Not validated (random data accepted) | Full identity verification |
| Exchange rates | May differ from live market | Live market rates |
| Mock testing | Enabled ([see mock guide](/reference/transactionpartnerv2controller_getmocktestingguide)) | Disabled |
### Rate Limits
All API endpoints are rate-limited to **10 requests per second** per partner. Exceeding this limit returns a `429 Too Many Requests` response. Rate limit information is not included in response headers.
### Error Response Format
All error responses follow this structure:
```json theme={null}
{
"status": "failed",
"code": "INVALID_USER_INPUT",
"message": "Description of what went wrong",
"errors": [
{ "field": "email", "message": "must be a valid email" }
]
}
```
Common error codes:
| HTTP Status | Code | Description |
| - | - | - |
| 400 | `INVALID_USER_INPUT` | Validation error (missing or invalid fields) |
| 401 | `UNAUTHORIZED` | Invalid or missing API key |
| 403 | `FORBIDDEN` | Action not allowed for this partner or user |
| 404 | `NOT_FOUND` | Resource not found |
| 409 | `USER_ALREADY_EXISTS_ERROR` | User with this email or externalUserId already exists |
| 429 | — | Rate limit exceeded |
### Idempotency
The API does not currently support idempotency keys. Sending the same request twice will create two separate resources (e.g., two transactions). Ensure your integration includes safeguards to prevent duplicate submissions.
### API Status
Monitor API availability at [status.capa.fi](https://status.capa.fi).
## 🔌 Our API
### Buy and Sell Cryptocurrency
Allow your users to purchase and sell digital assets such as USDC directly through our API, using supported fiat currencies including MXN, DOP, USD, and EUR.
### Cross-Border Fiat Transfers
Enable your users to convert between fiat currencies (e.g., MXN to USD) without touching crypto. Capa handles the conversion and delivers funds to the target bank account.
### Instant Transfers and Fast Settlement
With Capa, you can eliminate long wait times for international transfers. Our platform offers near-instant settlement for both crypto and fiat transactions, ensuring users can transact quickly and efficiently.
### Local Banking and Crypto Integration
Capa provides a smooth integration between banking infrastructure and the world of cryptocurrencies. Your users will be able to deposit fiat currencies, which can be instantly converted to digital assets, and they can withdraw their crypto to fiat with ease.
***
## What's Next
* [🔑 API Key Generation Guide](/docs/partner-api-key-generation)
* [👤 User Creation and Verification](/docs/create-and-verify-a-user-with-capa-api)
* [💎 On-Ramp Guide](/docs/on-ramp)
* [💵 Off-Ramp Guide](/docs/off-ramp)
* [🔄 Cross-Ramp Guide](/docs/cross-ramp)
* [📣 Webhooks](/docs/how-our-webhooks-work)
* [❓ FAQ](/docs/faq)
# 📣 Webhooks
Source: https://docs.capa.fi/docs/how-our-webhooks-work
For any notifications related to transactions status and KYC status partners can make use of the webhooks
***
### 📢 Overview
The Capa API provides a webhook-based notification system that allows third-party partners to receive real-time updates whenever specific events occur in our backend systems. This enables partners to stay informed about changes related to their users or transactions and to make necessary updates in their systems accordingly.
### 🔧 How It Works
1. **🔗 Registering a Webhook URL**:\
Each partner can register a single URL where they wish to receive webhook notifications. This URL must be provided through the designated endpoint. [Set Webhook Settings](/reference/partnerwebhooksettingsv2controller_update)
2. **📬 Receiving Notifications**:\
Once a URL is registered, partners will receive notifications whenever an event relevant to them occurs, such as:
* 📝 Updates to a user's KYC (Know Your Customer) status.
* 💸 Transaction status changes
3. **📡 Notification Handling**:\
If a notification fails to be delivered (e.g., due to network issues or a problem on the partner's end), the system will automatically retry the delivery up to **5 times** with the following delays:
| Attempt | Delay |
| - | - |
| 1st retry | Immediate |
| 2nd retry | 1 minute |
| 3rd retry | 5 minutes |
| 4th retry | 10 minutes |
| 5th retry | 15 minutes |
If all attempts fail, the notification will be marked as "Failed." You can [list failed notifications](/reference/partnerwebhooknotificationv2controller_listwebhooknotifications), inspect [full delivery details and error payloads](/reference/partnerwebhooknotificationv2controller_getwebhooknotificationdetail), and [manually trigger a retry](/reference/partnerwebhooknotificationv2controller_retry) — up to 3 manual retries per notification. As a fallback, use the [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails) or [List Transactions](/reference/transactionpartnerv2controller_listpartnerusertransactions) endpoints to poll for the latest status.
4. **🧪 Mock Testing**:\
Webhooks **do fire** during mock testing in the sandbox environment, as long as a webhook URL is configured. This allows you to test your webhook handling end-to-end using the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide).
### 🌐 Source IP Addresses
Webhook events are delivered from the following static IP addresses. If your webhook endpoint is behind a firewall or IP allowlist, add these to your allowed inbound list.
| Environment | IP Addresses |
| - | - |
| **Sandbox** | `44.209.202.213`, `98.86.19.115` |
| **Production** | `100.59.133.128`, `34.230.206.95`, `52.72.151.133` |
### 💡 Best Practices
* **🕒 Ensure Availability**: Make sure your webhook URL is always available to receive notifications.
* **🤖 Handle Retries Gracefully**: Implement logic to handle repeated notifications in case of retries.
* **🔍 Validate Data**: Validate all incoming data to ensure it is from a trusted source and in the expected format.
* **✅ Respond with 2xx**: Return a `200` or `2xx` status code promptly to acknowledge receipt. A non-2xx response triggers retries.
***
What’s Next
* [Transaction Events](/docs/transaction-events)
* [KYC Events](/docs/kyc-events)
* [Webhook Settings](/reference/partnerwebhooksettingsv2controller_update)
* [List Webhook Notifications](/reference/partnerwebhooknotificationv2controller_listwebhooknotifications)
* [Retry a Failed Notification](/reference/partnerwebhooknotificationv2controller_retry)
# 🔁 Idempotency
Source: https://docs.capa.fi/docs/idempotency
Safely retry transaction-creation requests without duplicating operations using the Idempotency-Key header.
***
### Overview
When creating a transaction, a network drop, client timeout, or unexpected `5xx` response can leave you unsure whether the operation was processed. Retrying the request without a safeguard can result in duplicate transactions.
The `Idempotency-Key` header lets you retry any transaction-creation `POST` safely. If Capa receives a request with a key it has already seen, it replays the original response — no duplicate transaction is created. The header is **opt-in**: if omitted, the request is processed normally with no idempotency guarantee.
***
### Supported Endpoints
The header is recognized on the following `POST` endpoints only. All other endpoints ignore it.
* [`POST /api/partner/v2/on-ramp`](/reference/onramppartnerv2controller_createpartneronramp)
* [`POST /api/partner/v2/off-ramp`](/reference/offramppartnerv2controller_createpartnerofframp)
* [`POST /api/partner/v2/cross-ramp`](/reference/crossramppartnerv2controller_createpartnercrossramp)
***
### How It Works
1. **Generate a unique `Idempotency-Key`** (UUIDv4 recommended) for each logical operation and include it as an HTTP header.
2. **Capa processes the request** and caches the response under the `(userId, key)` pair.
3. **On retry**, send the **identical request body** with the **same key**. Capa detects the duplicate and returns the original response — the handler is not invoked again.
4. **Failed originals can be retried.** If the first attempt resulted in an error (non-2xx), the record is marked `FAILED` and a subsequent request with the same key reprocesses normally.
5. **The guarantee window is 48 hours.** After that the key expires, the record is reclaimed, and the next request is treated as a new operation. It is the caller's responsibility to avoid duplicates beyond this window.
***
### Header Reference
| Field | Value |
| - | - |
| Name | `Idempotency-Key` |
| Required | No (opt-in) |
| Format | 16–128 printable ASCII characters |
| Recommended value | UUIDv4 |
| Scope | `(userId, idempotencyKey)` — scoped per user, not per endpoint |
***
### Behavior Matrix
| Scenario | Behavior | HTTP Status |
| - | - | - |
| New key | Request is processed; response cached under key | Handler's status (2xx) |
| Same key + same body, original completed | Cached response replayed; handler not invoked | Original status |
| Same key + same body, original in-flight | Request rejected — wait and retry | `409 Conflict` |
| Same key + **different** body | Rejected — request body hash mismatch | `422` |
| Same key + original failed (non-2xx) | Record marked `FAILED`; request reprocessed | Handler's status |
| Key older than 48h | Record reclaimed; request treated as new | Handler's status (2xx) |
***
### Errors
| HTTP Status | Code | Message |
| - | - | - |
| `400` | `INVALID_USER_INPUT_ERROR` | Malformed `Idempotency-Key` header (format violation) |
| `409 Conflict` | — | Same key is already being processed; retry after a short delay |
| `422` | `INVALID_USER_INPUT_ERROR` | `Idempotency-Key was already used with a different request body` |
***
### Best Practices
* **Generate a fresh UUIDv4 per logical operation.** Never hardcode or reuse keys across distinct operations.
* **Persist the key** client-side until you receive a confirmed terminal response for that operation.
* **Retry with the identical body.** Changing any field in the request body is treated as a different operation and results in a `422`.
* **Do not share a key across endpoints** for the same user. Keys are scoped to `(userId, key)`, so the same key on a different endpoint collides on a body hash mismatch.
* **Omit the header on `GET`, `PUT`, `PATCH`, and `DELETE` requests** — it has no effect on those methods.
* **Plan for the 48-hour window.** Retries after expiry are reprocessed as new requests; implement your own deduplication logic for long-lived operations.
***
### Example
```shell theme={null}
curl --request POST \
--url https://api.sandbox.capa.fi/api/partner/v2/on-ramp \
--header 'Content-Type: application/json' \
--header 'partner-api-key: ' \
--header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
--data '{
"userId": "user_abc123",
"fiatCurrency": "MXN",
"blockchainSymbol": "ETH",
"tokenSymbol": "USDC",
"fiatAmount": 500,
"destinationWalletAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
}'
```
If this request times out or fails at the network level, resend the **exact same body** with the **same `Idempotency-Key`**. Capa will return the original response without creating a second transaction.
# 🔍 KYC Events
Source: https://docs.capa.fi/docs/kyc-events
KYC events are notifications sent to partners when there are updates or changes in the user's identity verification process. These notifications help partners stay informed about the status of their users' verification and take appropriate actions in their systems.
***
## Types of KYC Events
1. **`CREATED_KYC`(Created KYC):**
* **Description:** This event is triggered when a user has successfully uploaded all the information on our provider and the KYC has been created.
* **Example Payload:**
```json JSON theme={null}
{
"type": "KYC",
"event": "CREATED_KYC",
"externalUserId": "61561e29-cb8f-475a-b913-7d70430badfe",
}
```
2. **`VERIFIED_USER`(Verified User):**
* **Description:** This event is triggered when a user has successfully completed the KYC process and has been verified.
* **Use Case:** The partner can update their system to mark the user as verified, allowing the user to access certain features or services that require a verified status.
* **Example Payload:**
```json JSON theme={null}
{
"type": "KYC",
"event": "VERIFIED_USER",
"externalUserId": "61561e29-cb8f-475a-b913-7d70430badfe",
}
```
3. **`REJECTED_KYC`(KYC Rejected):**
* **Description:** This event is triggered when a user's KYC process has been completed, but the user has been rejected due to failing the verification checks (e.g., invalid documents, mismatched information).
* **Use Case:** The partner may notify the user that their verification has failed and guide them on the next steps to retry or provide additional information.
* **Example Payload:**
```json JSON theme={null}
{
"type": "KYC",
"event": "REJECTED_KYC",
"externalUserId": "61561e29-cb8f-475a-b913-7d70430badfe",
}
```
4. **`REVIEW_NEEDED_KYC`(Review Needed KYC):**
* **Description:** This event is triggered when the KYC verification process is incomplete, and additional review or manual intervention is needed.
* **Use Case:** Capa compliance team might need to manually review certain documents or data provided by the user to complete the verification process.
* **Example Payload:**
```json JSON theme={null}
{
"type": "KYC",
"event": "REVIEW_NEEDED_KYC",
"externalUserId": "61561e29-cb8f-475a-b913-7d70430badfe",
}
```
### Summary of KYC Events
These KYC events help partners automate their response to changes in user verification status:
* **`VERIFIED_USER`**: Confirms that a user has been successfully verified.
* **`REJECTED_KYC`**: Alerts the partner that a user’s verification has failed.
* **`REVIEW_NEEDED_KYC`**: Indicates that additional action is needed to complete the verification process.
By processing these events, partners can ensure that their systems are up-to-date with the latest user verification statuses and can take appropriate actions to maintain compliance and provide the correct level of access to their users.
# 🤖 Model Context Protocol
Source: https://docs.capa.fi/docs/mcp-server
Connect your AI tools to Capa's documentation
***
## About MCP Servers
The Model Context Protocol (MCP) is an open protocol that creates standardized connections between AI applications and external services, like documentation. Capa's MCP server enables AI tools to search your documentation directly instead of making generic web searches, providing more accurate and up-to-date information about Capa's API, features, and integration guides.
When an AI tool has your documentation MCP server connected:
* The AI can proactively search your documentation while generating a response, not just when explicitly asked
* The AI determines when to use the search tool based on the context of the conversation and the relevance of your documentation
* Each tool call happens during the generation process, so the AI searches up-to-date information from your documentation
## Capa's MCP Server
Capa automatically generates an MCP server for your documentation and hosts it at:
**MCP Server URL**: `https://docs.capa.fi/mcp`
This MCP server provides access to all indexed content on the Capa documentation site, including:
* API references and endpoints
* Integration guides and tutorials
* Webhook documentation
* Code examples and best practices
## Connect to Your AI Tool
Your users must connect Capa's MCP server to their preferred AI tools. Follow the instructions below for your specific tool.
### Cursor
To connect Capa's MCP server to Cursor:
1. **Open MCP settings**
1. Use Command + Shift + P (Ctrl + Shift + P on Windows) to open the command palette
2. Search for "Open MCP settings"
3. Select **Add custom MCP**. This opens the `mcp.json` file
2. **Configure the Capa MCP server**
In `mcp.json`, add:
```json JSON theme={null}
{
"mcpServers": {
"Capa": {
"url": "https://docs.capa.fi/mcp"
}
}
}
```
3. **Test the connection**
In Cursor's chat, ask "What tools do you have available?" Cursor should show the Capa MCP server as an available tool.
### VS Code
To connect Capa's MCP server to VS Code:
1. **Create the configuration file**
1. Create a `.vscode/mcp.json` file in your workspace
2. **Configure the Capa MCP server**
In `mcp.json`, add:
```json JSON theme={null}
{
"servers": {
"Capa": {
"type": "http",
"url": "https://docs.capa.fi/mcp"
}
}
}
```
### Claude
To use Capa's MCP server with Claude:
1. **Add the Capa MCP server to Claude**
1. Navigate to the Connectors page in the Claude settings
2. Select **Add custom connector**
3. Add the Capa MCP server:
* Name: `Capa`
* URL: `https://docs.capa.fi/mcp`
4. Select **Add**
2. **Access the MCP server in your chat**
1. When using Claude, select the attachments button (the plus icon)
2. Select the Capa MCP server
3. Ask Claude a question about Capa
### Claude Code
To use Capa's MCP server with Claude Code, run the following command:
```bash bash theme={null}
claude mcp add --transport http Capa https://docs.capa.fi/mcp
```
Test the connection by running:
```bash bash theme={null}
claude mcp list
```
## Using Multiple MCP Servers
Users can connect multiple MCP servers to their AI tools. Connected MCP servers do not consume context until the AI calls a search tool. The AI decides when to search based on query relevance, so it doesn't search every connected server for every question.
Best practices for using multiple MCP servers:
* Connect only the MCP servers relevant to your current work
* Be specific in your prompts so the AI searches the most relevant server
* Disconnect servers you're not actively using to reduce potential context usage
***
What's Next
* [🚀 Getting Started](/docs/getting-started)
* [🔑 API Key Guide](/docs/partner-api-key-generation)
# 💵 Off-Ramp
Source: https://docs.capa.fi/docs/off-ramp
The Off-Ramp feature provided by Capa allows users to sell cryptocurrencies to get fiat currencies. Capa supports off-ramp operations for MXN (Mexican Peso) and DOP (Dominican Peso).
***
## Integration Flow
[Create User](/reference/userpartnercontrollerv2_createuser), then [KYC verification](/reference/userpartnercontrollerv2_createverificationlink)
[Create Quote](/reference/quotepartnerv2controller_createpartnerquote) to lock the exchange rate
[Create Off Ramp](/reference/offramppartnerv2controller_createpartnerofframp)
The user sends crypto to the wallet address returned in the transaction response
## How to do an Off-Ramp Operation
To successfully execute an off-ramp operation, follow these steps:
1. ### Select the Blockchain and Token:
1. The following is a list of the Blockchains and Tokens
2. The list of Blockchains - Tokens symbols supported in Capa are:
1. POL - USDC, USDT
2. ETH - USDC
3. SOL - USDC
4. BSC - USDT (available only for transactions over 50,000 MXN)
2. ### Select the Fiat Currency:
1. Choose the supported fiat currency for the transaction. Capa currently supports the following fiat currencies:
1. MXN (Mexican Peso)
2. DOP (Dominican Peso)
3. ### Quoting:
1. To show a real-time estimate of how much fiat the user will get with the selected **Blockchain Token**, **Fiat Currency** and **Amount** use the [Get Quote Rate](/reference/quotepartnerv2controller_getpartnerquoterate) endpoint.
2. To lock a guaranteed exchange rate, use the [Create Quote](/reference/quotepartnerv2controller_createpartnerquote) endpoint. This returns a `quoteId` with an expiration time (`expiresAt`). Pass the `quoteId` when creating the transaction to use the locked rate.
3. You can specify either `fiatAmount` or `cryptoAmount` in the quote request.
4. **Forward Settlement (MXN OTC only):** For MXN orders above 50,000 MXN (OTC), the locked quote supports a `forwardingDays` parameter (0-4) that lets you lock the rate now but settle the transaction T+N days later.
4. ### User Bank Information:
1. The user bank information can be provided in multiple ways:
* **Inline bank details**: Pass `userBankInformation` directly in the off-ramp request body.
* **Saved bank account**: Use a previously saved bank account ID.
2. Bank account requirements vary by country:
| Country | Required Fields |
| - | - |
| **MX** | `accountIdentifier` (18-digit CLABE) |
| **DO** | `accountIdentifier`, `bankName`, `accountType`, `documentIdentifier`, `documentType`. [List of Dominican Republic Banks](/docs/off-ramp#dominican-republic-banks) |
3. For MX accounts, the bank information will go through a penny testing process to ensure that the KYC'd user is the actual owner. If the user is successfully verified the transaction will go through, otherwise it will be CANCELLED and the crypto will be reimbursed.
5. ### Obtain User Wallet Address:
After a successful KYC users get their own **Wallet Address** which is used as an intermediary to identify the user's off ramps on chain transaction. In order to show the user where the crypto must be sent to you must obtain the wallet by creating the Off Ramp transaction, the response would have the wallet address.
6. ### Execute the Off-Ramp:
With all required information gathered, you can proceed to execute the off-ramp operation using the Capa API. See the documentation to execute the off ramp [Create Off Ramp](/reference/offramppartnerv2controller_createpartnerofframp)
Key parameters:
* `userId` (required): The user's Capa ID
* `fiatCurrency`, `blockchainSymbol`, `tokenSymbol`: The selected currency, blockchain, and token (required when `quoteId` is not provided)
* `fiatAmount` or `cryptoAmount`: The amount to convert
* `userBankInformation` (optional): Inline bank account details
* `quoteId` (optional): A locked quote ID from the Create Quote endpoint
* `premiumSpread` (optional): A spread percentage to apply to the exchange rate
## Transaction Status Progression
```mermaid theme={null}
stateDiagram-v2
[*] --> PENDING_PAYMENT
PENDING_PAYMENT --> CRYPTO_RECEIVED : Crypto deposited
PENDING_PAYMENT --> CRYPTO_NOT_RECEIVED : Timeout
PENDING_PAYMENT --> CANCELLED : Cancelled
CRYPTO_RECEIVED --> IN_PROGRESS : Processing
IN_PROGRESS --> COMPLETED_CRYPTO_RECEIVAL : Crypto confirmed
COMPLETED_CRYPTO_RECEIVAL --> WAITING_FOR_FUNDS : Waiting
COMPLETED_CRYPTO_RECEIVAL --> CANCELLED : Cancelled
WAITING_FOR_FUNDS --> BANK_TRANSFER_PENDING : Preparing payout
BANK_TRANSFER_PENDING --> READY_BANK_TRANSFER : Ready
READY_BANK_TRANSFER --> COMPLETED : Fiat sent
```
## Settlement Times
Expected settlement times for MXN off-ramp fiat payouts:
| Amount | Expected Time |
| - | - |
| Under 50,000 MXN | \~2–3 minutes |
| Over 50,000 MXN | 10–30 minutes |
The times above apply to same-day settlements. For forwarded OTC quotes that use the `forwardingDays` parameter (1–4), the fiat payout is scheduled on settlement day T+N and the 10–30 minute window applies once that day arrives.
## Over-Funding
If a partner sends more crypto than the quoted amount, the excess remains in the user's wallet and is not included in the off-ramp transaction.
## SPEI Payout Reference
Off-ramp fiat payouts via SPEI include the **sanitized transaction ID** (with dashes, the `-` character, removed) in the concepto field, allowing partners to match payouts to specific off-ramp orders.
For example, a transaction ID of `f51f2332-c7d4-40eb-8fb2-11e1608bc24c` appears in the concepto as `f51f2332c7d440eb8fb211e1608bc24c`.
## Rounding
Fiat amounts are rounded to **2 decimal places**. Crypto amounts are rounded to **6 decimal places**. Any difference between the amount sent and the transaction amount stays in the user's wallet.
## Execute Off-Ramp (Forwards-Enabled Partners)
This section only applies to partners enabled for forwards who hold crypto in their wallet. To enable this feature, contact the Capa development team.
For partners with forwards enabled, off-ramp transactions are not immediately executed after creation. Instead, you must explicitly trigger processing by calling the [Execute Off-Ramp](/reference/offramppartnerv2controller_executeofframp) endpoint with the `transactionId`.
This call triggers the `CRYPTO_RECEIVED_OFF_RAMP` webhook event and starts the fiat payout process.
```json theme={null}
{
"transactionId": "8374f327-38bd-4b0b-b8a7-2524599eb903"
}
```
If you receive the error `"partner is not configured for manual off-ramp execution"`, this feature has not been enabled for your partner account. Contact the Capa team to request enablement.
## Available Platforms
* **Partner API**: Off-ramp operations are performed via the API using the Partner API key. For instructions on generating and using the API key, refer to the [API Key Generation Guide](/docs/partner-api-key-generation).
* **[Sandbox](https://sandbox.capa.fi)**: Test and manage off-ramp transactions, users, quoting, and webhook configuration in the sandbox environment.
* **[Dashboard](https://dashboard.capa.fi)**: Manage off-ramp transactions, users, quoting, and webhook configuration in production.
## Notifications
Capa sends notifications to keep you updated on transaction statuses:
* Webhooks for Partners: Capa posts updates to the configured partner webhooks to notify of transaction status changes.\
Email Notifications for Users (Optional): Users receive email notifications regarding their transaction status and other relevant updates. [Webhooks](/docs/how-our-webhooks-work)
* Ensure that your webhook settings are correctly configured to receive timely notifications and stay informed about the status of transactions. [Webhook Settings](/reference/partnerwebhooksettingsv2controller_update)
## Dominican Republic Banks
```json JSON theme={null}
[
"ABONAP",
"Alaver",
"Asociación Cibao",
"Asociación Duarte",
"Asociación La Nacional",
"Asociación Maguana",
"Asociación Mocana",
"Asociación Peravia",
"Asociación Popular",
"Asociación Romana",
"Bancamérica",
"Banco Activo",
"Banco Ademi",
"Banco Adopem",
"Banco Agrícola",
"Banco Atlántico",
"Banco BACC",
"Banco BDI",
"Banco BHD",
"Banco Caribe",
"Banco Cofaci",
"Banco Confisa",
"Banco Fihogar",
"Banco Gruficorp",
"Banco Lafise",
"Banco López de Haro",
"Banco Popular",
"Banco Promerica",
"Banco Santa Cruz",
"Banco Unión",
"Banco Vimenca",
"Banco Óptima",
"Bancotui",
"Bandex",
"Banesco",
"Banfondesa",
"Banreservas",
"Bonanza Banco",
"Citibank",
"Corporación de Crédito Monumental",
"Corporación de Crédito Nordestana",
"Corporación de Crédito Oficorp",
"JMMB Bank",
"Leasing Confisa",
"Motor Crédito",
"Qik Banco Digital",
"Scotiabank"
]
```
***
## What's Next
* [/api/partner/v2/off-ramp](/reference/offramppartnerv2controller_createpartnerofframp)
# 💎 On-Ramp
Source: https://docs.capa.fi/docs/on-ramp
The On-Ramp feature provided by Capa allows users to purchase cryptocurrencies using fiat currencies. Capa supports on-ramp operations for MXN (Mexican Peso) and DOP (Dominican Peso).
***
## Integration Flow
[Create User](/reference/userpartnercontrollerv2_createuser), then [KYC verification](/reference/userpartnercontrollerv2_createverificationlink)
[Create Quote](/reference/quotepartnerv2controller_createpartnerquote) to lock the exchange rate
[Create On Ramp](/reference/onramppartnerv2controller_createpartneronramp)
The user deposits fiat to the bank account returned in `bankAccount`
## How to do an On-Ramp Operation
To successfully execute an on-ramp operation, follow these steps:
1. ### Select the Blockchain and Token:
1. The following is a list of the Blockchains and Tokens
2. The list of Blockchains - Tokens symbols supported in Capa are:
1. POL - USDC, USDT
2. ETH - USDC
3. SOL - USDC
4. BSC - USDT (available only for transactions over 50,000 MXN)
2. ### Select the Fiat Currency:
1. Choose the supported fiat currency for the transaction. Capa currently supports the following fiat currencies:
1. MXN (Mexican Peso)
2. DOP (Dominican Peso)
3. ### Quoting:
1. To show a real-time estimate of how much crypto the user will get with the selected **Blockchain Token**, **Fiat Currency** and **Amount** use the [Get Quote Rate](/reference/quotepartnerv2controller_getpartnerquoterate) endpoint.
2. To lock a guaranteed exchange rate, use the [Create Quote](/reference/quotepartnerv2controller_createpartnerquote) endpoint. This returns a `quoteId` with an expiration time (`expiresAt`). Pass the `quoteId` when creating the transaction to use the locked rate.
3. You can specify either `fiatAmount` or `cryptoAmount` in the quote request.
4. **Forward Settlement (MXN OTC only):** For MXN orders above 50,000 MXN (OTC), the locked quote supports a `forwardingDays` parameter (0-4) that lets you lock the rate now but settle the transaction T+N days later.
4. ### Obtain User Wallet Address:
1. Ensure to ask the user for their wallet address where the crypto wants to be deposited. Make sure to verify that this is a valid address for the selected blockchain, and save it for step 6.
5. ### Show Bank Information:
1. After KYC verification, users in Mexico automatically get a dedicated CLABE (bank account identifier) created for them. For the Dominican Republic, a single Capa bank account is used. For both countries you can get the bank information after creating the transaction — it is also returned in the on-ramp creation response under `bankAccount`.
**CLABE Behavior (Mexico):**
* The CLABE is **automatically created** when the user's KYC is verified — no separate API call is needed.
* The CLABE is **permanent and reusable** across all on-ramp transactions for that user. It does not expire.
* The CLABE alone is sufficient for SPEI deposits — the account holder name auto-populates in the sender's banking app.
6. ### Execute the On-Ramp:
With all required information gathered, you can proceed to execute the on-ramp operation using the Capa API. See the documentation to execute the on ramp [Create On Ramp Transaction](/reference/onramppartnerv2controller_createpartneronramp)
Key parameters:
* `userId` (required): The user's Capa ID
* `destinationWalletAddress` (required): Where the crypto will be sent
* `fiatCurrency`, `blockchainSymbol`, `tokenSymbol` (required): The selected currency, blockchain, and token
* `fiatAmount` or `cryptoAmount`: The amount to convert
* `quoteId` (optional): A locked quote ID from the Create Quote endpoint
* `premiumSpread` (optional): A spread percentage to apply to the exchange rate
### Transaction Limits
| Currency | Minimum | Maximum |
| - | - | - |
| MXN | 50 MXN | No maximum |
| DOP | 150 DOP | No maximum |
## Funding an On-Ramp
An on-ramp transaction **must be created before** making the fiat deposit. Deposits received without a matching active on-ramp order will be refunded at end of business day.
### Funding a Specific Order
**For MXN (SPEI) deposits:**
To fund a specific on-ramp transaction, include the **transaction ID** (with or without dashes) in the SPEI **concepto** field. This ensures the deposit is matched to the correct order regardless of amount or creation order.
If the concepto does not contain a transaction ID, the deposit is matched to the user's open on-ramp orders by **amount + CLABE**. If multiple orders share the same amount, the **oldest order** is matched first.
If neither the transaction ID nor the amount + CLABE combination matches an open order, the deposit is refunded at end of business day.
**For DOP, USD, and EUR deposits:**
Bank details and deposit instructions are returned in the on-ramp creation response under `bankAccount`. Deposits are matched internally by Capa — no concepto or reference field is required.
### Multiple Concurrent Orders
You can have multiple on-ramp transactions open at the same time for a single user.
## Quoting and Premium Spread
When using `premiumSpread`, note that if a `quoteId` is provided in the on-ramp request, the **quote's `premiumSpread` is used** and any `premiumSpread` value in the request body is ignored. To ensure consistent pricing, always set `premiumSpread` when creating the quote.
A locked quote is valid for **10 seconds** after creation (`expiresAt` field in the response). Once the quote is used to create a transaction, the rate is locked for that transaction. Quote locking is available for all amounts — there is no minimum.
A quote is **optional** — if no `quoteId` is provided, the amount is calculated at the exchange rate at the time of funding.
### On-Ramp by Destination Amount
To specify how much crypto the user should receive (instead of how much fiat they'll send), use the [Create Quote](/reference/quotepartnerv2controller_createpartnerquote) endpoint with `cryptoAmount` and `transactionType: "ON_RAMP"`. Then immediately create the on-ramp with the returned `quoteId`.
## Transaction Status Progression
```mermaid theme={null}
stateDiagram-v2
[*] --> PENDING_PAYMENT
PENDING_PAYMENT --> FIAT_PAYMENT_RECEIVED : Fiat deposited
PENDING_PAYMENT --> CANCELLED : Cancelled
FIAT_PAYMENT_RECEIVED --> GETTING_FUNDS : Processing
GETTING_FUNDS --> WAITING_FOR_FUNDS : Waiting
WAITING_FOR_FUNDS --> IN_PROGRESS : Processing
IN_PROGRESS --> COMPLETED : Crypto sent
```
## Available Platforms
* **Partner API**: On-ramp operations are performed via the API using the Partner API key. For instructions on generating and using the API key, refer to the [API Key Generation Guide](/docs/partner-api-key-generation).
* **[Sandbox](https://sandbox.capa.fi)**: Test and manage on-ramp transactions, users, quoting, and webhook configuration in the sandbox environment.
* **[Dashboard](https://dashboard.capa.fi)**: Manage on-ramp transactions, users, quoting, and webhook configuration in production.
## Notifications
Capa sends notifications to keep you updated on transaction statuses:
* Webhooks for Partners: Capa posts updates to the configured partner webhooks to notify of transaction status changes.\
Email Notifications for Users (Optional): Users receive email notifications regarding their transaction status and other relevant updates. [Webhooks](/docs/how-our-webhooks-work)
* Ensure that your webhook settings are correctly configured to receive timely notifications and stay informed about the status of transactions. [Webhook Settings](/reference/partnerwebhooksettingsv2controller_update)
***
## What's Next
* [/api/partner/v2/on-ramp](/reference/onramppartnerv2controller_createpartneronramp)
# 🔑 API Key Guide
Source: https://docs.capa.fi/docs/partner-api-key-generation
How to create an API KEY
***
Welcome to our API Key Generation Guide! This page will walk you through the process of obtaining an API key to interact with Capa's payments infrastructure powered by stablecoins, enabling your users to do payins and payouts.
## 📝 How to Obtain Your API Key
Capa provides two environments, each with its own onboarding process:
### 🧪 Sandbox
For testing and development, use the **Sandbox** at [sandbox.capa.fi](https://sandbox.capa.fi). No KYB (Know Your Business) verification is required. The sandbox lets you test transaction creation, create users, generate API keys, view metrics, check quoting and spread, list transactions and users, and configure webhooks.
1. Go to [sandbox.capa.fi](https://sandbox.capa.fi)
2. Sign up with your email
3. Navigate to **Settings > Dev Tools** and click **Generate API Key** to obtain your sandbox API key
4. Use this key with the sandbox API at `https://api.sandbox.capa.fi`
### 🚀 Production
For live transactions, use the **Dashboard** at [dashboard.capa.fi](https://dashboard.capa.fi). This requires KYB verification and self-onboarding. The dashboard provides the same capabilities as the sandbox -- transaction creation, user management, API key generation, metrics, quoting, spread, transaction and user listing, and webhook configuration -- connected to the production environment.
1. Go to [dashboard.capa.fi](https://dashboard.capa.fi)
2. Sign up with your email
3. Complete the **KYB (Know Your Business)** verification process
4. Follow the **self-onboarding** steps to set up your partner account
5. Navigate to **Settings > Dev Tools** and click **Generate API Key** to obtain your production API key
6. Use this key with the production API at `https://api.capa.fi`
If you run into any issues during the onboarding process, don't hesitate to contact us at [onboarding@capa.fi](mailto:onboarding@capa.fi) or [juanda@capa.fi](mailto:juanda@capa.fi).
## 🔄 Regenerating Your API Key
If you need to regenerate your API key, log in to the partners dashboard at [dashboard.capa.fi](https://dashboard.capa.fi) (production) or [sandbox.capa.fi](https://sandbox.capa.fi) (sandbox), go to **Settings > Dev Tools**, and click **Regenerate API Key**. It's a quick and easy process to ensure your integration remains secure in case there is any issue with the integrity of your API Key. 🔑
## 🔍 Using Your API Key
To authenticate your requests, you need to include your API key in the headers of every HTTP request you make to our API.
### Example Request with API Key
Include your API key in the request header under the `partner-api-key` field.
```Text bash theme={null}
curl -X GET 'https://api.sandbox.capa.fi/api/partner/v2/transactions' \
-H 'partner-api-key: YOUR_API_KEY_HERE'
```
### 🔄 Header Format
```Text http theme={null}
partner-api-key: YOUR_API_KEY_HERE
```
Replace **YOUR\_API\_KEY\_HERE** with the actual API key provided to you.
## 🚨 Important Security Notes
* **Keep Your API Key Secure:** Treat your API key like a password. Do not share it publicly or expose it in client-side code (e.g., JavaScript, HTML). 🛡️
If you suspect that your API key has been compromised, please contact us **immediately**. We will promptly issue a new key to ensure your account's security.
# 🤝 Service Level Agreement
Source: https://docs.capa.fi/docs/service-level-agreement
***
### **1. What This SLA Covers**
This SLA outlines what you can expect when using Capa’s on-and-off ramp services for virtual assets, including API access, system availability, support, and performance standards.
***
### **2. Services Provided**
We offer the following services:
* **API Access** to convert fiat to crypto and crypto to fiat
* **Transaction Handling** through connected banks and payment partners
* **Onboarding & KYC/B** for businesses and end users using our verification process
***
### **3. Availability & Uptime**
* **Uptime Target**: We aim for **99.9% uptime** each month, excluding scheduled maintenance.
* **Planned Downtime**: We'll notify you ahead of any scheduled maintenance.
* **Unplanned Incidents**: If something unexpected happens, we’ll communicate it promptly.
* **Status Page**: Monitor real-time API availability and incident updates at [status.capa.fi](https://status.capa.fi).
* **Uptime Calculation**: Uptime is calculated monthly as (total minutes in the month − downtime minutes) / total minutes in the month, excluding scheduled maintenance. Downtime means the API is unreachable or returns error responses on more than 5% of requests over any 5-minute window.
* **Incident Log**: [status.capa.fi](https://status.capa.fi) is the authoritative record of incidents and historical uptime. Incidents affecting availability are logged there promptly after detection.
***
### **4. Incident Severity & Response**
We classify incidents by severity and respond according to the following:
| Severity | What it means | Response time | Partner notification |
| - | - | - | - |
| Critical | Core service outage, payment rails offline, or immediate risk to funds or data | Within 30 minutes | Within 2 hours |
| High | Service degraded, partial transaction failures, contained impact | Within 2 hours | Within 8 hours |
| Medium | Limited-scope issue, no funds or sensitive data at risk | Within 8 hours | Within 24 hours |
Response and notification times apply during office hours.
Following a Critical or High incident, we send affected partners a final post-incident report within 10 business days of resolution.
***
### **5. Market Hours**
Transactions and liquidity are available 24/7.
However, open market rates are available during the following hours:
* **Monday to Friday**: 8:00 AM – 3:00 PM (CST)
* **Closed**: Saturdays, Sundays, and official holidays in **Mexico, the European Union, and the United States**
Transactions created outside open market hours will have an additional spread.
***
### **6. Service Performance**
* **Transaction Speed**: Completion time depends on banks, payment methods, compliance checks, and external systems.
* **API Response Time**: On average, our API responds in **5 seconds**.
* **System Stability**: We aim for a transaction success rate above **99%**.
* **Liquidity**: We manage liquidity actively to ensure smooth operations during market hours.
***
### **7. Dashboard Access**
All partners have access to two dashboards:
* **[Sandbox](https://sandbox.capa.fi)** -- for testing and development
* **[Dashboard](https://dashboard.capa.fi)** -- connected to the production environment for live operations
Both platforms provide the same capabilities:
* **Create, monitor, and track transactions** in real time
* **Create, verify, list, and monitor users**
* **Generate and manage API keys**
* **View metrics**, check quoting, and check spread
* **Configure webhooks**
* **View revenue** generated via optional premium spreads on exchange rates
* **Monthly Disbursements**: Revenue generated from premiums will be disbursed at the end of each month
***
### **8. Support & Maintenance**
* **Support Channels**: Contact us via Slack, Telegram, Whatsapp or [email](mailto:partner-support@capa.fi)
* **Response Time**: Expect a first response within **30 minutes** during office hours, depending on issue severity
* **Critical Issues**: Escalated immediately and handled with priority
* **Scheduled Maintenance**: You’ll be notified at least **24 hours** in advance
* **Emergency Fixes**: We’ll communicate them as soon as they arise
***
### **9. SLA Updates**
This SLA may be updated as our platform and services evolve. We’ll communicate any changes clearly and in advance.
# Step-by-Step Process for HMAC Signature Validation
Source: https://docs.capa.fi/docs/step-by-step-process-for-hmac-signature-validation
***
### Step-by-Step Process for HMAC Signature Validation
#### Step 1: Generate HMAC Signature Using the Shared `WEBHOOK_SECRET`
Before you start signing your payloads, you need to use the `**WEBHOOK_SECRET**` that we share to generate the HMAC signature. This key ensures secure communication between your system and ours.
1. **Create the HMAC Signature**
* Use the shared `**WEBHOOK_SECRET**` to create the HMAC signature. This key is combined with the payload to produce the signature using the SHA-256 hashing algorithm. Here’s how you can create the HMAC signature:
```typescript theme={null}
const secretKey = WEBHOOK_SECRET; // Use the shared API_KEY
const payload = JSON.stringify(data); // The data you want to send
const signature = crypto.createHmac('sha256', secretKey)
.update(payload)
.digest('hex');
```
* This `signature` will be included in the headers of your request under the `capa-signature` header to authenticate the payload.
2. **Include the Signature in the Request**
* When making a request to our API, include the signature as a header:
#### Step 2: Verify the Payload Signature
When your system receives a payload, you should verify its authenticity by comparing the signature in the header with the one generated from the payload.
1. **Extract the Signature from the Header**
* The signature will be sent in the `capa-signature` header:
```typescript theme={null}
const receivedSignature = req.headers['capa-signature'];
```
2. **Compare the Signatures**
* Hash the received payload and compare it to the signature provided:
```typescript theme={null}
const hash = crypto.createHmac('sha256', secretKey).update(JSON.stringify(payload)).digest('hex');
const isValid = crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(receivedSignature, 'hex'));
```
3. **Process the Payload Based on Verification**
* If the signature matches, proceed with processing the payload. If it doesn’t, reject the request to prevent unauthorized actions
***
### Summary of the Process
1. **Generate HMAC Signature**: Use the shared `**WEBHOOK_SECRET**` to generate an HMAC signature. Combine the payload with the `**WEBHOOK_SECRET**` and hash it using the SHA-256 algorithm.
2. **Include Signature in the Request**: Add the HMAC signature to the request header (`capa-signature`).
3. **Verify Payload**: When receiving a payload, compare the signature from the header with the hashed payload.
By following these steps, you can securely transmit and verify payloads using HMAC signatures, ensuring the integrity of the communication between your system and ours.
***
What’s Next
* [Webhook Events Overview](/docs/webhook-events-overview)
* [Transaction Events](/docs/transaction-events)
* [KYC Events](/docs/kyc-events)
# 🏦 Transaction Events
Source: https://docs.capa.fi/docs/transaction-events
Transaction events are notifications sent to partners when there are updates or changes related to transactions involving their users. These notifications help partners stay informed about the status of various transactions.
***
## Types of Transaction Events
1. **`COMPLETED_ON_RAMP`(Completed On-Ramp):**
* **Description:** This event is triggered when an "on-ramp" transaction (converting fiat currency to cryptocurrency) has been successfully completed. **When this event fires, the crypto is already in the user's destination wallet.**
* **Use Case:** The partner updates their system to reflect that the user's on-ramp transaction has been finalized. The crypto has been sent and confirmed on-chain.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "COMPLETED_ON_RAMP",
"externalUserId": "customer_1753744084024",
"transactionId": "bf08852e-6b1a-444d-bf87-9a9f1837cfc5",
"blockchain": "Polygon PoS",
"cryptoCurrency": "USDC",
"fiatCurrency": "MXN",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"fiatAmount": "10.00",
"cryptoAmount": "0.578031",
"exchangeRate": "17.300119008048114"
}
```
2. **`FIAT_RECEIVED_ON_RAMP`(Fiat Received On-Ramp):**
* **Description:** This event is triggered when fiat currency has been received for an on-ramp transaction and the conversion process is starting. **The crypto has not been sent yet at this point.**
* **Use Case:** The partner is informed that the user's fiat payment has been received and the process to convert it to cryptocurrency has begun. Do not treat this as a completed transaction.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "FIAT_RECEIVED_ON_RAMP",
"externalUserId": "customer_1753744084024",
"transactionId": "bf08852e-6b1a-444d-bf87-9a9f1837cfc5",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO"
}
```
3. **`COMPLETED_OFF_RAMP`(Completed Off-Ramp):**
* **Description:** This event is triggered when an "off-ramp" transaction (converting cryptocurrency to fiat currency) has been successfully completed.
* **Use Case:** The partner updates their system to reflect that the user's off-ramp transaction has been finalized, indicating the user has received their fiat currency.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "COMPLETED_OFF_RAMP",
"externalUserId": "customer_1765265674454",
"transactionId": "3e6e0936-f714-4337-92d0-303dd9da5df5",
"blockchain": "Polygon PoS",
"cryptoCurrency": "USDC",
"fiatCurrency": "MXN",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"cryptoAmount": "1.000000",
"fiatAmount": "17.0799",
"exchangeRate": "17.085723916631654"
}
```
4. **`CREATED_ON_RAMP`(Created On-Ramp):**
* **Description:** This event is triggered when a new on-ramp transaction (fiat to cryptocurrency) has been initiated.
* **Use Case:** The partner can record the initiation of a new on-ramp transaction and begin tracking its status.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "CREATED_ON_RAMP",
"externalUserId": "customer_1753744084024",
"transactionId": "bf08852e-6b1a-444d-bf87-9a9f1837cfc5",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO"
}
```
5. **`CRYPTO_RECEIVED_OFF_RAMP`(Crypto Received Off-Ramp):**
* **Description:** This event is triggered when a partner's user has sent cryptocurrency to Capa's wallet, and Capa is starting the process of sending the equivalent fiat currency to the user's account with the partner.
* **Use Case:** The partner is informed that the user has transferred cryptocurrency to Capa, and the process to exchange it for fiat currency and deposit it into the user’s account has begun. The partner can update their system to reflect this ongoing transaction status.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "CRYPTO_RECEIVED_OFF_RAMP",
"externalUserId": "customer_1765265674454",
"transactionId": "3e6e0936-f714-4337-92d0-303dd9da5df5",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO"
}
```
6. **`CREATED_OFF_RAMP`(Created Off-Ramp):**
* **Description:** This event is triggered when a new off-ramp transaction (cryptocurrency to fiat) has been initiated.
* **Use Case:** The partner can record the initiation of a new off-ramp transaction and begin tracking its status.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "CREATED_OFF_RAMP",
"externalUserId": "customer_1765265674454",
"transactionId": "3e6e0936-f714-4337-92d0-303dd9da5df5",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO"
}
```
7. **`CANCELLED_ON_RAMP`(Cancelled On-Ramp):**
* **Description:** This event is triggered when an on-ramp transaction (fiat to cryptocurrency) has been cancelled.
* **Use Case:** The partner can update their system to reflect that the user's on-ramp transaction has been cancelled.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "CANCELLED_ON_RAMP",
"externalUserId": "customer_1768865246072",
"transactionId": "e4c0854f-d448-4e1c-bcaf-d6269e6736b3",
"blockchain": "Polygon PoS",
"cryptoCurrency": "USDC",
"fiatCurrency": "MXN",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"fiatAmount": "10.0000",
"cancellationReason": "USER_CANCELLED"
}
```
8. **`CANCELLED_OFF_RAMP`(Cancelled Off-Ramp):**
* **Description:** This event is triggered when an off-ramp transaction (cryptocurrency to fiat) has been cancelled.
* **Use Case:** The partner can update their system to reflect that the user's off-ramp transaction has been cancelled.
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "CANCELLED_OFF_RAMP",
"externalUserId": "customer_1753744084024",
"transactionId": "ce96ebb3-ace0-4ff6-8d26-baf9d77b44ba",
"blockchain": "Polygon PoS",
"cryptoCurrency": "USDC",
"fiatCurrency": "MXN",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"cryptoAmount": "1.000000",
"cancellationReason": "USER_CANCELLED"
}
```
***
## Cross Ramp Events
Cross-ramp transactions convert between different fiat currencies. The following events are emitted in order during a cross-ramp transaction lifecycle:
9. **`CREATED_CROSS_RAMP`(created-cross-ramp):**
* **Description:** This event is emitted when a cross-ramp transaction is created.
* **Transaction Status:** `PENDING_FUNDS`
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "CREATED_CROSS_RAMP",
"transactionId": "a1b2c3d4-5678-4abc-8def-1234567890ab",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"externalUserId": "4e6c9c80-6c4a-4bfe-8f70-3a0e4db6d015"
}
```
10. **`FUNDS_RECEIVED_CROSS_RAMP`(funds-received-cross-ramp):**
* **Description:** This event is emitted when funds are received for the transaction.
* **Status Transition:** `PENDING_FUNDS` → `FUNDS_RECEIVED`
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "FUNDS_RECEIVED_CROSS_RAMP",
"transactionId": "a1b2c3d4-5678-4abc-8def-1234567890ab",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"externalUserId": "4e6c9c80-6c4a-4bfe-8f70-3a0e4db6d015"
}
```
11. **`STARTED_PROCESSING_CROSS_RAMP`(started-processing-cross-ramp):**
* **Description:** This event is emitted when processing starts.
* **Status Transition:** `FUNDS_RECEIVED` → `IN_PROGRESS`
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "STARTED_PROCESSING_CROSS_RAMP",
"transactionId": "a1b2c3d4-5678-4abc-8def-1234567890ab",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"externalUserId": "4e6c9c80-6c4a-4bfe-8f70-3a0e4db6d015"
}
```
12. **`AWAITING_FUND_TRANSFER_CROSS_RAMP`(awaiting-fund-transfer-cross-ramp):**
* **Description:** This event is emitted when the transaction is awaiting fund transfer.
* **Status Transition:** `IN_PROGRESS` → `AWAITING_FUND_TRANSFER`
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "AWAITING_FUND_TRANSFER_CROSS_RAMP",
"transactionId": "a1b2c3d4-5678-4abc-8def-1234567890ab",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"externalUserId": "4e6c9c80-6c4a-4bfe-8f70-3a0e4db6d015"
}
```
13. **`COMPLETED_CROSS_RAMP`(completed-cross-ramp):**
* **Description:** This event is emitted when the transaction completes.
* **Status Transition:** `AWAITING_FUND_TRANSFER` → `COMPLETED`
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "COMPLETED_CROSS_RAMP",
"transactionId": "a1b2c3d4-5678-4abc-8def-1234567890ab",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"externalUserId": "4e6c9c80-6c4a-4bfe-8f70-3a0e4db6d015",
"sourceCurrency": "USD",
"targetCurrency": "EUR",
"sourceAmount": "1000",
"targetAmount": "920",
"exchangeRate": "0.92"
}
```
14. **`CANCELLED_CROSS_RAMP`(cancelled-cross-ramp):**
* **Description:** This event is emitted when the transaction is cancelled. This can occur at any stage of the transaction.
* **Transaction Status:** `CANCELLED`
* **Example Payload:**
```json JSON theme={null}
{
"type": "TRANSACTION",
"event": "CANCELLED_CROSS_RAMP",
"transactionId": "a1b2c3d4-5678-4abc-8def-1234567890ab",
"firstName": "SATOSHI",
"lastName": "NAKAMOTO",
"externalUserId": "4e6c9c80-6c4a-4bfe-8f70-3a0e4db6d015",
"sourceCurrency": "USD",
"targetCurrency": "EUR",
"sourceAmount": "1000",
"targetAmount": "920",
"exchangeRate": "0.92",
"cancellationReason": "User requested cancellation"
}
```
***
## Bank Account Events
Bank account events are sent when the verification status of a user's bank account changes. These use `type: "USER"` instead of `type: "TRANSACTION"`.
15. **`VERIFIED_BANK_ACCOUNT`(Verified Bank Account):**
* **Description:** This event is triggered when a user's bank account has been successfully verified (e.g., after a penny test for MX accounts).
* **Example Payload:**
```json JSON theme={null}
{
"type": "USER",
"event": "VERIFIED_BANK_ACCOUNT",
"externalUserId": "customer_1753744084024",
"userBankInfoId": "a1b2c3d4-5678-4abc-8def-1234567890ab"
}
```
16. **`REJECTED_BANK_ACCOUNT`(Rejected Bank Account):**
* **Description:** This event is triggered when a user's bank account verification has been rejected.
* **Example Payload:**
```json JSON theme={null}
{
"type": "USER",
"event": "REJECTED_BANK_ACCOUNT",
"externalUserId": "customer_1753744084024",
"userBankInfoId": "a1b2c3d4-5678-4abc-8def-1234567890ab"
}
```
***
### Summary of Transaction Events
These transaction events help partners automate their response to changes in transaction statuses:
* **`COMPLETED_ON_RAMP`** and **`COMPLETED_OFF_RAMP`**: Confirm that a user's transaction has been successfully completed.
* **`FIAT_RECEIVED_ON_RAMP`**: Indicates that fiat currency has been received and the conversion to cryptocurrency has begun.
* **`CREATED_ON_RAMP`** and **`CREATED_OFF_RAMP`**: Notify the partner when a new transaction is initiated.
* **`CANCELLED_ON_RAMP`** and **`CANCELLED_OFF_RAMP`**: Alert the partner that a user's transaction has been cancelled.
* **`CRYPTO_RECEIVED_OFF_RAMP`**: Indicates that Capa has received cryptocurrency from a partner's user and the process of sending fiat currency to the user has started.
**Cross Ramp Events:**
* **`CREATED_CROSS_RAMP`**: Notifies when a cross-ramp transaction is created.
* **`FUNDS_RECEIVED_CROSS_RAMP`**: Indicates funds have been received for the cross-ramp transaction.
* **`STARTED_PROCESSING_CROSS_RAMP`**: Signals that processing has begun.
* **`AWAITING_FUND_TRANSFER_CROSS_RAMP`**: Indicates the transaction is awaiting fund transfer.
* **`COMPLETED_CROSS_RAMP`**: Confirms the cross-ramp transaction has been successfully completed.
* **`CANCELLED_CROSS_RAMP`**: Alerts that the cross-ramp transaction has been cancelled.
**Bank Account Events:**
* **`VERIFIED_BANK_ACCOUNT`**: Confirms a user's bank account has been verified.
* **`REJECTED_BANK_ACCOUNT`**: Alerts that a user's bank account verification was rejected.
By processing these events, partners can ensure their systems are up-to-date with the latest transaction statuses and take appropriate actions to manage user interactions and maintain accurate records.
# 💰 Transactions
Source: https://docs.capa.fi/docs/transactions-1
***
## Overview
Transactions are a crucial part of the flow in Capa, this will guide you on the listing of the transaction and the cancel of the transaction when a user needs to make this action.
## List Transactions
To get a list of the user's transactions use the [List User Transactions Endpoint](/reference/transactionpartnerv2controller_listpartnerusertransactions). The endpoint has multiple ways in which it can be filtered, by type of transaction, transaction id, user id and it can be listed in order.
## Transaction Detail
To get the information of a single transaction the [List User Transactions Endpoint](/reference/transactionpartnerv2controller_listpartnerusertransactions) can be used and filtered by transaction id so you only receive the specific information of that transaction
## Cancel Transaction
Use the [Cancel User Transaction Endpoint](/reference/transactionpartnerv2controller_cancelpartnertransaction) when an On/Off Ramp wants to be cancelled
Only when an On Ramp is in **PENDING\_PAYMENT** status it can be canceled
Only when an Off Ramp is in **PENDING\_PAYMENT** or in **COMPLETED\_CRYPTO\_RECEIVAL** status it can be canceled
***
What’s Next
* [/api/partner/v2/transactions](/reference/transactionpartnerv2controller_listpartnerusertransactions)
* [/api/partner/v2/transactions//cancel](/reference/transactionpartnerv2controller_cancelpartnertransaction)
# 👛 User Wallets
Source: https://docs.capa.fi/docs/user-wallets
Manage wallet addresses for users to receive crypto during on-ramp transactions.
***
## Overview
Wallets are destination blockchain addresses where cryptocurrency is sent during on-ramp transactions. Each wallet is tied to a virtual machine (VM) type that determines which blockchains are compatible with that address. You can save wallets ahead of time or pass the address directly when creating a transaction.
## VM Types
The `vmType` determines the address format and which blockchains are compatible:
| VM Type | Compatible Blockchains | Address Format |
| - | - | - |
| `EVM` | POL, ETH, BSC | Ethereum-style (`0x...`) |
| `SVM` | SOL | Solana-style base58 address |
The wallet address is validated against the selected `vmType` when created. Duplicate combinations of `walletAddress` + `vmType` for the same user are rejected.
## Creating a Wallet
Use the [Create Wallet](/reference/walletaddresspartnerv2controller_createuserwallet) endpoint to register a new wallet for a user.
```
POST /api/partner/v2/users/{userId}/wallets
```
### Required Fields
* `vmType`: The virtual machine type (`EVM` or `SVM`)
* `walletAddress`: The blockchain address
* `alias`: A friendly name for the wallet (e.g., "My Polygon Wallet")
### Example
```json theme={null}
{
"vmType": "EVM",
"walletAddress": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
"alias": "My Polygon Wallet"
}
```
### Response
```json theme={null}
{
"success": true,
"data": {
"id": "8374f327-38bd-4b0b-b8a7-2524599eb903",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"walletAddress": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
"alias": "My Polygon Wallet",
"vmType": "EVM",
"enabled": true,
"createdAt": "2025-05-08T18:00:00Z"
}
}
```
## Listing Wallets
Use the [List Wallets](/reference/walletaddresspartnerv2controller_listuserwallets) endpoint to retrieve all enabled wallets for a user.
```
GET /api/partner/v2/users/{userId}/wallets
```
### Query Parameters
| Parameter | Required | Description |
| - | - | - |
| `vmType` | No | Filter by VM type (`EVM`, `SVM`) |
| `id` | No | Filter by wallet ID |
| `skip` | No | Number of results to skip (pagination) |
| `limit` | No | Maximum number of results to return |
| `sortBy` | No | Sort field (supported: `createdAt`) |
The response includes `count` (matching results) and `total` (all wallets for the user) fields for pagination.
## Deleting a Wallet
Use the [Delete Wallet](/reference/walletaddresspartnerv2controller_deleteuserwallet) endpoint to soft-delete (disable) a wallet. Disabled wallets will no longer appear in list results.
```
DELETE /api/partner/v2/users/{userId}/wallets/{walletId}
```
## Using Wallets in Transactions
When creating an on-ramp transaction, the `destinationWalletAddress` field specifies where crypto will be sent. You can either:
* **Use a saved wallet**: Save the wallet first with the Create Wallet endpoint, then reference the address in the transaction.
* **Pass the address directly**: Provide the `destinationWalletAddress` in the [Create On-Ramp](/reference/onramppartnerv2controller_createpartneronramp) request without saving it beforehand.
In both cases, the address must be valid for the blockchain used in the transaction.
***
What's Next
* [Create Wallet](/reference/walletaddresspartnerv2controller_createuserwallet)
* [List Wallets](/reference/walletaddresspartnerv2controller_listuserwallets)
* [💎 On-Ramp](/docs/on-ramp)
# 🏛️ Virtual Accounts
Source: https://docs.capa.fi/docs/virtual-accounts
Give your US users a named bank account of their own, so funds arrive under their name instead of a shared account.
***
## Overview
A **virtual account** is a US bank account — account number plus ABA routing number — issued in your **user's own name**. Funds sent to it land directly in that user's balance, with no shared account and no manual reference matching.
Virtual accounts are provisioned through **banking onboarding**: Capa submits the user's verified identity to our US banking partner, the partner runs its own review, and on approval the account is created and attached to the user.
Virtual accounts are currently available for the **United States (`US`)** only. `country` must be `US` on both endpoints — any other value is rejected with `400`.
## Before You Start
The user must have **completed KYB** with Capa. Banking onboarding does not collect identity data of its own — it consumes what KYB already produced, so a user who has cleared KYB is ready to be onboarded. See [Create and Verify a User](/docs/create-and-verify-a-user-with-capa-api).
## The Flow
Call [Get Virtual Account Status](/reference/bankingonboardingpartnerv2controller_getonboardingstatus) to see whether the user can be onboarded.
```
GET /api/partner/v2/banks/users/{userId}/banking-onboarding?country=US
```
```json theme={null}
{
"success": true,
"data": {
"hasVirtualAccount": false,
"canRequest": true,
"status": "NOT_STARTED",
"missingRequirements": []
}
}
```
Use `canRequest` to gate the action in your UI. It is `true` only when KYB is complete, the user has no existing virtual account, and no onboarding is already in flight.
Call [Request Virtual Account](/reference/bankingonboardingpartnerv2controller_requestonboarding).
```
POST /api/partner/v2/banks/users/{userId}/banking-onboarding
```
```json theme={null}
{ "country": "US" }
```
Branch on `data.status` — a `201` does not always mean a new onboarding was started:
| `status` | Meaning | What to do |
| - | - | - |
| `SUBMITTED` | Sent to the banking partner. | Poll for completion. |
| `ALREADY_IN_PROGRESS` | One is already running. | Poll for completion. |
| `ALREADY_COMPLETED` | The user already has an account. | Read it from the bank accounts endpoint. |
| `REQUIREMENTS_MISSING` | The user's KYB is not complete; **nothing was submitted**. | Finish KYB, then retry. |
The endpoint is safe to call repeatedly — it returns the existing onboarding rather than starting a second one.
`status: "REQUIREMENTS_MISSING"` means the user's KYB is not complete. Send them to `kybLink` to finish it:
```json theme={null}
{
"success": true,
"data": {
"status": "REQUIREMENTS_MISSING",
"kybLink": "https://verify.capa.fi/s/2f9c1b7e",
"missingRequirements": [
{ "field": "taxId", "message": "Tax ID is required for business onboarding" }
]
}
}
```
`missingRequirements` lists what is still outstanding — useful for surfacing a reason in your UI, but you do not need to collect those fields yourself. Completing KYB satisfies them. `kybLink` is optional; if it is absent, direct the user through your usual KYB flow.
Once KYB is complete, call the request endpoint again.
There is **no webhook** for virtual account onboarding. Poll the status endpoint until `status` is `COMPLETED` or `FAILED`.
| `status` | Meaning |
| - | - |
| `NOT_STARTED` | No onboarding has ever been created. |
| `IN_PROGRESS` | Submitted, awaiting the banking partner's decision. |
| `COMPLETED` | Approved and provisioned. |
| `FAILED` | Did not complete. `canRequest` returns to `true`, so it can be retried. |
Approval typically takes minutes to hours, so poll every few minutes — not every few seconds.
Once `hasVirtualAccount` is `true`, fetch the account from [List Bank Accounts](/reference/userbankinfopartnerv2controller_getuserbankinfosbyuserid):
```
GET /api/partner/v2/banks/users/{userId}/accounts?country=US
```
The provisioned account appears as a normal bank account record carrying the user's `accountNumber`, `routingNumber`, `bankName`, and `accountType`. It is stored with `rampType: "CROSS_RAMP"`, so you can filter on that to isolate virtual accounts from bank accounts the user added themselves.
## Retrying a Failed Onboarding
A `FAILED` onboarding is not terminal. `canRequest` flips back to `true`, and calling the request endpoint again starts a fresh attempt. If the user's KYB is not complete, the retry returns `REQUIREMENTS_MISSING` rather than failing again.
***
## What's Next
* [Request Virtual Account (User)](/reference/bankingonboardingpartnerv2controller_requestonboarding)
* [Get Virtual Account Status (User)](/reference/bankingonboardingpartnerv2controller_getonboardingstatus)
* [🏦 Bank Accounts](/docs/bank-accounts)
# Webhook Events Overview
Source: https://docs.capa.fi/docs/webhook-events-overview
The Capa API utilizes a webhook-based notification system to keep partners informed of significant events related to their users and transactions. These webhook events enable partners to automate their response to changes, maintain compliance, and ensure their systems are up-to-date.
***
## What is a Webhook Event?
A webhook event is a real-time notification sent from the Capa API to a partner's specified URL whenever a specific action or change occurs within our system. These events could relate to a variety of operations, such as user verification (KYC) updates, transaction status changes, and more.
## General Structure of Webhook Events
Each webhook event sent by the Capa API follows a standardized structure to ensure consistency and ease of integration. Below is the general structure of a webhook event:
* **Event Type**: A category defining the nature of the event.
* We currently support this type of events:
* `KYC`
* `TRANSACTION`
* `USER` (bank account verification events)
* `SIGNATURE` (document/cédula signature events)
* **Event Name**: A specific identifier of the event type (e.g., `VERIFIED_USER`, `COMPLETED_ON_RAMP`).
* **Payload**: Contains relevant data and details specific to the event type.
### Example of a General Webhook Event
Here’s a generic example of how a webhook event might look:
```json theme={null}
{
"type": "TRANSACTION", // Event Type: Can be 'KYC', 'TRANSACTION', etc.
"event": "COMPLETED_ON_RAMP", // Event Name: Specific to the event (e.g., 'VERIFIED_USER', 'CRYPTO_RECEIVED_OFF_RAMP')
// The rest values are event specific
}
```
### Security and Authentication
To ensure secure communication, each webhook event is signed using HMAC (Hash-based Message Authentication Code) authentication. This signature is computed using a secret key shared between Capa and the partner. The partner must validate the signature to confirm that the notification is genuine and has not been tampered with.
### Common Event Types
1. **KYC (Know Your Customer) Events**:
* Triggered when there are updates or changes in the user's identity verification process.
* Examples: `VERIFIED_USER`, `REJECTED_KYC`, `REVIEW_NEEDED_KYC`.
2. **Transaction Events**:
* Triggered when there are updates or changes related to user transactions, such as cryptocurrency exchanges, purchases, or cancellations.
* Examples: `COMPLETED_ON_RAMP`, `COMPLETED_OFF_RAMP`, `CRYPTO_RECEIVED_OFF_RAMP`, `CANCELLED_ON_RAMP`.
3. **Signature Events**:
* Triggered when a user or beneficial owner completes a document signature, such as the [UBO cédula required for MX cross-ramp business users](/docs/associated-person-signature).
* Example: `DOCUMENT_SIGNED`.
### Handling Webhook Events
#### 1. Registering a Webhook URL
Partners need to register a single webhook URL where they want to receive notifications. This URL should be provided via the designated endpoint:
* **Endpoint**: [PUT /api/partner/v2/webhook-settings](/reference/partnerwebhooksettingsv2controller_update)
* **Request Body Example:**
```json theme={null}
{
"url": "https://your-webhook-endpoint.url"
}
```
#### 2. Receiving Webhook Notifications
When an event occurs, the Capa API sends a POST request to the partner's registered webhook URL. Partners should ensure their endpoints are always available to receive these notifications.
#### 3. Validating the Signature
Partners should validate the HMAC signature included in the event header to ensure the integrity and authenticity of the notification. This helps protect against tampered or spoofed events.
#### 4. Responding to Webhook Events
Partners must respond with a `200 OK` status to acknowledge receipt of the webhook. If the Capa API does not receive this acknowledgment, it will retry the delivery of the event up to 5 times with exponential backoff.
### Error Handling and Retries
* **Automatic Retries**: If the Capa API does not receive a `200 OK` response from the partner's endpoint, it will automatically retry up to 5 times with increasing delays between attempts.
* **Manual Retries**: Partners can manually request a retry of failed notifications by making a `POST` request to the [Retry Webhook Notification](/reference/partnerwebhooknotificationv2controller_retry) endpoint.
### Best Practices for Handling Events
* **Ensure Endpoint Availability**: Make sure your webhook URL is always available and responsive to receive notifications.
* **Handle Retries Gracefully**: Design your system to handle duplicate events gracefully in case of retries.
* **Secure Your Webhook Endpoint**: Validate the HMAC signature and use HTTPS to ensure secure communication.
* **Log and Monitor Events**: Log all incoming events and monitor them to detect any issues early.
### Summary
The Capa API's webhook event system provides partners with real-time notifications of significant events, allowing them to stay informed and take timely action. By understanding the general structure, security measures, and handling practices, partners can effectively integrate with the webhook system and ensure smooth operation.
# Get Beneficial Owner Signature Status
Source: https://docs.capa.fi/reference/associatedpersonpartnercontrollerv2_listuboformsignatures
reference/openapi/AssociatedPersonPartnerControllerV2_listUboFormSignatures.json GET /api/partner/v2/users/{userId}/associated-persons/signatures
Read-only, so it is the endpoint to poll. Every beneficial owner appears, including those never asked for a cédula, which carry no status — a caller who added an owner since their last request would otherwise see a short list and believe they were done. Every owner reporting SIGNED is what satisfies the requirement.
Returns where every `UBO`-role associated person stands on their cédula. Read-only — this is the endpoint to poll, and it's also the safest fallback for the [`DOCUMENT_SIGNED` webhook](/docs/associated-person-signature#webhook-notification).
***
## Important Notes
* **Every UBO appears**, including those never asked for a cédula — those entries carry no `status`. This avoids a caller believing they're done because a short list came back, after adding a new owner since their last request.
* `status` is `PENDING` (awaiting signature) or `SIGNED` (complete). A missing `status` means no cédula was ever requested for that owner, or that requesting one previously failed (see `error`/`errorCode`).
* **Every owner reporting `SIGNED` is what satisfies the [cross-ramp signature requirement](/docs/associated-person-signature#enforcement-on-cross-ramp)** — poll this endpoint until every UBO shows `SIGNED`.
* `signingUrl` is only present while `status` is `PENDING`; it's withheld once `SIGNED`.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 422 | `UNPROCESSABLE_ENTITY` | "User is not a business" |
| 500 | `INTERNAL_SERVER_ERROR` | "Internal server error" |
# Request Beneficial Owner Signatures
Source: https://docs.capa.fi/reference/associatedpersonpartnercontrollerv2_requestuboformsignatures
reference/openapi/AssociatedPersonPartnerControllerV2_requestUboFormSignatures.json POST /api/partner/v2/users/{userId}/associated-persons/signatures
Idempotent: an owner with an outstanding or completed cédula is left untouched and their existing state returned, so calling this again never produces a second link for the same owner. A failure for one owner does not affect the others — each carries its own status. Capa sends no email, so delivering each signingUrl to its owner is the caller's responsibility.
Requests a cédula (beneficial owner declaration) signing link for every `UBO`-role associated person on record for a business user who doesn't already have one. See [Associated Person Signature](/docs/associated-person-signature) for the full flow.
***
## Important Notes
* **MX only.** `country` must be `MX` — the cédula's legal basis (art. 95 Bis LGOAAC) is Mexico-specific.
* **Idempotent.** An owner with an outstanding (`PENDING`) or completed (`SIGNED`) cédula is left untouched; their existing state is returned rather than a new link being issued. Calling this repeatedly never produces a second link for the same owner.
* **Partial failure is per-owner.** A failure for one owner (e.g. missing email) doesn't block the others — each result carries its own `status`/`error`.
* **Capa sends no email.** Delivering each `signingUrl` to its owner is the caller's responsibility. Treat `signingUrl` as sensitive — anyone holding it can sign as that owner.
* The business must be KYB-verified for `MX` and have at least one `UBO`-role associated person on record, and at most 25.
* Every `UBO` must have an `email` set — otherwise that owner's entry in the response carries an `error`/`errorCode` instead of a `signingUrl`.
* Supports an optional `Idempotency-Key` header (16–128 printable ASCII characters) to protect against duplicate submissions from client-side retries.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 403 | `USER_NOT_VERIFIED_ERROR` | "Business KYB verification is not verified for this country" |
| 404 | `NOT_FOUND_ERROR` | "No business identity found for user" |
| 422 | `UNPROCESSABLE_ENTITY` | "Business has no beneficial owners on record" |
| 422 | `UNPROCESSABLE_ENTITY` | "Business reports more than 25 beneficial owners" |
| 422 | `UNPROCESSABLE_ENTITY` | "The beneficial-owner cédula is only available for MX; is not supported" |
| 500 | `INTERNAL_SERVER_ERROR` | "Internal server error" |
# Get Virtual Account Status (User)
Source: https://docs.capa.fi/reference/bankingonboardingpartnerv2controller_getonboardingstatus
reference/openapi/BankingOnboardingPartnerV2Controller_getOnboardingStatus.json GET /api/partner/v2/banks/users/{userId}/banking-onboarding
Returns everything you need to decide whether to request a virtual account for a user: whether they already have one, whether a request would be accepted right now, the state of their latest onboarding, and any identity data still missing. Safe to poll while an onboarding is in flight.
Returns everything you need to decide whether to request a **named virtual account** for a user: whether they already have one, whether a request would be accepted right now, the state of their latest onboarding, and whether their KYB is still outstanding. See the [Virtual Accounts guide](/docs/virtual-accounts) for the full flow.
Currently available for the **United States (`US`)** only. `country` is required — any other value is rejected with `400`.
This endpoint is read-only and safe to poll while an onboarding is in flight.
***
## Reading the Response
| Field | Description |
| - | - |
| `hasVirtualAccount` | The user already has an active virtual account in this country. |
| `canRequest` | A [request](/reference/bankingonboardingpartnerv2controller_requestonboarding) right now would be accepted — KYB complete, no existing virtual account, and no onboarding in flight. Use this to gate a "Get an account" button. |
| `status` | State of the user's most recent onboarding for this country. |
| `missingRequirements` | What is still outstanding on the user's KYB. Empty once KYB is complete. |
### Status Values
| `status` | Meaning |
| - | - |
| `NOT_STARTED` | No onboarding has ever been created for this user and country. |
| `IN_PROGRESS` | Submitted to the banking provider, awaiting a decision. Keep polling. |
| `COMPLETED` | Approved and provisioned. The account is available from [List Bank Accounts](/reference/userbankinfopartnerv2controller_getuserbankinfosbyuserid). |
| `FAILED` | The onboarding did not complete. `canRequest` returns to `true`, so it can be retried. |
`status: "COMPLETED"` and `hasVirtualAccount: true` are related but not identical. `status` reflects the onboarding record; `hasVirtualAccount` reflects whether an active account actually exists for the user today. Gate your UI on `canRequest`, and fetch the account details once `hasVirtualAccount` is `true`.
### Polling
There is no webhook for virtual account onboarding. After a `SUBMITTED` response, poll this endpoint until `status` is `COMPLETED` or `FAILED`. Provider approval typically takes minutes to hours, so poll every few minutes rather than every few seconds.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" — `country` is missing, not `US`, or `userId` is not a valid UUID |
| 404 | `NOT_FOUND_ERROR` | "User not found" |
# Request Virtual Account (User)
Source: https://docs.capa.fi/reference/bankingonboardingpartnerv2controller_requestonboarding
reference/openapi/BankingOnboardingPartnerV2Controller_requestOnboarding.json POST /api/partner/v2/banks/users/{userId}/banking-onboarding
Starts banking onboarding for one of your users so Capa can provision a named virtual account in their own name. Onboarding runs asynchronously — a `SUBMITTED` response only means the request was accepted. Poll the status endpoint until it reports `COMPLETED`, then read the provisioned account from the bank accounts endpoint. Currently available for the United States only.
Starts banking onboarding for one of your users so Capa can provision a **named virtual account** — a US bank account (account number + ABA routing number) issued in the user's own name. See the [Virtual Accounts guide](/docs/virtual-accounts) for the full flow.
Currently available for the **United States (`US`)** only. Any other `country` value is rejected with `400`.
Onboarding is **asynchronous**. A `SUBMITTED` response only means the request was accepted — the account is not usable yet. Poll [Get Virtual Account Status](/reference/bankingonboardingpartnerv2controller_getonboardingstatus) until it reports `COMPLETED`, then read the account from [List Bank Accounts](/reference/userbankinfopartnerv2controller_getuserbankinfosbyuserid).
***
## Response Statuses
A `201` does not always mean a new onboarding was started. Always branch on `data.status`:
| `status` | Meaning | What to do |
| - | - | - |
| `SUBMITTED` | Onboarding was created and sent to the banking provider. | Poll the status endpoint until `COMPLETED`. |
| `ALREADY_IN_PROGRESS` | An onboarding for this user and country is already running. Nothing new was created. | Keep polling — the call is safe to repeat. |
| `ALREADY_COMPLETED` | The user already completed onboarding for this country. | Read the account from [List Bank Accounts](/reference/userbankinfopartnerv2controller_getuserbankinfosbyuserid). |
| `REQUIREMENTS_MISSING` | The user's KYB is not complete. **Nothing was submitted.** | Send the user to `kybLink` to finish KYB, then retry. |
The endpoint is safe to call repeatedly: when an onboarding is already in flight or already complete, it returns the existing record instead of starting a second one.
## Missing Requirements
`status: "REQUIREMENTS_MISSING"` means the user's KYB is not complete. Nothing was submitted. The response carries:
* **`kybLink`** — a hosted link where the user can finish their KYB. It may be absent if a link could not be minted; in that case, direct the user through your usual KYB flow.
* **`missingRequirements`** — what is still outstanding, each entry with a `field` path and a human-readable `message`. Useful for surfacing a reason in your UI, but you do not need to collect these fields yourself — completing KYB satisfies them.
```json theme={null}
{
"success": true,
"data": {
"status": "REQUIREMENTS_MISSING",
"kybLink": "https://verify.capa.fi/s/2f9c1b7e",
"missingRequirements": [
{ "field": "taxId", "message": "Tax ID is required for business onboarding" }
]
}
}
```
Once KYB is complete, call this endpoint again.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" — `country` is missing, not `US`, or `userId` is not a valid UUID |
| 404 | `NOT_FOUND_ERROR` | "User not found" |
# Create Cross-Ramp
Source: https://docs.capa.fi/reference/crossramppartnerv2controller_createpartnercrossramp
reference/openapi/CrossRampPartnerV2Controller_createPartnerCrossRamp.json POST /api/partner/v2/cross-ramp
Creates a cross-ramp transaction on behalf of a **verified** user. This converts one fiat currency to another (e.g., MXN to USD) and initiates the transfer to the target bank account.
For sandbox transaction testing, use the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide).
***
## Idempotency
Retry safely without creating duplicate transactions: pass an optional **`Idempotency-Key`** header (UUIDv4). A retry with the same key and body replays the original response. See the [Idempotency Guide](/docs/idempotency) for the full contract.
```shell theme={null}
curl --request POST \
--url https://api.sandbox.capa.fi/api/partner/v2/cross-ramp \
--header 'Content-Type: application/json' \
--header 'partner-api-key: ' \
--header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
--data '...'
```
***
## Field Relationships
* Provide either `sourceAmount` or `targetAmount`, not both.
* Provide either `targetBankAccount` (inline bank details) or `targetBankAccountId` (reference to a saved bank account).
* `sourceCurrency` and `targetCurrency` must differ.
* `targetCurrency` must match the currency of the target bank account's country.
***
## Bank Account Requirements by Country
| Country | Required Fields |
| - | - |
| **MX** | `accountIdentifier` (18-digit CLABE) |
| **DO** | `accountIdentifier`, `bankName`, `accountType`, `documentIdentifier`, `documentType` |
| **US** | `accountIdentifier`, `bankName`, `routingNumber`, `accountHolder`, `address` |
| **SEPA** | `iban`, `bic`, `bankName`, `accountHolder` |
| **CN / HK** | `accountIdentifier`, `bankName`, `accountType`, `accountHolder`, `address`; `bic` optional (SWIFT) |
***
## China & Hong Kong Destinations
USD can be delivered to bank accounts in **China (CN)** and **Hong Kong (HK)** as **destination-only** corridors:
* **USD only** — CNY is not yet supported. Since source and target currencies must differ, the source currency must be **MXN, DOP, or EUR**.
* **`targetCountry`** — set to `"CN"` or `"HK"` to route USD there (USD otherwise defaults to the US); resolved from the target bank account country when omitted.
* **`targetRail`** — `LOCAL` (default) or `SWIFT`.
* **`invoiceFile`** — Base64-encoded PDF invoice, **required** when the target country is `CN`.
* **`reference`** — optional memo (max 140 chars) forwarded to the payment provider. Supported across all currencies.
See the [Cross-Ramp guide](/docs/cross-ramp#china--hong-kong-destinations) for the full flow.
***
## Integration Flow
[POST /api/partner/v2/users](/reference/userpartnercontrollerv2_createuser), then [KYC verification](/reference/userpartnercontrollerv2_createverificationlink)
[POST /api/partner/v2/cross-ramp/quotes](/reference/crossramppartnerv2controller_createpartnercrossrampquote) — locks the exchange rate
`POST /api/partner/v2/cross-ramp` (this endpoint)
The user deposits fiat in the source currency to the bank account in `sourceBankAccount`.
***
## Important Notes
* **User must be KYC-verified** before creating transactions.
* **Supported corridors**: MX, DO, US, and SEPA countries. USD can also be delivered to **CN (China)** and **HK (Hong Kong)** as destination-only corridors.
* **Amount limits**: Fiat amounts must fall within the min/max thresholds defined in your partner agreement.
***
## Use Cases
* **International Remittances**: Convert MXN to USD and send funds to a US bank account.
* **Multi-Currency Payouts**: Deliver funds in a different fiat currency from the source deposit.
* **Cross-Border B2B Payments**: Facilitate business payments across currencies.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Verified User Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `REQUIRED_USER_ID_ERROR` | "This endpoint requires a user id to be provided" |
| 403 | `USER_NOT_VERIFIED_ERROR` | "User is not allowed to perform the operations because has not completed the KYC verification." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Either targetBankAccount or targetBankAccountId must be provided" |
| 400 | `BAD_REQUEST` | "Unsupported country for cross-ramp: " |
| 400 | `BAD_REQUEST` | "invoiceFile is required when target bank account country is CN" |
| 400 | `BAD_REQUEST` | "Target currency must match target bank account currency (expected: , actual: )" |
| 400 | `BAD_REQUEST` | "Source and target currencies must differ" |
| 400 | `BAD_REQUEST` | "Requested fiat currency is invalid or inactive" |
| 400 | `BAD_REQUEST` | "Provided quoteId is not a CROSS\_RAMP quote" |
| 400 | `BAD_REQUEST` | "Quote currency mismatch (expected: , actual: )" |
| 400 | `QUOTE_EXPIRED` | "Quote has expired" |
| 400 | `BAD_REQUEST` | "Cannot create transaction: No supported banks configured for user with id: " |
| 403 | `USER_BANK_INFO_ACCESS_DENIED` | "The bank information does not belong to the specified user." |
| 404 | `USER_BANK_INFO_NOT_FOUND` | "User bank information not found." |
# Create Cross-Ramp Quote
Source: https://docs.capa.fi/reference/crossramppartnerv2controller_createpartnercrossrampquote
reference/openapi/CrossRampPartnerV2Controller_createPartnerCrossRampQuote.json POST /api/partner/v2/cross-ramp/quotes
Creates a locked cross-ramp quote with a guaranteed exchange rate. Pass the returned `quoteId` when creating a cross-ramp transaction.
***
## Field Relationships
* Provide either `sourceAmount` or `targetAmount`, not both.
***
## Important Notes
* Quotes expire — check the `expiresAt` field and create the transaction before it lapses.
* Pass the returned `quoteId` to [Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp).
* To quote a USD payout to **China (CN)** or **Hong Kong (HK)**, pass `targetCountry: "CN"` (or `"HK"`) and optionally `targetRail` (`LOCAL` default, or `SWIFT`). See the [Cross-Ramp guide](/docs/cross-ramp#china--hong-kong-destinations).
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Requested fiat currency is invalid or inactive" |
| 500 | `INTERNAL_SERVER_ERROR` | "Failed to create quote" |
# Get Cross-Ramp Rate
Source: https://docs.capa.fi/reference/crossramppartnerv2controller_getpartnercrossrampquoterate
reference/openapi/CrossRampPartnerV2Controller_getPartnerCrossRampQuoteRate.json GET /api/partner/v2/cross-ramp/quotes
Retrieves a real-time exchange rate for a fiat-to-fiat (cross-ramp) conversion. Use this to display estimated amounts before creating a transaction.
***
## Field Relationships
* Provide either `sourceAmount` or `targetAmount`, not both.
***
## Important Notes
* This returns a real-time rate that is **not locked**. To lock a rate, use [POST /api/partner/v2/cross-ramp/quotes](/reference/crossramppartnerv2controller_createpartnercrossrampquote).
* To quote a USD payout to **China (CN)** or **Hong Kong (HK)**, pass `targetCountry=CN` (or `HK`) and optionally `targetRail` (`LOCAL` default, or `SWIFT`). See the [Cross-Ramp guide](/docs/cross-ramp#china--hong-kong-destinations).
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Requested fiat currency is invalid or inactive" |
# Get KYB Details
Source: https://docs.capa.fi/reference/kybpartnercontrollerv2_getuserkybdetails
reference/openapi/KYBPartnerControllerV2_getUserKYBDetails.json GET /api/partner/v2/kyb/details
Retrieves the latest KYB (Know Your Business) verification details for a business user. Use this to check verification status and retrieve business profile data.
***
## Important Notes
* The `sessionUrl` field contains a URL to resume an in-progress KYB session, if available.
* KYB status values: `NOT_STARTED`, `IN_PROGRESS`, `VERIFIED`, `REJECTED`, `REVIEW_NEEDED`.
***
## Use Cases
* **Verification status check**: Determine whether a business user has completed KYB before allowing transactions.
* **Resume verification**: Use `sessionUrl` to redirect users with an in-progress session.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 404 | `NOT_FOUND_ERROR` | "Requested KYB verification was not found" |
| 500 | `INTERNAL_SERVER_ERROR` | "Failed to get user KYB details" |
# Submit Business Profile
Source: https://docs.capa.fi/reference/kybpartnercontrollerv2_submitbusinessprofile
reference/openapi/KYBPartnerControllerV2_submitBusinessProfile.json POST /api/partner/v2/kyb
Submits a business's KYB (Know Your Business) data for verification. The endpoint accepts the submission and returns immediately with `202 Accepted` — the underlying AiPrise call chain (session creation, business profile creation, questionnaire, document uploads, related-person verification, and running the verification) is driven asynchronously in the background.
Poll [`GET /details`](/reference/kybpartnercontrollerv2_getuserkybdetails) to track progress and retrieve the final verification result.
***
## Important Notes
* This endpoint always returns `202 Accepted` with `submissionStage: "DRAFT"` and no `externalResourceId` — the AiPrise call chain hasn't started yet at response time.
* Only one KYB submission can be in progress per user at a time. Submitting again while a prior submission is `IN_PROGRESS` returns the existing verification instead of creating a new one.
* An optional `Idempotency-Key` header (16–128 printable ASCII characters) protects against duplicate submissions from client-side retries — a repeated request with the same key and body replays the original response instead of processing twice.
* `country` accepts `MX`, `US`, `DO`, or any SEPA-region country code for EUR-market businesses.
* Several fields are conditionally required based on other answers (e.g. `isUsMsb` fields only apply when `country` is `US`; `mx*` fields only apply when `country` is `MX`) — see each field's description in the schema.
* Every `relatedPersons` entry represents a UBO, director, authorized representative, controlling person, or legal representative — at least one is required. `taxRegistrationDocument`, `beneficialOwnerDeclarationDocument`, and `accountStatementDocument` are optional per person and only relevant in specific markets/products (see field descriptions).
* All document fields expect base64-encoded file content (`fileBase64`) alongside a `fileName`.
***
## Use Cases
* **Business onboarding**: Collect and submit a business's full KYB questionnaire and supporting documents in a single call without blocking on AiPrise's processing time.
* **Status tracking**: Combine with `GET /details` to build a progress UI for partners while the verification runs in the background.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Idempotency Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `BAD_REQUEST` | "Idempotency-Key "..." must be 16–128 printable ASCII characters" |
| 409 | `CONFLICT` | "Idempotency-Key was already used with a different request body" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `BAD_REQUEST` | Request body validation failure (missing/invalid field) |
| 422 | `UNPROCESSABLE_ENTITY` | "KYB submission requires a BUSINESS user" |
| 500 | `INTERNAL_SERVER_ERROR` | "Internal server error" |
# Get KYC Details
Source: https://docs.capa.fi/reference/kycpartnercontrollerv2_getuserverificationdetails
reference/openapi/KYCPartnerControllerV2_getUserVerificationDetails.json GET /api/partner/v2/kyc/details
Retrieves the latest KYC verification details for a user. Use this to check verification status, inspect ID data, face match scores, AML results, and fraud signals after the user completes the KYC session.
***
## Important Notes
* Verification results are only available **after** the user completes the full KYC session.
* Some response fields may be `null` depending on the verification provider and user input.
* This is a read-only endpoint — it does not trigger updates or notifications.
* If a session has expired or was inconclusive, generate a new verification link via [KYC Link Generation](/reference/userpartnercontrollerv2_createverificationlink).
***
## Use Cases
* **Compliance review**: Inspect ID, liveness, AML, and fraud data for regulatory review.
* **Automated decisioning**: Use face match scores and fraud signals to determine onboarding acceptance.
* **Support investigation**: Inspect verification artifacts during dispute resolution.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 404 | `NOT_FOUND_ERROR` | "Requested KYC was not found" |
| 500 | `INTERNAL_SERVER_ERROR` | "Failed to get user verification" |
# Create Off-Ramp
Source: https://docs.capa.fi/reference/offramppartnerv2controller_createpartnerofframp
reference/openapi/OffRampPartnerV2Controller_createPartnerOffRamp.json POST /api/partner/v2/off-ramp
Creates an off-ramp transaction on behalf of a **verified** user. This converts crypto to fiat and initiates a withdrawal to the user's registered bank account.
For sandbox transaction testing, use the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide).
***
## Idempotency
Retry safely without creating duplicate transactions: pass an optional **`Idempotency-Key`** header (UUIDv4). A retry with the same key and body replays the original response. See the [Idempotency Guide](/docs/idempotency) for the full contract.
```shell theme={null}
curl --request POST \
--url https://api.sandbox.capa.fi/api/partner/v2/off-ramp \
--header 'Content-Type: application/json' \
--header 'partner-api-key: ' \
--header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
--data '...'
```
***
## Field Relationships
* Provide `quoteId` **OR** (`fiatCurrency` + `blockchainSymbol` + `tokenSymbol`). When a quote is used, currency/token fields are inherited from the quote.
* Provide either `fiatAmount` or `cryptoAmount`, not both.
* Provide either `userBankInformation` (inline bank details) or `userBankInformationId` (reference to a saved bank account).
***
## Bank Account Requirements by Country
| Country | Required Fields |
| - | - |
| **MX** | `accountIdentifier` (18-digit CLABE) |
| **DO** | `accountIdentifier`, `bankName`, `accountType`, `documentIdentifier`, `documentType` |
| **US** | `accountIdentifier`, `bankName`, `routingNumber`, `accountHolder`, `address` |
| **SEPA** | `iban`, `bic`, `bankName`, `accountHolder` |
***
## Integration Flow
[POST /api/partner/v2/users](/reference/userpartnercontrollerv2_createuser)
[POST /api/partner/v2/users//kyc/verification-link](/reference/userpartnercontrollerv2_createverificationlink)
[POST /api/partner/v2/quotes](/reference/quotepartnerv2controller_createpartnerquote) — locks the exchange rate
`POST /api/partner/v2/off-ramp` (this endpoint) — returns a `destinationWalletAddress`
The user sends crypto to the `destinationWalletAddress` from the response.
[POST /api/partner/v2/off-ramp/execute](/reference/offramppartnerv2controller_executeofframp) — triggers bank payout after crypto is received
***
## Important Notes
* **User must be KYC-verified** before creating transactions.
* **Mexico bank verification**: First-time bank accounts trigger a penny test to verify the KYC'd user is the account owner. If verification fails, the transaction is cancelled.
* **CLABE validation (Mexico)**: Must match `^\d{18}$`. Consider using [clabe-validator](https://www.npmjs.com/package/clabe-validator).
* **Amount limits**: Fiat and crypto amounts must fall within the min/max thresholds defined in your partner agreement.
* **Destination wallet**: The response includes a unique `destinationWalletAddress` — the user must send crypto to this address.
***
## Use Cases
* **User Payout**: Convert crypto to fiat and withdraw to a verified bank account.
* **B2C Integration**: Let individual users off-ramp from within your app.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Verified User Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `REQUIRED_USER_ID_ERROR` | "This endpoint requires a user id to be provided" |
| 403 | `USER_NOT_VERIFIED_ERROR` | "User is not allowed to perform the operations because has not completed the KYC verification." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Either quoteId or fiatCurrency, blockchainSymbol, tokenSymbol, and at least one of fiatAmount or cryptoAmount must be provided" |
| 400 | `BAD_REQUEST` | "Fiat currency is disabled or does not exist" |
| 400 | `BAD_REQUEST` | "This blockchain and token combination is disabled or does not exist" |
| 400 | `QUOTE_EXPIRED` | "Quote has expired" |
| 400 | `BAD_REQUEST` | "Quote is not valid for OFF\_RAMP" |
| 400 | `INVALID_TOKEN_OPERATION_ERROR` | "Token cannot be operated as OTC, only stable coins are allowed" |
| 400 | `BAD_REQUEST` | "Either userBankInformationId or userBankInformation must be provided" |
| 400 | `INVALID_FIAT_AMOUNT_ERROR` | "Fiat amount is outside of the allowed range for OFF\_RAMP. Should be between and " |
| 400 | `INVALID_TOKEN_AMOUNT_ERROR` | "Token amount is outside of the allowed range for OFF\_RAMP. Should be between and " |
| 403 | `USER_BANK_INFO_ACCESS_DENIED` | "The bank information does not belong to the specified user." |
| 403 | `USER_BANK_INFO_ACCESS_DENIED` | "The bank information belongs to a different partner." |
| 404 | `USER_BANK_INFO_NOT_FOUND` | "User bank information not found." |
# Execute Off-Ramp
Source: https://docs.capa.fi/reference/offramppartnerv2controller_executeofframp
reference/openapi/OffRampPartnerV2Controller_executeOffRamp.json POST /api/partner/v2/off-ramp/execute
Executes a previously created off-ramp transaction. This endpoint triggers the actual processing of the off-ramp transaction after crypto funds have been received.
Executes a previously created off-ramp transaction, triggering the fiat payout to the user's bank account. Call this after the user has sent crypto to the `destinationWalletAddress` returned by the [Create Off-Ramp](/reference/offramppartnerv2controller_createpartnerofframp) endpoint.
***
## Transaction Status Values
| Status | Description |
| :- | :- |
| `PENDING_PAYMENT` | Awaiting the user's crypto deposit. |
| `IN_PROGRESS` | Transaction is being processed. |
| `COMPLETED` | Transaction has completed successfully. |
| `FAILED` | Transaction has failed. |
| `CANCELLED` | Transaction was cancelled. |
| `CRYPTO_RECEIVED` | Crypto payment has been received. |
| `CRYPTO_NOT_RECEIVED` | Crypto payment was not received. |
| `COMPLETED_CRYPTO_RECEIVAL` | Crypto receival process has completed. |
| `WAITING_FOR_FUNDS` | Awaiting fiat liquidity for the payout. |
| `BANK_TRANSFER_PENDING` | Bank transfer is pending. |
| `READY_BANK_TRANSFER` | Ready to initiate bank transfer. |
***
## Important Notes
* The `transactionId` must reference a valid off-ramp transaction created via [Create Off-Ramp](/reference/offramppartnerv2controller_createpartnerofframp).
* The transaction must belong to the partner associated with the API key.
* Optionally include `transactionHash`, the blockchain hash of the user's crypto transfer, to help Capa locate and confirm the incoming funds. It must be a valid blockchain hash.
***
## Use Cases
* **Manual Execution**: Trigger execution after verifying crypto receipt.
* **Workflow Integration**: Separate transaction creation from execution in automated workflows.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 404 | `TRANSACTION_NOT_FOUND` | "Transaction not found" |
| 403 | `TRANSACTION_ACCESS_DENIED` | "Transaction does not belong to this partner" |
# Create On-Ramp
Source: https://docs.capa.fi/reference/onramppartnerv2controller_createpartneronramp
reference/openapi/OnRampPartnerV2Controller_createPartnerOnRamp.json POST /api/partner/v2/on-ramp
Creates an on-ramp transaction on behalf of a **verified** user. This converts fiat to crypto — the user deposits fiat via bank transfer and receives crypto at the specified wallet address. The response returns the bank account details for the fiat deposit.
For sandbox transaction testing, use the [Mock Testing Guide](/reference/transactionpartnerv2controller_getmocktestingguide).
***
## Idempotency
Retry safely without creating duplicate transactions: pass an optional **`Idempotency-Key`** header (UUIDv4). A retry with the same key and body replays the original response. See the [Idempotency Guide](/docs/idempotency) for the full contract.
```shell theme={null}
curl --request POST \
--url https://api.sandbox.capa.fi/api/partner/v2/on-ramp \
--header 'Content-Type: application/json' \
--header 'partner-api-key: ' \
--header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
--data '...'
```
***
## Field Relationships
* Provide `quoteId` **OR** (`fiatCurrency` + `blockchainSymbol` + `tokenSymbol`). When a quote is used, currency/token fields are inherited from the quote.
* Provide either `fiatAmount` or `cryptoAmount`, not both.
* Provide either `destinationWalletAddress` (inline) or `walletAddressId` (reference to a saved wallet).
***
## Integration Flow
[POST /api/partner/v2/users](/reference/userpartnercontrollerv2_createuser)
[POST /api/partner/v2/users//kyc/verification-link](/reference/userpartnercontrollerv2_createverificationlink)
[POST /api/partner/v2/quotes](/reference/quotepartnerv2controller_createpartnerquote) — locks the exchange rate
`POST /api/partner/v2/on-ramp` (this endpoint) — returns bank account details for the fiat deposit
The user sends fiat to the bank account returned in the `bankAccount` response field.
***
## Important Notes
* **User must be KYC-verified** before creating transactions.
* **Wallet type must match blockchain**: An EVM wallet cannot be used on a Solana chain, and vice versa.
* **Amount limits**: Fiat and crypto amounts must fall within the min/max thresholds defined in your partner agreement.
***
## Use Cases
* **Buy Crypto**: Enable users to purchase cryptocurrency using fiat deposited via bank transfer.
* **B2C Integration**: Let individual users on-ramp from within your app.
* **Automated Purchases**: Programmatically initiate fiat-to-crypto conversions for your users.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Verified User Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `REQUIRED_USER_ID_ERROR` | "This endpoint requires a user id to be provided" |
| 403 | `USER_NOT_VERIFIED_ERROR` | "User is not allowed to perform the operations because has not completed the KYC verification." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "address must be a valid address" |
| 400 | `BAD_REQUEST` | "Either quoteId or fiatCurrency, blockchainSymbol, tokenSymbol, and at least one of fiatAmount or cryptoAmount must be provided" |
| 400 | `BAD_REQUEST` | "Fiat currency is disabled or does not exist" |
| 400 | `BAD_REQUEST` | "This blockchain and token combination is disabled or does not exist" |
| 400 | `QUOTE_EXPIRED` | "Quote has expired" |
| 400 | `BAD_REQUEST` | "Quote is not valid for ON\_RAMP" |
| 400 | `INVALID_TOKEN_OPERATION_ERROR` | "Token cannot be operated as OTC, only stable coins are allowed" |
| 400 | `BAD_REQUEST` | "Either walletAddressId or destinationWalletAddress must be provided" |
| 400 | `BAD_REQUEST` | "Cannot create transaction: No supported banks configured for user with id: " |
| 400 | `INVALID_FIAT_AMOUNT_ERROR` | "Fiat amount is outside of the allowed range for ON\_RAMP. Should be between and " |
| 400 | `INVALID_TOKEN_AMOUNT_ERROR` | "Token amount is outside of the allowed range for ON\_RAMP. Should be between and " |
# Get Webhook
Source: https://docs.capa.fi/reference/partnerwebhooknotificationv2controller_getwebhooknotificationdetail
reference/openapi/PartnerWebhookNotificationV2Controller_getWebhookNotificationDetail.json GET /api/partner/v2/webhook-notifications/{id}
Retrieves the full details of a single webhook notification, including its original payload, delivery status, retry history, and the error response from the last failed attempt.
For an overview of webhook events and payload structures, see the [Webhooks Guide](/docs/how-our-webhooks-work).
***
## Important Notes
* **`latestErrorPayload`**: Contains the error body returned by your endpoint on the last failed delivery attempt. Use this to diagnose why delivery failed (e.g. a `500` from your server).
* **`retryAttempts`**: The number of automatic delivery attempts made by Capa. After the maximum is reached, status transitions to `FAILED`.
* **`manualRetryCount`**: Incremented each time you call the [retry endpoint](/reference/partnerwebhooknotificationv2controller_retry). A maximum of 3 manual retries are allowed per notification.
***
## Status Reference
| Status | Description |
| - | - |
| `PENDING` | Queued for delivery or currently being retried |
| `SENT` | Successfully delivered to your endpoint |
| `FAILED` | All automatic delivery attempts exhausted |
| `NOT_SENT` | No webhook URL was configured at the time of the event |
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 404 | `NOT_FOUND` | "Webhook notification not found" |
# List Webhooks
Source: https://docs.capa.fi/reference/partnerwebhooknotificationv2controller_listwebhooknotifications
reference/openapi/PartnerWebhookNotificationV2Controller_listWebhookNotifications.json GET /api/partner/v2/webhook-notifications
Returns a paginated list of webhook notifications sent on behalf of your partner account. Filter by `status` to quickly surface failed deliveries that may need attention.
For an overview of webhook events and payload structures, see the [Webhooks Guide](/docs/how-our-webhooks-work).
***
## Important Notes
* **Scoped access**: Only notifications belonging to your partner account are returned.
* **Pagination**: Use `skip` and `limit` to page through results. `limit` defaults to 20 and cannot exceed 100.
* **`eventType`**: The `eventType` field reflects the `event` value from the notification payload (e.g. `CREATED_ON_RAMP`, `CREATED_CROSS_RAMP`, `VERIFIED_USER`).
***
## Use Cases
* **Monitor failed deliveries**: Filter by `status=FAILED` to identify notifications that need to be retried.
* **Reconciliation**: Cross-reference notification timestamps against your internal event log.
* **Audit trail**: Review all webhook activity for compliance or debugging.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" (invalid query parameter) |
# Retry Webhook
Source: https://docs.capa.fi/reference/partnerwebhooknotificationv2controller_retry
reference/openapi/PartnerWebhookNotificationV2Controller_retry.json POST /api/partner/v2/webhook-notifications/{id}/retry
Manually retries a `FAILED` webhook notification, queuing it for immediate re-delivery to your configured webhook URL. The automatic retry cycle is reset and `manualRetryCount` is incremented.
***
## Important Notes
* **Status requirement**: The notification must be in `FAILED` status. Attempting to retry a `SENT`, `PENDING`, or `NOT_SENT` notification returns a `409`.
* **Manual retry limit**: Each notification allows a maximum of **3 manual retries**. Once reached, the endpoint returns `409` and the notification cannot be retried again.
* **Rate limit**: A maximum of **10 manual retries per partner per minute** is enforced. Exceeding this returns `429`.
* **Automatic retry reset**: On a successful retry request, `retryAttempts` is reset to `0` and `latestErrorPayload` is cleared, giving the notification a fresh delivery cycle.
***
## When to Retry vs. When to Contact Support
**Retry when:**
* Your endpoint was temporarily unavailable (e.g. a deployment, a transient 5xx).
* You fixed a bug in your webhook handler and need the event reprocessed.
* The `latestErrorPayload` shows a recoverable error (connection timeout, 503, etc.).
**Contact support when:**
* Your webhook URL has changed — update it first via [PUT /api/partner/v2/webhook-settings](/reference/partnerwebhooksettingsv2controller_update), then retry.
* The event payload is stale and the corresponding transaction has already been handled through another channel.
* You have exhausted all 3 manual retries and still cannot receive the notification.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 404 | `NOT_FOUND` | "Webhook notification not found" |
| 409 | `CONFLICT` | "Notification is not in FAILED status" |
| 409 | `CONFLICT` | "Partner webhook notification has reached the maximum number of manual retries (3)" |
| 429 | `TOO_MANY_REQUESTS` | "Rate limit exceeded. Try again in a few seconds." |
# Update Webhook Settings
Source: https://docs.capa.fi/reference/partnerwebhooksettingsv2controller_update
reference/openapi/PartnerWebhookSettingsV2Controller_update.json PUT /api/partner/v2/webhook-settings
Updates the webhook URL where your partner account receives event notifications. To disable webhook delivery, send `null` as the `url` value.
For details on webhook payloads and HMAC validation, see the [Webhooks Guide](/docs/how-our-webhooks-work).
***
## Use Cases
* **Set a webhook URL**: Start receiving notifications for transaction status changes and KYC events.
* **Update the URL**: Point notifications to a new server after a migration.
* **Disable webhooks**: Send `url: null` to stop receiving notifications.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
# Create Quote
Source: https://docs.capa.fi/reference/quotepartnerv2controller_createpartnerquote
reference/openapi/QuotePartnerV2Controller_createPartnerQuote.json POST /api/partner/v2/quotes
Creates a locked quote with a guaranteed exchange rate. Pass the returned `quoteId` when creating a transaction to use this rate.
***
## Field Relationships
* `cryptoAmount` is required for `OFF_RAMP` transactions, `fiatAmount` is required for `ON_RAMP`.
* `country` and `rail` are optional overrides for non-default currency/country combinations.
* `forwardingDays` (0–4) is only supported for MXN OTC transactions (T+N settlement).
***
## Important Notes
* Quotes expire — check the `expiresAt` field and create the transaction before it lapses.
* Pass the returned `quoteId` to [Create On-Ramp](/reference/onramppartnerv2controller_createpartneronramp), [Create Off-Ramp](/reference/offramppartnerv2controller_createpartnerofframp), or [Create Cross-Ramp](/reference/crossramppartnerv2controller_createpartnercrossramp).
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Fiat currency is disabled or does not exist" |
| 400 | `BAD_REQUEST` | "This blockchain and token combination is disabled or does not exist" |
| 500 | `INTERNAL_SERVER_ERROR` | "Failed to create quote" |
# Get Quote Rate
Source: https://docs.capa.fi/reference/quotepartnerv2controller_getpartnerquoterate
reference/openapi/QuotePartnerV2Controller_getPartnerQuoteRate.json GET /api/partner/v2/quotes
Retrieves a real-time exchange rate quote for an on-ramp or off-ramp transaction. Use this to display estimated conversion amounts to users before they commit to a transaction.
***
## Field Relationships
* `cryptoAmount` is required for `OFF_RAMP` transactions, `fiatAmount` is required for `ON_RAMP`.
* `country` and `rail` are optional overrides — use them only when the currency is not the default for the country (e.g., USD in China via SWIFT).
***
## Important Notes
* This returns a **real-time rate** that is not locked. To lock a rate, use [POST /api/partner/v2/quotes](/reference/quotepartnerv2controller_createpartnerquote) instead.
* The `flow` field is always `OTC` — every on-ramp, off-ramp, and cross-ramp now uses the OTC lifecycle regardless of amount.
* Use consistent `tokenSymbol`, `blockchainSymbol`, and `fiatCurrency` values when creating the actual transaction.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Fiat currency is disabled or does not exist" |
| 400 | `BAD_REQUEST` | "This blockchain and token combination is disabled or does not exist" |
| 500 | `INTERNAL_SERVER_ERROR` | "Internal error doing quote" |
# Cancel Transaction
Source: https://docs.capa.fi/reference/transactionpartnerv2controller_cancelpartnertransaction
reference/openapi/TransactionPartnerV2Controller_cancelPartnerTransaction.json PUT /api/partner/v2/transactions/{id}/cancel
Cancels a previously created on-ramp, off-ramp, or cross-ramp transaction. Cancellation is only allowed if the transaction has not yet received funds.
***
## Important Notes
* Only transactions in a **cancellable state** can be cancelled: `PENDING_PAYMENT` (on-ramp and off-ramp), `COMPLETED_CRYPTO_RECEIVAL` (off-ramp), and `PENDING_FUNDS` (cross-ramp).
* Completed or locked transactions **cannot** be cancelled.
* The transaction must belong to the authenticated partner account.
* This operation is **idempotent** — calling it multiple times on a cancelled transaction will not cause errors.
***
## Use Cases
* **Abort abandoned payments**: Cancel stuck or forgotten transaction flows.
* **Back-office controls**: Let support teams reverse pending transactions before execution.
* **Automatic timeout flows**: Cancel after inactivity via cron jobs or watchdog logic.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Invalid transaction state transition" |
| 403 | `PARTNER_NOT_OWNER_OF_TRANSACTION_ERROR` | "Partner is not the owner of the transaction." |
| 403 | `USER_NOT_OWNER_OF_TRANSACTION_ERROR` | "User is not the owner of the transaction." |
# Mock Testing Guide
Source: https://docs.capa.fi/reference/transactionpartnerv2controller_getmocktestingguide
reference/openapi/TransactionPartnerV2Controller_getMockTestingGuide.json GET /api/partner/v2/transactions/mock-testing-guide
Returns the current mock testing configuration for the sandbox environment. Use specific transaction amounts to simulate different outcomes.
Mock testing is only available in the **sandbox environment**.
***
## Mock Rules
The `10.01`, `10.02`, and `10.03` amounts have been **retired**. They collided with genuine small-value sandbox orders, so a real `10.01` transaction was silently completed by the mock path. If your integration is pinned to any of those amounts, move it to the `1000.0x` band below.
### On-Ramp Transactions
Use `fiatAmount` to trigger specific behaviors:
| Fiat Amount | Expected Status | Description |
| - | - | - |
| `1000.01` | `COMPLETED` | OTC transaction completes successfully |
| `1000.02` | `CANCELLED` (NAME\_VERIFICATION\_FAILED) | OTC bank beneficiary is not the KYC'd user |
### Off-Ramp Transactions
Use `cryptoAmount` to trigger specific behaviors:
| Crypto Amount | Expected Status | Description |
| - | - | - |
| `1000.01` | `COMPLETED` | OTC transaction completes successfully |
| `1000.02` | `CANCELLED` (NAME\_VERIFICATION\_FAILED) | OTC bank beneficiary is not a KYC'd user |
| `1000.03` | `CRYPTO_NOT_RECEIVED` | OTC failure due to no crypto transfer |
### Cross-Ramp Transactions
Use `sourceAmount` to trigger specific behaviors:
| Source Amount | Expected Status | Description |
| - | - | - |
| `2000.01` | `COMPLETED` | Walks the full lifecycle: `PENDING_FUNDS` → `FUNDS_RECEIVED` → `IN_PROGRESS` → `AWAITING_FUND_TRANSFER` → `COMPLETED` |
| `2000.02` | `CANCELLED` (NAME\_VERIFICATION\_FAILED) | Bank beneficiary is not a KYC'd user |
| `2000.03` | `AWAITING_FUND_TRANSFER` | Halts at `AWAITING_FUND_TRANSFER` so the payout leg can be exercised |
For example, to create a cross-ramp that halts at `AWAITING_FUND_TRANSFER`:
```json theme={null}
{
"userId": "12a121ad-cbea-4ec4-9e0a-5c861e528bba",
"sourceAmount": 2000.03,
"sourceCurrency": "MXN",
"targetCurrency": "USD",
"targetBankAccountId": "0b7e1c9f-3a4d-4f21-9a2e-5d8c6b1f4e30"
}
```
Cross-ramp uses its own `2000.0x` band rather than reusing the on/off-ramp amounts. The internal on-ramp leg of a cross-ramp inherits the parent's source amount, so a cross-ramp at `1000.01` would trigger the on-ramp rule underneath the cross-ramp one.
**Cross-ramp specifics:**
* Only `sourceAmount` triggers a mock. On the `targetAmount` path the source amount is fee-derived and will never match a magic value.
* If you create the transaction from a `quoteId`, the quote itself must have been created with `sourceAmount` set to one of the magic values.
* The transaction advances through the real state machine, so every cross-ramp webhook fires in order — `CREATED_CROSS_RAMP`, `FUNDS_RECEIVED_CROSS_RAMP`, `STARTED_PROCESSING_CROSS_RAMP`, `AWAITING_FUND_TRANSFER_CROSS_RAMP`, and `COMPLETED_CROSS_RAMP` (or `CANCELLED_CROSS_RAMP` for `2000.02`).
***
## Important Notes
* You must use the **exact amounts** specified above (1000.01, 1000.02, 1000.03, 2000.01, 2000.02, 2000.03).
* Transaction amount limits are not enforced for mock amounts. All other validation rules still apply (KYC status, etc.).
* The system automatically handles state transitions to reach the target status.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
# List Transactions
Source: https://docs.capa.fi/reference/transactionpartnerv2controller_listpartnerusertransactions
reference/openapi/TransactionPartnerV2Controller_listPartnerUserTransactions.json GET /api/partner/v2/transactions
Returns a paginated list of on-ramp, off-ramp, and cross-ramp transactions belonging to your partner account. Use query parameters to filter by status, type, user, or currency.
***
## Important Notes
* **Scoped access**: You can only retrieve transactions created via your own partner credentials.
* **Pagination**: Use `skip` and `limit` to paginate. `limit` must not exceed 100 (defaults to 20).
* Combine filters such as `status`, `type`, and `userId` freely.
***
## Use Cases
* **User dashboards**: Display past transactions per user.
* **Reconciliation**: Fetch completed transactions for accounting.
* **Customer support**: Inspect and manage pending transactions.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
# Get Wallet Balances
Source: https://docs.capa.fi/reference/userbalancepartnerv2controller_getuserwalletbalances
reference/openapi/UserBalancePartnerV2Controller_getUserWalletBalances.json GET /api/partner/v2/users/{userId}/wallets/balance
Retrieves the crypto balances for all wallets belonging to a user. Supports optional filtering by `blockchain` and `token`.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" (invalid UUID) |
# Add Bank Account (User)
Source: https://docs.capa.fi/reference/userbankinfopartnerv2controller_createuserbankinfobyuserid
reference/openapi/UserBankInfoPartnerV2Controller_createUserBankInfoByUserId.json POST /api/partner/v2/banks/users/{userId}/accounts
Creates a new bank account for a user. Required fields vary by country — see the [Off-Ramp bank requirements](/reference/offramppartnerv2controller_createpartnerofframp#bank-account-requirements-by-country) for details.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `INVALID_USER_INPUT` | "Bank name is required for Dominican Republic users" |
| 400 | `OFF_RAMP_USER_BANK_INFO_CREATION_FAILED` | "The provided bank: is not currently supported for this transaction." |
| 400 | `USER_BANK_INFO_CREATION_FAILED` | "Failed to create user bank information." |
# Delete Bank Account (User)
Source: https://docs.capa.fi/reference/userbankinfopartnerv2controller_deleteuserbankinfobyuserid
reference/openapi/UserBankInfoPartnerV2Controller_deleteUserBankInfoByUserId.json DELETE /api/partner/v2/banks/users/{userId}/accounts/{userBankInfoId}
Deletes a bank account for a user. This performs a soft delete by disabling the account.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 403 | `USER_BANK_INFO_ACCESS_DENIED` | "The bank information does not belong to the specified user." |
| 403 | `USER_BANK_INFO_ACCESS_DENIED` | "The bank information belongs to a different partner." |
| 404 | `USER_BANK_INFO_NOT_FOUND` | "User bank information not found." |
# List Bank Accounts (User)
Source: https://docs.capa.fi/reference/userbankinfopartnerv2controller_getuserbankinfosbyuserid
reference/openapi/UserBankInfoPartnerV2Controller_getUserBankInfosByUserId.json GET /api/partner/v2/banks/users/{userId}/accounts
Retrieves bank accounts for a user with pagination. Supports filtering by country, ramp type, or fetching a specific account by ID.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
# Create User
Source: https://docs.capa.fi/reference/userpartnercontrollerv2_createuser
reference/openapi/UserPartnerControllerV2_createUser.json POST /api/partner/v2/users
Creates a new user under your partner account. Each user must have a unique email address within the scope of the partner. After creation, initiate KYC verification before the user can transact.
***
## Important Notes
* Each `externalUserId` (if provided) must be unique per partner. Reuse will cause a conflict.
* Creating a user does **not** initiate KYC — use the [KYC verification link](/reference/userpartnercontrollerv2_createverificationlink) endpoint next.
* Set `type` to `INDIVIDUAL` for personal accounts or `BUSINESS` for corporate accounts. Business users use [KYB verification](/reference/userpartnercontrollerv2_generatekybverificationlink) instead of KYC.
* Store the returned `userId` — you'll need it for all subsequent API calls for this user.
***
## Use Cases
* **User onboarding**: Create users ahead of any financial operation.
* **CRM syncing**: Use `externalUserId` to map Capa users to your internal user model.
* **Business registration**: Set `type` to `BUSINESS` for corporate flows.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" (validation errors for type, email, externalUserId, alias) |
| 400 | `BAD_REQUEST` | "Provided email is not valid" |
| 500 | `INTERNAL_SERVER_ERROR` | "User creation failed" |
# Start KYC
Source: https://docs.capa.fi/reference/userpartnercontrollerv2_createuserkyc
reference/openapi/UserPartnerControllerV2_createUserKYC.json POST /api/partner/v2/users/{userId}/kyc
Submits KYC verification data directly for a user. Use this when you collect identity documents on your own platform and want to send them to Capa for verification programmatically, rather than redirecting to the KYC provider.
***
## Important Notes
* The user must be created first via [Create User](/reference/userpartnercontrollerv2_createuser).
* All document URLs (`frontIdUrl`, `backIdUrl`, `selfieUrl`) must be accessible pre-signed URLs.
* Verification is async — results are delivered via webhook or can be polled using [Get KYC Details](/reference/kycpartnercontrollerv2_getuserverificationdetails).
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `NO_KYC_TEMPLATE_FOUND` | "No KYC template found for country: " |
| 400 | `EMAIL_NOT_FOUND` | "Provided user authentication method is not email" |
| 400 | `BAD_REQUEST` | "Provided url is not valid" |
| 400 | `FAILED_TO_DOWNLOAD_FILE_FROM_PRESIGNED_URL` | "Failed to download file from presigned url" |
| 400 | `FAILED_TO_DOWNLOAD_DOCUMENT` | "Failed to download document" |
| 404 | `NOT_FOUND_ERROR` | "Requested User was not found" |
| 500 | `INTERNAL_SERVER_ERROR` | "Identity verification creation failed" |
# Create KYC Link
Source: https://docs.capa.fi/reference/userpartnercontrollerv2_createverificationlink
reference/openapi/UserPartnerControllerV2_createVerificationLink.json POST /api/partner/v2/users/{userId}/kyc/verification-link
Generates a direct KYC verification link for an existing user. Redirect the user to this link to complete identity verification with the KYC provider.
***
## Important Notes
* The user must be created first via [Create User](/reference/userpartnercontrollerv2_createuser).
* Each `kycLink` is tied to a unique session. If expired, generate a new link.
* If `partnerRedirectUri` is not provided, the user may not be redirected after completing KYC.
* Supports both HTTPS and custom mobile URI schemes (e.g., `myapp://`).
***
## Use Cases
* **Initiate KYC**: Redirect the user to complete identity verification after account creation.
* **Email or app link**: Embed the `kycLink` in an onboarding email or app screen.
* **Custom redirect**: Use `partnerRedirectUri` to bring the user back to your app after verification.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `INVALID_VERIFICATION_TYPE_FOR_USER` | " users can only request verification" |
| 400 | `KYC_TEMPLATE_NOT_CONFIGURED_ERROR` | "KYC template not configured for " |
| 404 | `NOT_FOUND_ERROR` | "Requested User was not found" |
| 500 | `INTERNAL_SERVER_ERROR` | "Failed to create verification link" |
# Create KYB Link
Source: https://docs.capa.fi/reference/userpartnercontrollerv2_generatekybverificationlink
reference/openapi/UserPartnerControllerV2_generateKYBVerificationLink.json POST /api/partner/v2/users/{userId}/kyb/verification-link
Generates a KYB (Know Your Business) verification link for a business user. Redirect the user to complete business verification with the provider.
***
## Important Notes
* Only users created with `type: "BUSINESS"` can request KYB verification. Individual users should use [KYC verification](/reference/userpartnercontrollerv2_createverificationlink).
* Each link is tied to a unique session. If expired, generate a new one.
* Check results via [Get KYB Details](/reference/kybpartnercontrollerv2_getuserkybdetails).
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `INVALID_VERIFICATION_TYPE_FOR_USER` | " users can only request verification" |
| 400 | `KYB_TEMPLATE_NOT_CONFIGURED_ERROR` | "KYB template not configured for " |
| 404 | `NOT_FOUND_ERROR` | "Requested User was not found" |
| 500 | `INTERNAL_SERVER_ERROR` | "Failed to create verification link" |
# Request Signature
Source: https://docs.capa.fi/reference/userpartnercontrollerv2_requestdocumentsignature
reference/openapi/UserPartnerControllerV2_requestDocumentSignature.json POST /api/partner/v2/users/{userId}/documents/sign
Initiates the document signing process for a user. Returns a URL where the user can sign required agreements for a specific country.
***
## Important Notes
* Only `INDIVIDUAL` users can request document signatures.
* The user must be KYC-verified before requesting signatures.
* Users who have already signed documents cannot sign again.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `BAD_REQUEST` | "Country not supported" |
| 400 | `BAD_REQUEST` | "User must be of type INDIVIDUAL to request document signatures" |
| 400 | `BAD_REQUEST` | "User already has signed documents" |
| 400 | `USER_NOT_VERIFIED_ERROR` | "User identity verification not verified" |
| 400 | `BAD_REQUEST` | "User authentication method is not email" |
| 400 | `BAD_REQUEST` | "User identity verification missing name" |
| 404 | `NOT_FOUND_ERROR` | "Failed to find user identity verification" |
| 404 | `NOT_FOUND_ERROR` | "No active templates found" |
| 404 | `NOT_FOUND_ERROR` | "Requested User was not found" |
| 500 | `INTERNAL_SERVER_ERROR` | "Document signature provider failure" |
# Create Wallet
Source: https://docs.capa.fi/reference/walletaddresspartnerv2controller_createuserwallet
reference/openapi/WalletAddressPartnerV2Controller_createUserWallet.json POST /api/partner/v2/users/{userId}/wallets
Creates a new wallet for a user. The wallet address must be valid for the specified virtual machine type (`EVM` or `SVM`).
***
## Important Notes
* Wallet address validation is performed based on the `vmType`. An EVM address cannot be used with `SVM`, and vice versa.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" |
| 400 | `UserOnRampWalletDuplicateError` | "A wallet with this address and VM type already exists." |
# Delete Wallet
Source: https://docs.capa.fi/reference/walletaddresspartnerv2controller_deleteuserwallet
reference/openapi/WalletAddressPartnerV2Controller_deleteUserWallet.json DELETE /api/partner/v2/users/{userId}/wallets/{walletId}
Soft deletes (disables) a wallet for a user. The wallet will no longer be available for transactions.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" (invalid UUID) |
# List Wallets
Source: https://docs.capa.fi/reference/walletaddresspartnerv2controller_listuserwallets
reference/openapi/WalletAddressPartnerV2Controller_listUserWallets.json GET /api/partner/v2/users/{userId}/wallets
Retrieves all enabled wallets for a user. Supports pagination and filtering by virtual machine type (`EVM`, `SVM`) or wallet ID.
***
## Error Codes
### Common Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "API Key is missing" |
| 401 | `UNAUTHORIZED` | "Invalid API Key format" |
| 401 | `UNAUTHORIZED` | "Invalid API Key" |
| 403 | `INVALID_PARTNER_FLOW` | "The partner has an invalid flow." |
### User Ownership Errors
| HTTP Status | Code | Message |
| - | - | - |
| 401 | `UNAUTHORIZED` | "Partner information is required for this operation" |
| 401 | `UNAUTHORIZED` | "User is not associated with the partner" |
### Endpoint-Specific Errors
| HTTP Status | Code | Message |
| - | - | - |
| 400 | `INVALID_USER_INPUT_ERROR` | "Invalid User Input" (invalid UUID) |