# Funding via Bank Transfer Source: https://docs.reap.global/accounts/bank-transfers How to top up the master collateral account by wire. Projects enabled for bank transfers can fund the master collateral account with a wire instead of an on-chain deposit. The money reaches Reap through the banking system, and the balance is credited once the bank has settled it. Applies to [Program-Funded](/program-configuration/funding-models#program-funded) projects only. Talk to Reap to have it enabled on yours. ## Getting the bank details Call [`GET /fiat-deposits/bank-details`](/api-reference/fiat-deposits/get-bank-details). The response carries the beneficiary name and address, the account number, the bank name and address, the SWIFT code, and the domestic bank and branch codes. Always quote the returned `reference` on the transfer. Unless your business holds a dedicated account, the one you are given is shared with other Reap customers, and the reference is the only thing that identifies the transfer as yours. A transfer without it may be delayed or returned. The reference belongs to your business rather than to a single transfer, so it is the same every time. Send the transfer in the billing currency of your card program. That is the currency the master account is credited in. Read the bank details fresh before each transfer rather than storing them. A bank can close an account, and a transfer sent to a closed account has to be recalled. `FIAT_DEPOSITS_NOT_ENABLED` means the capability is off for the project; talk to Reap to have it turned on. ## What happens after you send The bank settles the transfer, and Reap credits the master account balance. You then receive a [FIAT\_DEPOSIT\_CREATED](/api-reference/fiat-deposit-created) webhook, and the [balance endpoint](/api-reference/accounts/get-account-balance) reflects the new amount. A bank transfer carries no deposit fee. Timing depends on the rail and the sending bank. A domestic transfer usually settles the same day. An international one can take two or three business days. Unlike a crypto deposit, a bank transfer becomes visible to you only once it is settled and credited. There is no earlier status to poll for, so a transfer you have sent but not yet seen a webhook for is still with the banks. ## Reading a deposit Look one up with [`GET /fiat-deposits/:id`](/api-reference/fiat-deposits/get-fiat-deposit), or list them alongside your other activity through the [activities endpoint](/api-reference/activities/list-activities) with `type=FIAT_DEPOSIT`. Each deposit reports the transfer twice. `amount` and `currency` are what reached your balance. `originalAmount` and `originalCurrency` are what the bank recorded receiving. `transactionId` is the bank's own identifier for the transfer. Use it to reconcile a deposit against a bank statement. ## Testing in sandbox Bank transfers cannot be sent end to end in sandbox, so use [`POST /simulation/fiat-deposits`](/api-reference/simulation/simulate-a-fiat-deposit) to create one. It returns the deposit it recorded, credits the balance, and fires the same webhook a real transfer would. Sandbox serves placeholder bank details. The account is not real and no wire can reach it, so the endpoint is there to build your integration against, not to send money to. Only the details returned in production identify a live account. Pass the `amount` to credit and the `currency` you are sending. A currency your project does not accept is rejected with `SIMULATION_UNSUPPORTED_CURRENCY`, listing the ones it does. A sender name is generated unless you pass one, and the reference is left off unless you pass it. # Funding via Deposits Source: https://docs.reap.global/accounts/deposits How to fund an account with on-chain token transfers. To fund an account, send a supported token to the account's deposit address for the relevant chain. Send only the tokens listed under [Supported Assets](/accounts/supported-assets). Anything else stays in the account without counting toward the spending balance, and [withdrawals](/accounts/withdrawals) cannot move it back out. In [Program-Funded](/program-configuration/funding-models#program-funded) projects, user accounts do not have deposit addresses. Deposits go to the project's [master collateral account](#master-collateral-funding), which has its own addresses and follows the same flow described below. User-level spending power is set by [posting](/virtual-assets/postings) against each account, not by depositing. ## Deposit flow When you send a supported token to a deposit address: 1. The system detects the on-chain transfer and creates a deposit in `PENDING` status. 2. Once the transaction is finalized on-chain, the deposit moves to `CONFIRMED`. 3. The deposit is validated. 4. If validation passes, the deposit is `APPROVED` and the account balance is updated. If validation fails, the deposit is `REJECTED`. You receive a [CRYPTO\_DEPOSIT\_CREATED](/api-reference/crypto-deposit-created) webhook when the deposit is first detected, and a [CRYPTO\_DEPOSIT\_STATUS\_UPDATED](/api-reference/crypto-deposit-status-updated) webhook at each subsequent status change. `CONFIRMED` means only that the transaction is final on-chain - it does **not** mean the funds have been credited to the account. Crediting happens at `APPROVED`, after validation passes. Do not treat `CONFIRMED` as available balance; always rely on the [balance endpoint](/api-reference/accounts/get-account-balance) or wait for `APPROVED`. ### Minimum deposit amount Deposits below **USD 0.50** are rejected with a `REJECTED` status. This threshold exists to prevent dust deposits. We recommend filtering out rejected deposits before displaying deposit history to your users, as low-value rejections add noise to the UI. ## Master collateral funding In [Program-Funded](/program-configuration/funding-models#program-funded) projects, the master collateral account is provisioned at project setup. It is the only crypto-holding account in the project, and every authorized card transaction across the program is settled against it. Keep it funded above expected outstanding spend. To find the master account and its deposit addresses, call [`GET /accounts?ownerType=PROJECT`](/api-reference/accounts/list-accounts). The response includes deposit addresses per enabled chain, the same as for user accounts in User-Funded projects. The deposit flow above applies unchanged: send a supported stablecoin, wait for the [CRYPTO\_DEPOSIT\_STATUS\_UPDATED](/api-reference/crypto-deposit-status-updated) webhook, and the balance is available once approved. A master deposit is `APPROVED` once the funds are credited and available to back card spend. That lands shortly after on-chain finality, so expect a short gap between the `CONFIRMED` and `APPROVED` webhooks. The master account can also be funded by wire. See [Funding via Bank Transfer](/accounts/bank-transfers). ### Deposit fee Master deposits can carry a deposit fee, set per project in basis points and agreed with Reap. The fee is charged on the deposited amount, and the master account balance is credited net of it. Each deposit reports what it was charged in `feeAmount`, in the same token as `amount`. A project with no deposit fee reports `0`. Deposits into user accounts are never charged a deposit fee and report `null`. The fee Reap has charged on this account's deposits appears under `liabilities` on the [balance endpoint](/api-reference/accounts/get-account-balance), so the available balance already accounts for it. ### Per-asset minimum for master deposits Master deposits enforce a higher floor than user deposits: | Asset | Minimum per deposit | | - | - | | `USDC` | 2 USD | Deposits below the floor are `REJECTED`. The floor is per deposit, not per balance, so a series of smaller deposits is rejected even if their total is above the threshold. ## Checking deposit status Look up a deposit directly using the [deposit endpoint](/api-reference/crypto-deposits/get-crypto-deposit). Once a deposit is approved, the [balance endpoint](/api-reference/accounts/get-account-balance) will reflect the updated balance. ## Testing in sandbox Deposit testing requires real on-chain transactions. There is no simulation shortcut for the deposit flow. You send actual tokens on testnet, and the system processes them end to end. To get sandbox tokens funded to your test wallet, reach out to your Reap contact. See [Supported Assets](/accounts/supported-assets) for the token contract addresses on the supported chains. ### Scenario 1: Successful deposit 1. [Create an account](/api-reference/accounts/create-account) and retrieve its deposit address for all supported chains. 2. Send RUSDC from your funded test wallet to that address. 3. Confirm you receive a [CRYPTO\_DEPOSIT\_CREATED](/api-reference/crypto-deposit-created) webhook. 4. Wait for [CRYPTO\_DEPOSIT\_STATUS\_UPDATED](/api-reference/crypto-deposit-status-updated) webhooks as the deposit progresses. The first will show `status: CONFIRMED` (the transaction has finalized on-chain). The second will show `status: APPROVED` (it has cleared validation and the balance has updated). 5. Check the [balance endpoint](/api-reference/accounts/get-account-balance). It should reflect the deposited amount. ### Scenario 2: Unsupported token 1. Send native ETH or SOL to the same deposit address. 2. Confirm you receive a [CRYPTO\_DEPOSIT\_CREATED](/api-reference/crypto-deposit-created) webhook confirming the transfer was detected. 3. Confirm a [CRYPTO\_DEPOSIT\_STATUS\_UPDATED](/api-reference/crypto-deposit-status-updated) webhook with `status: REJECTED`. Unsupported tokens are rejected immediately on detection, without going through on-chain finalization. 4. Check the [assets endpoint](/api-reference/accounts/get-account-assets) to confirm the token appears there, and check the [balance endpoint](/api-reference/accounts/get-account-balance) to confirm it is unchanged. # Overview Source: https://docs.reap.global/accounts/overview How accounts work and how their balance is computed. An account is the unit of spending in Reap. Every card is issued against an account, every authorization is evaluated against an account's balance, and every settlement clears debt on an account. ## Ownership Each account belongs to a single owner. You choose the owner type when you [create the account](/api-reference/accounts/create-account). Your project's [program mode](/program-configuration/modes) constrains which types are allowed. * **USER ownership.** Each account belongs to a verified individual user with their own isolated balance. The default, and the only option for consumer programs. Also available in corporate programs for per-user spending. * **COMPANY ownership.** Each account belongs to a verified company with a single pooled balance shared across all cardholders under that company. Only available in corporate programs. Individual cardholders always require an approved KYC application before a card can be issued to them, even when the balance lives at the company level. ## The signer key In [User-Funded](/program-configuration/funding-models#user-funded) projects, [creating an account](/api-reference/accounts/create-account) requires a `signers` entry for each chain family the project has enabled. You prove ownership of each key by signing a message from [Generate signer message](/api-reference/accounts/generate-signer-message). That key is what authorizes money leaving the account later. Reap holds one key to the account's wallets and the signer you register is the other, so [withdrawals](/accounts/withdrawals) need a signature from both. Nothing about deposits or card spend depends on it. You decide whose key it is. A key held in your backend lets you withdraw on the user's behalf, while a key in the user's own wallet means no withdrawal can happen without them. [Who holds the signing key](/accounts/withdrawals#who-holds-the-signing-key) covers the tradeoff. The curve differs per chain family, so one key cannot cover both. An account on EVM chains and Solana needs two, and Reap cannot replace a key the owner loses. ## Account status An account is `ACTIVE` or `RESTRICTED`. Reap restricts an account when its owner's compliance state requires it, for example a rejected application or a case under compliance review. The [`ACCOUNT_STATUS_UPDATED`](/api-reference/account-status-updated) webhook fires on every change, and Reap lifts the restriction once the underlying issue is resolved. A restricted account declines every card authorization. Nothing else about it changes. The cards on it keep their own statuses, and you can still freeze a card, set a PIN, or display card details. A card can therefore report `status: ACTIVE` while its account is restricted, and declines carry the `ACCOUNT_NOT_ACTIVE` [decline reason](/transactions/decline-reasons). If you show cardholders whether a card can be used right now, read the account status alongside the [card status](/cards/overview#card-statuses). ## How balance is computed Every account has a balance made up of three numbers: ``` availableBalance = assets - liabilities ``` * **Assets** is what backs spending. The shape depends on your [funding model](/program-configuration/funding-models). In User-Funded projects it is stablecoin deposits held in the account's wallets. In Program-Funded projects it is the Virtual Asset balances you allocate (Managed mode); under [External authorization](/transactions/external-authorization/overview) no per-user asset balance is held in Reap. * **Liabilities** is outstanding card spend: authorizations on hold and cleared amounts that have not yet been settled. On a master collateral account it also includes any [deposit fees](/accounts/deposits#deposit-fee) charged on deposits into that account. * **Available balance** is what the cardholder can actually use at the moment of a card swipe. Call [Get account balance](/api-reference/accounts/get-account-balance) at any time to read these values. `availableBalance` is the number you surface in your product when showing a user how much they have loaded on their card. ## Typical funding flow 1. [Create an account](/api-reference/accounts/create-account) with the owner type, owner ID, and a [signer key](#the-signer-key) per enabled chain family. You get back a deposit address for each enabled chain. 2. Share the deposit addresses with your user. They send supported stablecoins. See [Funding via Deposits](/accounts/deposits). 3. Track deposits via the [CRYPTO\_DEPOSIT\_CREATED](/api-reference/crypto-deposit-created) and [CRYPTO\_DEPOSIT\_STATUS\_UPDATED](/api-reference/crypto-deposit-status-updated) webhooks. 4. Once a deposit is approved, the balance updates automatically. Check it via [Get account balance](/api-reference/accounts/get-account-balance). 5. To move funds back out to an external address, see [Withdrawing Funds](/accounts/withdrawals). 1. Your project has a project-level master collateral account, provisioned at setup. Fund it with stablecoin deposits. See [Funding via Deposits](/accounts/deposits). 2. [Create an account](/api-reference/accounts/create-account) for each verified user or company. Accounts in Program-Funded do not have their own deposit addresses. 3. [Post deposits, withdrawals, and settlements](/virtual-assets/postings) against each account to keep its Virtual Asset balances in sync with what the user has on your side. 4. Call [Get account balance](/api-reference/accounts/get-account-balance) at any time. Balance updates as you allocate and as the user spends. ## Assets breakdown The [assets endpoint](/api-reference/accounts/get-account-assets) shows what is held in the account, broken down per chain and per token (or per Virtual Asset symbol in Program-Funded projects). Only approved balances are included. Deposits that are still being confirmed on chain do not count toward `assets` or `availableBalance`. They surface in the deposit and activity feeds until approval, at which point both values update. ## Activity The [activity feed](/api-reference/activities/list-activities) gives a chronological record of all fund movements on an account: deposits, withdrawals, card transactions, virtual asset postings, and more. Filter by account or activity type to scope it to a specific user, or use it to power a transaction history view in your product. # Supported Assets Source: https://docs.reap.global/accounts/supported-assets Which tokens and chains are accepted for deposits. Only supported tokens are accepted for deposits. Sending any other token, including native chain tokens like ETH or SOL, will not count toward the spending balance. The tables below cover every asset the platform supports. Your project accepts a subset of them. [`GET /accounts/:id`](/api-reference/accounts/get-account) names the assets each of your deposit addresses takes, so read the list from there rather than hardcoding one. Which chains apply depends on your [funding model](/program-configuration/funding-models). Each table below marks the model it serves. | Funding model | Where deposits land | Chains | | - | - | - | | [User-Funded](/program-configuration/funding-models#user-funded) | Each user's own deposit addresses | Base, Polygon, Solana | | [Program-Funded](/program-configuration/funding-models#program-funded) | Your master collateral account | Ethereum, Polygon, Solana | Ethereum serves master collateral deposits only. Base serves user deposits only. A token address is only meaningful together with its chain. The same address can be a different token on another chain, which is the case for the sandbox tokens below. Always match on chain and address together, and never route by address alone. Production uses standard USDC issued by Circle and USDT issued by Tether. **Base** (User-Funded) | Token | Symbol | Standard | Contract Address | | - | - | - | - | | USD Coin | USDC | ERC-20 | `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913` | **Ethereum** (Program-Funded) | Token | Symbol | Standard | Contract Address | | - | - | - | - | | USD Coin | USDC | ERC-20 | `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | | Tether USD | USDT | ERC-20 | `0xdac17f958d2ee523a2206206994597c13d831ec7` | **Polygon** (both models) | Token | Symbol | Standard | Contract Address | | - | - | - | - | | USD Coin | USDC | ERC-20 | `0x3c499c542cef5e3811e1192ce70d8cc03d5c3359` | | Tether USD | USDT | ERC-20 | `0xc2132d05d31c914a87c6611c10748aeb04b58e8f` | **Solana** (both models) | Token | Symbol | Standard | Mint Address | | - | - | - | - | | USD Coin | USDC | Token | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | | Tether USD | USDT | Token | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` | The sandbox uses Reap-issued test tokens on public testnets. RUSDC and RUSDT are functionally equivalent to USDC and USDT but exist solely for integration testing. Contact the Reap team to receive test tokens for your wallet. **Base Sepolia** (User-Funded) | Token | Symbol | Standard | Contract Address | | - | - | - | - | | Reap USD Coin | RUSDC | ERC-20 | `0xeB9Cb47c97ecff4875a1f29A8345818BC0eDc30E` | **Ethereum Sepolia** (Program-Funded) | Token | Symbol | Standard | Contract Address | | - | - | - | - | | Reap USD Coin | RUSDC | ERC-20 | `0x8868B2867e744Be555d3456238330467343652e1` | | Reap Tether USD | RUSDT | ERC-20 | `0xeB9Cb47c97ecff4875a1f29A8345818BC0eDc30E` | `0xeB9Cb47c97ecff4875a1f29A8345818BC0eDc30E` is RUSDC on Base Sepolia and RUSDT on Ethereum Sepolia. The two testnet deployments reuse each other's addresses across the two symbols. This is intentional and not a documentation error, so treat chain and address as one key when you resolve a token. **Polygon Amoy** (both models) | Token | Symbol | Standard | Contract Address | | - | - | - | - | | Reap USD Coin | RUSDC | ERC-20 | `0x00D4e14024d8AbC6D6D35708b5bEC65B2f28e6E3` | | Reap Tether USD | RUSDT | ERC-20 | `0x0aAACf028519E30A88683222B00f4c7A8B5913CB` | **Solana Devnet** (both models) | Token | Symbol | Standard | Mint Address | | - | - | - | - | | Reap USD Coin | RUSDC | Token-2022 | `BAkGvYZjgR3u2zcphn2LLtXaWq7aDRBH27ipbV7uDZeU` | | Reap Tether USD | RUSDT | Token-2022 | `B2DPuMqUAXvzbsBXsWkVyLH1exXqp8aRojRL3b7zVXrp` | We are actively expanding our supported chains and tokens. If a chain or asset you need is not listed here, reach out to your Reap contact. # Withdrawing Funds Source: https://docs.reap.global/accounts/withdrawals How to move tokens out of an account wallet to an external address. A withdrawal sends tokens from an account's wallet to any address you name. Reap covers the network fee and charges a flat fee per chain, so you do not have to hold gas on any chain or manage a signing pipeline of your own. Withdrawals apply to [User-Funded](/program-configuration/funding-models#user-funded) projects, where each account holds its funds in its own wallets. In [Program-Funded](/program-configuration/funding-models#program-funded) projects the user's funds sit on your side, so you record a withdrawal by [posting](/virtual-assets/postings) against the account instead. ## How a withdrawal works Every withdrawal needs a signature from the wallet owner. Reap holds one key and you or your user holds the other. Nothing moves on chain without both, so neither side can withdraw alone. The key is the [signer you registered](/accounts/overview#the-signer-key) when you created the account. Each chain family has its own curve, so an account on both EVM chains and Solana carries two signer keys and each one signs only its own withdrawals. Call the [assets endpoint](/api-reference/accounts/get-account-assets). Each chain reports a `withdrawable` cap and the `withdrawalFee` for that chain. [Initiate crypto withdrawal](/api-reference/crypto-withdrawals/initiate-crypto-withdrawal) with the account, chain, asset, gross amount, and destination address. Reap reserves the funds and returns the withdrawal in `PENDING_SIGNATURE`. An [idempotency key](/api-reference/idempotency) is required. [Mint signing payload](/api-reference/crypto-withdrawals/mint-signing-payload) returns `payloadToSign`, the `executionId` that identifies it, and `validUntil`. The wallet owner signs `payloadToSign` with their registered key. [Submit withdrawal signature](/api-reference/crypto-withdrawals/submit-withdrawal-signature) with the signature and the `executionId` you were given. The withdrawal moves to `PROCESSING` and Reap broadcasts the transfer. `transactionId` appears once the transfer reaches the network. The withdrawal reaches `COMPLETED` when the transfer is irreversible on chain. ```mermaid theme={null} sequenceDiagram participant You as Your backend participant Reap participant Owner as Wallet owner You->>Reap: Initiate withdrawal Reap-->>You: PENDING_SIGNATURE, funds reserved You->>Reap: Fetch signing payload Reap-->>You: payloadToSign, validUntil You->>Owner: Relay payload Owner-->>You: Signature You->>Reap: Submit signature Reap-->>You: PROCESSING Reap-->>You: COMPLETED ``` ## Signing You do not need to parse `payloadToSign`. Forward it to the wallet exactly as it arrives and sign that string. Do not re-encode, hash, or trim it. **EVM chains** ```typescript theme={null} const signature = await walletClient.signMessage({ account, message: payloadToSign }); ``` **Solana** ```typescript theme={null} const signature = bs58.encode( nacl.sign.detached(Buffer.from(payloadToSign), keypair.secretKey) ); ``` ### Who holds the signing key You choose whether your backend signs or the end user does. The API, the payload, and the code above are the same either way, so this is a product decision rather than an integration one. Reap holds the other key in both cases. You register a key you control and sign every withdrawal server-side. Your own rules decide what leaves an account. That is what you want when withdrawals run on a schedule, clear an internal approval step, or have to work while the user is offline. The user never sees a prompt, so a withdrawal finishes in one call from your product. The tradeoff is concentration. One key authorizes every account, so whoever holds it can withdraw from all of them. Keep it in a KMS or HSM and treat access to it as access to client funds. The key lives in the user's wallet and you relay `payloadToSign` to their device. Nothing leaves an account without the person. A breach of your systems cannot move funds on its own, and the user holds a real veto over each withdrawal. This is the closer fit when you present the wallet as the user's own. The tradeoff is that the user has to be there. Scheduled and automated withdrawals stop being possible. Every prompt costs you some drop-off, and you own the recovery story for a user who loses their key. ### Payloads are short-lived A payload commits chain resources that other traffic can take, so `validUntil` is a few minutes out. Fetch a payload immediately before you sign it rather than at the start of a session. Each fetch retires the previous payload. Only the newest one can be submitted, which is why you pass back the `executionId` you were handed: a signature over a retired payload is rejected and you fetch a fresh one. Fetching a payload never affects the withdrawal itself, so you can do it as often as you need inside the signature window. ### Verify before you sign `payloadToSign` is readable JSON, not a hash. Your backend can decode it and check the destination, asset, and amount match the withdrawal you requested before relaying it to a user. On EVM the transfer sits in the typed data under `parameters.payload`; on Solana it is a hex-serialized transaction under `parameters.unsignedTransaction`, which decodes with `@solana/web3.js`. The signature covers those exact bytes. Change one byte and it no longer verifies, so what you inspect is what executes. ## Fees and amounts The amount you request is the gross. The fee comes out of it rather than on top, so `netAmount` is what the destination receives and the gross is what leaves the account. | Chain | Fee | Minimum withdrawal | | - | - | - | | Base | 0.30 USD | 5 USD | | Polygon | 0.30 USD | 5 USD | | Solana | 0.75 USD | 5 USD | The fee is flat because the cost behind it is flat. Sending a transfer costs the same whatever it carries, so charging a percentage would bill a large withdrawal for work nobody did. Solana costs more despite its cheap network fees. A first transfer to a destination has to open a token account for it, and that account needs a rent deposit in SOL that Reap funds and does not get back. Each withdrawal reports the fee it was charged in `fee.amount`, fixed when you initiated it. The [assets endpoint](/api-reference/accounts/get-account-assets) reports the current fee per chain in the asset you hold, so you can show a user what a withdrawal will cost before they commit to one. ### The withdrawable cap `withdrawable` on the assets endpoint is the largest gross amount you can request on that chain. It nets out outstanding card spend, including authorizations still on hold. A withdrawal therefore cannot take the [available balance](/accounts/overview#how-balance-is-computed) below zero. It reads zero while the account is not `ACTIVE`. Two things to keep in mind: * It is a per-chain figure that assumes nothing else is withdrawn, so the caps across chains cannot all be taken at once. * It is advisory and recomputed on every request. The authoritative check happens when you initiate, and a card authorization in between can move it. ## Statuses | Status | What it means | | - | - | | `PENDING_SIGNATURE` | Funds are reserved and Reap is waiting for the owner signature | | `PROCESSING` | Signed and executing on chain | | `COMPLETED` | The transfer is irreversible on chain | | `EXPIRED` | The signature window closed and Reap released the funds | | `CANCELLED` | You cancelled it and Reap released the funds | | `FAILED` | The withdrawal did not go through, see `failureReason` | You receive a [CRYPTO\_WITHDRAWAL\_CREATED](/api-reference/crypto-withdrawal-created) webhook when the withdrawal is created, and a [CRYPTO\_WITHDRAWAL\_STATUS\_UPDATED](/api-reference/crypto-withdrawal-status-updated) webhook at every status change after that. You can also read a withdrawal at any time with [Get crypto withdrawal](/api-reference/crypto-withdrawals/get-crypto-withdrawal), and withdrawals appear in the [activity feed](/api-reference/activities/list-activities) alongside deposits and card transactions. Reserved funds stop backing card spend for as long as the withdrawal is open. They return to the balance on `EXPIRED`, `CANCELLED`, and `FAILED`. `PROCESSING` with a `transactionId` means the transfer is on the network, not that it has landed. Treat only `COMPLETED` as final. ### Failures `failureReason` says what went wrong: * `TRANSACTION_FAILED`: the transfer did not go through on chain. The funds never left the account. * `ACCOUNT_RESTRICTED`: Reap restricted the account while the withdrawal was open. See [account status](/accounts/overview#account-status). * `REJECTED_BY_COMPLIANCE`: the destination did not clear screening. Reap pauses withdrawals on the account and reviews it. Further withdrawals are refused until that review finishes. ## The signature window A withdrawal stays signable for **15 minutes** from the moment you initiate it, reported as `expiresAt`. After that it moves to `EXPIRED` and Reap releases the funds. The window is short because an unsigned withdrawal costs the account something for nothing. The reserved funds no longer back card spend, and one open withdrawal per wallet and chain blocks the next. Signing normally follows initiation within the same session. If a user walks away, let the withdrawal expire or [cancel](/api-reference/crypto-withdrawals/cancel-crypto-withdrawal) it. Initiating another one costs nothing. Cancelling works only while the withdrawal is awaiting a signature. Once it is signed and broadcasting there is nothing to cancel. Withdrawals are irreversible. Reap cannot recover funds sent to a wrong address, and the destination must be a valid address on the chain you named. Check the address with your user before you initiate. ## Testing in sandbox The flow works end to end on testnets with the test tokens listed under [Supported Assets](/accounts/supported-assets). Fund an account, then withdraw to any address you control on the same chain and watch for the status webhooks. Reap pays the testnet gas, the same as in production. # FAQ Source: https://docs.reap.global/agentic-payments/faq Short answers to the questions that come up about the agentic payments roadmap Roadmap priorities can change, so contact us before committing to a build that depends on anything below. Yes. If your agent uses its own catalog or search, use the custom checkout URL flow and contact us to enable it with the merchant integrations you need. It is planned shortly after the current webhook work and will be enabled per account, so contact us once it is live. Merchant coverage changes as integrations launch, so contact us with the merchants you need and we will confirm what is live or in progress. Yes, but both require production approval from Visa and our internal teams, so start the review early. Not yet. MCP client builds remain in sandbox for now, so share your timeline and we will tell you how it aligns with ours. ## Next steps Store a card and confirm it is ready to charge. Run a purchase end to end. # How It Works Source: https://docs.reap.global/agentic-payments/how-it-works The actors and resources behind a single agentic purchase An agentic purchase runs in two phases. Setup happens once per card. The purchase flow then repeats for every order against that stored card. ## Who does what * The user is the cardholder who stores a card and approves each charge on a hosted page * The client is you, and your agent chooses what to buy and drives the API calls * Reap stores the card and prices the order with the merchant, then completes the checkout and reports the outcome * The merchant is the seller that supplies the catalog and the current price, including the shipping options and the tax ## The two phases ```mermaid theme={null} flowchart LR subgraph setup ["Setup, once per card"] A[Create enrollment] --> B[User enters the card] end subgraph purchase ["Purchase, once per order"] C[Search products] --> D[Price the order] --> E[Create checkout] --> F[User approves] --> G[Order confirmed] end B -- enrollment ready to charge --> C G -. next order .-> C ``` Setup runs once and the enrollment stays reusable. The purchase phase then repeats for every later order. ## One purchase in detail ```mermaid theme={null} sequenceDiagram participant User participant Agent as Your agent participant Reap participant Merchant User->>Agent: Ask for a purchase Agent->>Reap: Search products and pick one Reap-->>Agent: Products with prices Agent->>Reap: Create a quote and select shipping Reap->>Merchant: Open the merchant checkout Merchant-->>Reap: Shipping options and totals Reap-->>Agent: Quote with the final amount Agent->>Reap: Create a checkout for the quote and the enrollment Reap-->>Agent: Approval URL Agent->>User: Send the user to the approval page User->>Reap: Approve the charge Reap->>Merchant: Place the order Reap-->>User: Return the user to your return URL Agent->>Reap: Read the checkout Reap-->>Agent: Final amount ``` The search line and the quote line each stand for more than one call. [One-Time Purchases](/agentic-payments/one-time-purchases) shows every request and response in order. ## Enrollment An enrollment stores one card and returns an ID. Create one with [`POST /agentic/enrollments`](/api-reference/agentic/create-enrollment) and pass that ID to checkout whenever you charge the card. You can charge only an enrollment with status `ACTIVE`. Three sources store a card, and every source takes an owner and a return URL. The user then finishes the enrollment on a page hosted by Reap. * A Reap card uses `source` set to `REAP_CARD` with the card ID * A BIN sponsor card uses `source` set to `BIN_SPONSOR` with the public token ID * An external card uses `source` set to `EXTERNAL`, and the user enters the card on the hosted page Every enrollment belongs to an owner. Use `REAP_USER` for a Reap cardholder and `CLIENT_REFERENCE` for a customer record in your own system. Every call to [`GET /agentic/enrollments`](/api-reference/agentic/list-enrollments) covers a single owner, so keep the owner reference you sent. [Setup](/agentic-payments/setup) walks through each source with a request and a response. ## Mandate A mandate records the terms the user approved for one enrollment. It holds the approved amount, the charge frequency, the maximum number of charges, and the merchant scope. Read it with [`GET /agentic/mandates/:id`](/api-reference/agentic/get-mandate). The approved amount acts as a ceiling. The final quote total has to stay at or below that amount for the charge to run under the same approval. You can pause, resume, and cancel a mandate. [Lifecycle and Statuses](/agentic-payments/lifecycle) covers each operation. ## Product search Product search reads merchant catalogs and turns what the user chose into the variant a quote needs. * [`POST /agentic/products/search`](/api-reference/agentic/search-products) returns matching products with a price range and one preview variant * [`POST /agentic/products/details`](/api-reference/agentic/get-product-details) expands a product into its description, media, and option groups * [`POST /agentic/products/variant`](/api-reference/agentic/resolve-variant) turns the selected options into one purchasable variant Search returns a price range rather than one price, because shipping and tax arrive with the quote. [One-Time Purchases](/agentic-payments/one-time-purchases) shows each call with a request and a response. ## Quote [`POST /agentic/quotes`](/api-reference/agentic/create-quote) opens the merchant checkout for the selected items and returns pricing from the merchant. It gives you the available shipping options. It also returns an itemized total with subtotal, shipping, tax, discounts, and the final amount. A quote accepts a variant rather than a product, so resolve the chosen options to a variant first. Every quote carries an `expiresAt` value. Open the checkout before that time, and create a new quote once it passes. ## Checkout [`POST /agentic/checkouts`](/api-reference/agentic/create-checkout) charges an `ACTIVE` enrollment for an unexpired quote. The response tells you what to do next. * When `nextAction` is present, send the user to `nextAction.url` to approve the charge * When the user returns to your return URL, read the checkout with [`GET /agentic/checkouts/:id`](/api-reference/agentic/get-checkout) to get `orderId` and `finalAmount` A checkout cannot open against an enrollment outside `ACTIVE`. Read the enrollment with [`GET /agentic/enrollments/:id`](/api-reference/agentic/get-enrollment) before you charge it. Create a new enrollment when the stored card is revoked or expired. ## Idempotency Send an [`Idempotency-Key`](/api-reference/idempotency) header on every call that creates something. [`POST /agentic/enrollments`](/api-reference/agentic/create-enrollment), [`POST /agentic/quotes`](/api-reference/agentic/create-quote), and [`POST /agentic/checkouts`](/api-reference/agentic/create-checkout) all require it, and a call that omits the header is rejected. A retry with the same key replays the first response instead of storing a second card, creating a second quote, or running a second charge. Send `Reap-Version` on every request. See [versioning](/api-reference/versioning) for how versions change. ## Next steps Store a card and confirm it is ready to charge. Run the purchase phase end to end. # Lifecycle and Statuses Source: https://docs.reap.global/agentic-payments/lifecycle What each status means and which operations change it Every agentic resource reports a `status`. This page maps each value to the action it calls for, and covers the operations that move a resource from one value to the next. ## Enrollment ```mermaid theme={null} stateDiagram-v2 [*] --> REQUIRES_ACTION: create enrollment for a new card [*] --> ACTIVE: create enrollment for an already issued card REQUIRES_ACTION --> ACTIVE: user enters the card ACTIVE --> REVOKED: revoke enrollment REVOKED --> [*] ``` | Status | What it means | What to do | | - | - | - | | `REQUIRES_ACTION` | A hosted step is outstanding | Send the user to `nextAction.url` before `expiresAt`, then read the enrollment again | | `ACTIVE` | The card is stored and ready | Use the enrollment ID at checkout | | `REVOKED` | The stored card no longer accepts charges | Create a new enrollment for a replacement card | | `FAILED` | Reap could not store the card | Create a new enrollment and send the user through the hosted step again | | `EXPIRED` | The user did not finish the hosted step before `expiresAt` | Create a new enrollment and send the user to the new `nextAction.url` | An enrollment created from a Reap card or a BIN sponsor card can reach `ACTIVE` without a hosted step. An external card always passes through the hosted card entry page. ### Revoke an enrollment Revoke a stored card with [`POST /agentic/enrollments/:id/revoke`](/api-reference/agentic/revoke-enrollment) when the user removes it or when the card should stop working. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/enrollments/:id/revoke \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` ```json Response theme={null} { "id": "", "status": "REVOKED", "owner": { "type": "CLIENT_REFERENCE", "id": "", "email": "jsmith@example.com" }, "paymentMethod": { "type": "CARD", "network": "", "last4": "4242", "expiryMonth": 11, "expiryYear": 2029 }, "nextAction": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z" } ``` Revoking is final. You cannot charge or restore a revoked enrollment. You will need to create a new enrollment to use the same card. ## Mandate ```mermaid theme={null} stateDiagram-v2 [*] --> REQUIRES_ACTION: create mandate REQUIRES_ACTION --> ACTIVE: user approves the mandate ACTIVE --> PAUSED: pause PAUSED --> ACTIVE: resume ACTIVE --> CANCELLED: cancel PAUSED --> CANCELLED: cancel ACTIVE --> REVOKED: revoke the enrollment PAUSED --> REVOKED: revoke the enrollment CANCELLED --> [*] REVOKED --> [*] ``` | Status | What it means | What to do | | - | - | - | | `REQUIRES_ACTION` | The mandate is not approved yet | Complete the step in `nextAction`, then read the mandate again | | `ACTIVE` | Charges can run under the approved mandate | Charge inside `amount`, `maxCharges`, and `merchantScope` | | `PAUSED` | Charges are on hold and the mandate is intact | Resume the mandate to continue | | `CANCELLED` | You ended the mandate and the stored card stays usable | Collect a new mandate from the user | | `REVOKED` | The enrollment behind the mandate was revoked | Create a new enrollment, then collect a new mandate | `CANCELLED` and `REVOKED` are both end states, and neither returns to `ACTIVE`. The difference is scope. Canceling ends one mandate and leaves the stored card ready for a new one. Revoking the enrollment stops every mandate that points at that card. ### Pause a mandate [`POST /agentic/mandates/:id/pause`](/api-reference/agentic/pause-mandate) holds charges and keeps the approved mandate. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/mandates/:id/pause \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` ```json Response theme={null} { "id": "", "status": "PAUSED", "enrollmentId": "", "frequency": "WEEKLY", "maxCharges": 12, "merchantScope": "LISTED", "amount": { "amount": 150, "currency": "USD" } } ``` ### Resume a mandate ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/mandates/:id/resume \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` [`POST /agentic/mandates/:id/resume`](/api-reference/agentic/resume-mandate) returns `status` as `ACTIVE` with the same mandate. Resuming needs no further approval from the user, because nothing about the mandate changed. ### Cancel a mandate ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/mandates/:id/cancel \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` [`POST /agentic/mandates/:id/cancel`](/api-reference/agentic/cancel-mandate) returns `status` as `CANCELLED`. The enrollment stays untouched and remains available for a new purchase. Pause and resume suit a temporary hold. Cancel ends the mandate for good. Revoking the enrollment is the wider action, because it stops charges across every mandate that points at the same stored card, and those mandates report `REVOKED` rather than `CANCELLED`. ## Quote A quote has no status field. It has an expiry instead. * Read `expiresAt` on every quote and open the checkout before that time * Create a new quote with [`POST /agentic/quotes`](/api-reference/agentic/create-quote) once the expiry has passed, because merchant prices can move in between * Read the quote at any time with [`GET /agentic/quotes/:id`](/api-reference/agentic/get-quote) to get the current total ## Checkout ```mermaid theme={null} stateDiagram-v2 [*] --> REQUIRES_ACTION: create checkout [*] --> PROCESSING: create checkout under an approved mandate REQUIRES_ACTION --> PROCESSING: user approves the charge REQUIRES_ACTION --> EXPIRED: no approval before expiresAt PROCESSING --> COMPLETED: the merchant places the order PROCESSING --> FAILED: the charge or the order does not go through COMPLETED --> [*] FAILED --> [*] EXPIRED --> [*] ``` | Status | What it means | What to do | | - | - | - | | `REQUIRES_ACTION` | The charge is waiting on user approval | Send the user to `nextAction.url` before `expiresAt` | | `PROCESSING` | The charge is approved and the order is being placed | Read the checkout again until it reports a final status | | `COMPLETED` | The merchant order is placed | Store `orderId` and reconcile against `finalAmount` | | `FAILED` | The charge or the merchant order did not go through | Tell the user the purchase did not happen, then create a new quote and a new checkout | | `EXPIRED` | The user did not approve before `expiresAt` | Create a new quote and a new checkout, because the old quote is stale | `COMPLETED`, `FAILED`, and `EXPIRED` are final. A checkout never leaves one of them, so branch on the status value rather than treating anything other than `COMPLETED` as still in flight. Neither `FAILED` nor `EXPIRED` can be retried in place, and both need a fresh quote and a fresh checkout. [`POST /agentic/checkouts`](/api-reference/agentic/create-checkout) returns a `nextAction` when the charge needs approval. A checkout created under a mandate the user approved earlier skips that step and moves straight into `PROCESSING`. Read the checkout with [`GET /agentic/checkouts/:id`](/api-reference/agentic/get-checkout) after the user lands back on your return URL. Returning to that URL does not by itself mean the order is placed, so read the status. A `nextAction` of `null` on a completed checkout means you have nothing left to do. ## Next steps The full purchase sequence with requests and responses. Short answers to common integration questions. # One-Time Purchases Source: https://docs.reap.global/agentic-payments/one-time-purchases Run a single agentic purchase from product search to a confirmed order This page runs one purchase end to end. You start with a stored card and finish with a merchant order reference and the amount charged. ## Before you start You need an enrollment with status `ACTIVE`. [Setup](/agentic-payments/setup) covers how to create one, and [`GET /agentic/enrollments/:id`](/api-reference/agentic/get-enrollment) reads its status. ```bash Confirm the enrollment theme={null} curl --request GET \ --url https://sandbox.api.reap.global/agentic/enrollments/:id \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` Continue when `status` reads `ACTIVE`. Anything else means you cannot charge the card yet. ## Check the mandate A single charge runs under a mandate with `frequency` set to `ONE_TIME` and `maxCharges` set to 1. Read the mandate with [`GET /agentic/mandates/:id`](/api-reference/agentic/get-mandate) to confirm the terms before you price anything. ```bash Request theme={null} curl --request GET \ --url https://sandbox.api.reap.global/agentic/mandates/:id \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` ```json Response theme={null} { "id": "", "status": "ACTIVE", "enrollmentId": "", "frequency": "ONE_TIME", "maxCharges": 1, "merchantScope": "LISTED", "merchant": { "name": "", "url": "https://example.com", "country": "US" }, "amount": { "amount": 150, "currency": "USD" } } ``` The `amount` is a ceiling rather than a target. Compare it against the final quote total once you select a shipping option. A one-time mandate authorizes a single charge and cannot authorize another once that charge completes. ## Run the purchase Send the words the user gave you to [`POST /agentic/products/search`](/api-reference/agentic/search-products). Filters narrow the result set, and `context` sets the country and currency for pricing. If your application handles discovery, skip the search and variant steps. Construct a merchant checkout URL with the item quantity and any attribution parameters. Then continue at **Create the quote**. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/products/search \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Reap-Version: ' \ --data '{ "query": "Sony WH 1000XM5 headphones", "context": { "country": "US", "currency": "USD" }, "filters": { "price": { "min": "100", "max": "200" }, "availability": "AVAILABLE_ONLY" }, "pagination": { "limit": 20 } }' ``` ```json Response theme={null} { "id": "", "products": [ { "id": "", "merchant": { "name": "" }, "name": "Sony WH 1000XM5 Wireless Headphone", "imageUrl": "https://example.com/image.jpg", "priceRange": { "min": { "amount": 129, "currency": "USD" }, "max": { "amount": 149, "currency": "USD" } }, "available": true, "previewVariant": { "id": "", "name": "", "price": { "amount": 129, "currency": "USD" }, "available": true } } ], "pagination": { "nextCursor": "", "hasNextPage": true, "returnedCount": 20 }, "warnings": [] } ``` Shortlist a product by `id`. Add `merchantPreference` to the request when the user names a merchant or when you want Reap to prefer one. Search results carry a price range and one preview variant. [`POST /agentic/products/details`](/api-reference/agentic/get-product-details) expands a product into its description, media, and option groups. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/products/details \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Reap-Version: ' \ --data '{ "productIds": [""] }' ``` ```json Response theme={null} { "products": [ { "id": "", "name": "Sony WH 1000XM5 Wireless Headphones", "options": [ { "name": "Color", "values": [ { "optionId": "", "label": "Black", "available": true }, { "optionId": "", "label": "Silver", "available": false } ] } ], "defaultVariant": { "id": "", "price": { "amount": 129, "currency": "USD" }, "available": true, "requiresShipping": true } } ], "errors": [] } ``` Carry the `optionId` of each chosen value into the next step. Check `available` on every option value, because an unavailable value cannot resolve to a purchasable variant. Any product ID that fails to resolve comes back in `errors` rather than stopping the call. Skip the next step when the user accepts the `defaultVariant`, which is already a purchasable variant. A quote accepts a variant rather than a product. Turn the selected options into one variant with [`POST /agentic/products/variant`](/api-reference/agentic/resolve-variant). ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/products/variant \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Reap-Version: ' \ --data '{ "productId": "", "optionIds": [""] }' ``` ```json Response theme={null} { "id": "", "name": "", "options": [ { "name": "Color", "value": "Black" } ], "price": { "amount": 129, "currency": "USD" }, "available": true, "requiresShipping": true } ``` A `requiresShipping` value of `true` means the quote needs a shipping address. Stop here when `available` reads `false` and offer the user another option. [`POST /agentic/quotes`](/api-reference/agentic/create-quote) opens the merchant checkout and returns current pricing from the merchant. Send exactly one of `items` and `externalCheckout`. `offerCode` is optional for both quote sources. Use `items` with the variant IDs returned by Reap discovery. Send the shipping address whenever a variant requires shipping. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/quotes \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: ' \ --header 'Reap-Version: ' \ --data '{ "items": [{ "variantId": "var_123", "quantity": 1 }], "email": "avery.tan@reap.hk", "offerCode": "SAVE10" }' ``` Use `externalCheckout` with the checkout URL your application constructed. Reap sends the URL to the merchant without rebuilding it, so attribution parameters remain intact. External checkout quotes require a shipping address. External checkout URLs are available only for merchant domains that Reap has allowlisted for your integration. Contact Reap to request access before you send checkout URLs for a new merchant. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/quotes \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: ' \ --header 'Reap-Version: ' \ --data '{ "externalCheckout": { "merchantDomain": "merchant.example", "checkoutUrl": "https://merchant.example/cart/variant-1:1?attributes[partner_click_id]=example-123" }, "email": "avery.tan@reap.hk", "shippingAddress": { "firstName": "Avery", "lastName": "Tan", "phone": "+85200000000", "addressLine1": "123 Example Street", "city": "Example City", "postalCode": "000000", "country": "HK" }, "offerCode": "SAVE10" }' ``` ```json Response theme={null} { "id": "", "shippingOptions": [ { "id": "", "name": "Standard", "selected": false, "price": { "amount": 5, "currency": "USD" } }, { "id": "", "name": "Express", "selected": true, "price": { "amount": 13, "currency": "USD" } } ], "amountBreakdown": { "itemsSubtotal": { "amount": 129, "currency": "USD" }, "shipping": { "amount": 13, "currency": "USD" }, "tax": { "amount": { "amount": 5, "currency": "USD" }, "includedInPrices": false }, "discounts": [], "additionalCharges": [], "finalAmount": { "amount": 142, "currency": "USD" } }, "expiresAt": "2026-01-01T00:00:00Z" } ``` Keep the quote ID and note `expiresAt`. Prices come from the merchant, so the total can differ from the variant price once shipping and tax land. Reap makes one merchant request for each quote attempt. It does not retry a failed request. Correct `CHECKOUT_URL_INVALID`, `OFFER_CODE_INVALID`, `OFFER_CODE_EXPIRED`, and `QUOTE_UNFULFILLABLE` errors before you create another quote. `CARD_PAYMENT_UNAVAILABLE` means card payment is unavailable for this checkout. Do not retry the same request unchanged; choose another checkout or contact Reap. One option arrives preselected. Send a different one to [`POST /agentic/quotes/:id/shipping-option`](/api-reference/agentic/select-shipping-option), which reprices the quote and returns the updated breakdown. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/quotes/:id/shipping-option \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Reap-Version: ' \ --data '{ "shippingOptionId": "" }' ``` ```json Response theme={null} { "id": "", "shippingOptions": [ { "id": "", "name": "Standard", "selected": true, "price": { "amount": 5, "currency": "USD" } }, { "id": "", "name": "Express", "selected": false, "price": { "amount": 13, "currency": "USD" } } ], "amountBreakdown": { "itemsSubtotal": { "amount": 129, "currency": "USD" }, "shipping": { "amount": 5, "currency": "USD" }, "tax": { "amount": { "amount": 5, "currency": "USD" }, "includedInPrices": false }, "finalAmount": { "amount": 134, "currency": "USD" } }, "expiresAt": "2026-01-01T00:00:00Z" } ``` Show `amountBreakdown.finalAmount` to the user as the amount you will charge. Read the quote again with [`GET /agentic/quotes/:id`](/api-reference/agentic/get-quote) at any point to get the current total without changing anything. [`POST /agentic/checkouts`](/api-reference/agentic/create-checkout) charges the enrollment for the quote. Send the URL the user returns to after approving. In the sandbox, send `X-Simulate-Checkout: COMPLETED` to simulate a completed checkout. This header is rejected in production. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/checkouts \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: ' \ --header 'Reap-Version: ' \ --header 'X-Simulate-Checkout: COMPLETED' \ --data '{ "quoteId": "", "enrollmentId": "", "presentation": { "type": "REDIRECT", "returnUrl": "https://example.com/orders/done" } }' ``` ```json Response theme={null} { "id": "", "status": "REQUIRES_ACTION", "quoteId": "", "enrollmentId": "", "amount": { "amount": 134, "currency": "USD" }, "nextAction": { "type": "REDIRECT", "url": "", "expiresAt": "2026-01-01T00:00:00Z" } } ``` Branch on `nextAction`. When it holds a value, send the user to `nextAction.url` to review and approve the charge. When it is empty, the charge already ran under terms the user approved earlier. Read the checkout with [`GET /agentic/checkouts/:id`](/api-reference/agentic/get-checkout) once the user lands back on your return URL. A completed checkout carries the merchant order reference and the amount charged. ```bash Request theme={null} curl --request GET \ --url https://sandbox.api.reap.global/agentic/checkouts/:id \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` ```json Response theme={null} { "id": "", "status": "COMPLETED", "quoteId": "", "enrollmentId": "", "orderId": "", "finalAmount": { "amount": 134, "currency": "USD" }, "nextAction": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z" } ``` Store `orderId` against your own record of the purchase. It is the reference the user and the merchant both recognize. Reconcile against `finalAmount` rather than the quote total, because it is the amount actually charged. ## Next steps Charge the same card again on a schedule. Every status value and what to do about it. Field level detail for every field, header, and error sits in the [API Reference](/api-reference/overview) tab. This page carries the sequence rather than repeating that detail. # Overview Source: https://docs.reap.global/agentic-payments/overview Let an agent buy on behalf of a user while every charge stays under user approval Agentic Payments lets an AI agent complete a purchase for a user without handling the card number of that user. The user stores a card once. Your agent then searches merchant catalogs and prices an order. It opens a checkout against the stored card, and the user approves the charge on a page hosted by Reap. Reap completes the purchase with the merchant and returns the order reference. ## What Reap handles You do not need to build any of the following. * Card storage, so the card number never reaches your agent or your servers * Merchant catalog search, product detail lookup, and live pricing * The hosted page where the user enters a new card * The hosted page where the user approves each charge * Checkout with the merchant, including the order reference that comes back ## What you build * The agent logic that decides what to buy * The call sequence described in this section ## Core resources Five resources carry the whole flow. [How It Works](/agentic-payments/how-it-works) explains each one. * An enrollment stores a card. [`POST /agentic/enrollments`](/api-reference/agentic/create-enrollment) returns the enrollment ID, which is the payment handle for every later purchase. * A mandate records the charge terms the user approved for one enrollment. Read it with [`GET /agentic/mandates/:id`](/api-reference/agentic/get-mandate). * Product search finds a product in merchant catalogs and resolves it to a purchasable variant. Start with [`POST /agentic/products/search`](/api-reference/agentic/search-products). * A quote prices an order with the merchant and expires after a short window. Create it with [`POST /agentic/quotes`](/api-reference/agentic/create-quote). * A checkout charges the enrollment and completes the order with the merchant. Create it with [`POST /agentic/checkouts`](/api-reference/agentic/create-checkout). ## Use cases The user asks the agent to buy Sony WH-1000XM5 headphones for about USD 130. The agent finds them at audiohub.com and quotes USD 138.42 including shipping and tax. The user opens the approval link, checks the order, and pays with their card. The agent reports order 12345 with Friday delivery. ## Next steps The actors, the resources, and the two phases of a purchase. Store a card and confirm it is ready to charge. Run a single purchase from search to a confirmed order. Charge the same card on a schedule under approved terms. [Lifecycle and Statuses](/agentic-payments/lifecycle) covers every status value and the operations that change it. The [FAQ](/agentic-payments/faq) answers the questions that come up most during an integration. # Recurring Purchases Source: https://docs.reap.global/agentic-payments/recurring-purchases Charge the same stored card on a schedule under terms the user approved once A recurring purchase reuses one enrollment and one mandate across many charges. The user approves the terms once, and each later charge runs inside those terms instead of prompting the user again. ## What the mandate controls Read the mandate with [`GET /agentic/mandates/:id`](/api-reference/agentic/get-mandate) and check four things. | Term | What it does | | - | - | | `amount` | Sets the ceiling for a charge. The final quote total has to stay at or below it. | | `frequency` | Sets the cadence as `WEEKLY`, `MONTHLY`, or `YEARLY`. | | `maxCharges` | Caps how many charges the mandate authorizes in total. | | `merchantScope` | Limits where the mandate can be used. A `LISTED` scope names the merchant in the `merchant` block. | ```json Response theme={null} { "id": "", "status": "ACTIVE", "enrollmentId": "", "frequency": "MONTHLY", "maxCharges": 12, "merchantScope": "LISTED", "merchant": { "name": "", "url": "https://example.com", "country": "US" }, "amount": { "amount": 150, "currency": "USD" } } ``` You can charge only while `status` reads `ACTIVE`. [Lifecycle and Statuses](/agentic-payments/lifecycle) lists every value. ## The charge cycle ```mermaid theme={null} flowchart LR A[Read the mandate] --> B[Price the order again] B --> C{"Total at or below the ceiling?"} C -- Yes --> D[Charge and confirm] C -- No --> E[Collect new terms from the user] D -. next cycle .-> A ``` Each cycle repeats the purchase phase and reuses only the enrollment and the mandate. The ceiling check is the one point where a cycle can stop, so branch on it before you charge. Confirm the status is `ACTIVE` and that charges remain under `maxCharges`. A paused or canceled mandate cannot authorize the cycle. Create a fresh quote for the cycle with [`POST /agentic/quotes`](/api-reference/agentic/create-quote) and select a shipping option. Merchant prices move, so never reuse a quote or an old total across cycles. The call sequence matches [One-Time Purchases](/agentic-payments/one-time-purchases). Check `amountBreakdown.finalAmount` against the mandate `amount` before you charge. Hold the cycle and ask the user for new terms when the total sits above the ceiling. Create the checkout with [`POST /agentic/checkouts`](/api-reference/agentic/create-checkout) against the same enrollment, then read it with [`GET /agentic/checkouts/:id`](/api-reference/agentic/get-checkout) to capture `orderId` and `finalAmount` for that cycle. Branch on `nextAction` in the checkout response, and send the user to the approval page whenever it holds a value. ## Control the schedule A mandate stays under your control between cycles. * [`POST /agentic/mandates/:id/pause`](/api-reference/agentic/pause-mandate) stops charges and keeps the approved terms * [`POST /agentic/mandates/:id/resume`](/api-reference/agentic/resume-mandate) continues a paused mandate under the same terms * [`POST /agentic/mandates/:id/cancel`](/api-reference/agentic/cancel-mandate) ends the mandate Resuming needs no further approval from the user, because the terms have not changed. [Lifecycle and Statuses](/agentic-payments/lifecycle) covers each operation with a request and a response. Revoking an enrollment stops charges across every mandate that points at the same stored card. Pause or cancel the mandate instead when the card should stay usable. ## Next steps Pause, resume, cancel, and every status value. Short answers to common integration questions. # Setup Source: https://docs.reap.global/agentic-payments/setup Store a card once and confirm that it is ready to charge Agentic Payments enrolls only cards that support Cloud Token Framework and Visa Token Service tokenization. Check with your card issuer or BIN sponsor before you create an enrollment. The [Visa fact sheet](https://usa.visa.com/content/dam/VCOM/global/products/documents/visa-vts-cloud-token-framework-fact-sheet.pdf) covers what the framework provides. ## Before you start * A Reap API key for the sandbox or production environment * Agentic Payments enabled on your project * An HTTPS return URL that receives the user after the hosted card entry page and the hosted approval page Every request takes an [`Authorization`](/api-reference/authentication) header and a [`Reap-Version`](/api-reference/versioning) header. The examples below use the sandbox host. Switch to the production host when you go live. ## Store a card Pick the source that matches the card you are storing. [`POST /agentic/enrollments`](/api-reference/agentic/create-enrollment) requires an [`Idempotency-Key`](/api-reference/idempotency). A retry with the same key replays the first response instead of storing the card twice. Use this source for a card Reap already issued. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/enrollments \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: ' \ --header 'Reap-Version: ' \ --data '{ "source": "REAP_CARD", "cardId": "" }' ``` ```json Response theme={null} { "id": "", "status": "REQUIRES_ACTION", "source": "REAP_CARD", "owner": { "type": "REAP_USER", "id": "" } } ``` The `owner` block names the cardholder that Reap resolved from the card. Use this source for a card issued by a BIN sponsor. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/enrollments \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: ' \ --header 'Reap-Version: ' \ --data '{ "source": "BIN_SPONSOR", "cardId": "" }' ``` ```json Response theme={null} { "id": "", "status": "REQUIRES_ACTION", "source": "BIN_SPONSOR" } ``` This response carries no `owner` block. The sponsor card ID already resolves the cardholder on the sponsor side. Use this source when the user enters a new card. Send the owner reference from your own system and the URL the user returns to. ```bash Request theme={null} curl --request POST \ --url https://sandbox.api.reap.global/agentic/enrollments \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: ' \ --header 'Reap-Version: ' \ --data '{ "source": "EXTERNAL", "owner": { "type": "CLIENT_REFERENCE", "id": "", "email": "jsmith@example.com" }, "presentation": { "type": "REDIRECT", "returnUrl": "https://example.com/cards/added" } }' ``` ```json Response theme={null} { "id": "", "status": "REQUIRES_ACTION", "source": "EXTERNAL", "owner": { "type": "CLIENT_REFERENCE", "id": "", "email": "jsmith@example.com" }, "nextAction": { "type": "REDIRECT", "url": "", "expiresAt": "2026-01-01T00:00:00Z" } } ``` Keep the enrollment ID. You need it in the next two steps and at every checkout. This step applies to all sources. Redirect the user to `nextAction.url` before `expiresAt` passes, and the user enters the card on a page hosted by Reap. Card details never reach your servers. Reap sends the user back to the `returnUrl` you supplied. Returning to that URL means the hosted page is finished. It does not mean the card is stored, so confirm the status next. Read the enrollment with [`GET /agentic/enrollments/:id`](/api-reference/agentic/get-enrollment) after any hosted step. ```bash Request theme={null} curl --request GET \ --url https://sandbox.api.reap.global/agentic/enrollments/:id \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` ```json Response theme={null} { "id": "", "status": "ACTIVE", "owner": { "type": "REAP_USER", "id": "", "email": "jsmith@example.com" }, "paymentMethod": { "type": "CARD", "network": "", "last4": "4242", "expiryMonth": 11, "expiryYear": 2029 }, "nextAction": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z" } ``` A status of `ACTIVE` means the card is ready to charge. A status of `REQUIRES_ACTION` means a hosted step is still outstanding, and `nextAction` tells you where to send the user. The `paymentMethod` block gives you the network and the last four digits to display in your own interface. You can charge only an `ACTIVE` enrollment. Read the enrollment before you open a checkout rather than assuming the hosted page succeeded. ## Present the available enrollment cards to the user List the enrollments of one owner with [`GET /agentic/enrollments`](/api-reference/agentic/list-enrollments) and show each stored card so the user can pick one. The `paymentMethod` block carries the network, the last four digits, and the expiry to display. Scope every list call to a single owner. ```bash Request theme={null} curl --request GET \ --url 'https://sandbox.api.reap.global/agentic/enrollments?ownerType=REAP_USER&ownerId=:id&limit=20' \ --header 'Authorization: Bearer ' \ --header 'Reap-Version: ' ``` ```json Response theme={null} { "items": [ { "id": "", "status": "ACTIVE", "owner": { "type": "REAP_USER", "id": "", "email": "jsmith@example.com" }, "paymentMethod": { "type": "CARD", "network": "", "last4": "4242", "expiryMonth": 11, "expiryYear": 2029 }, "nextAction": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z" } ], "nextCursor": null } ``` Follow `nextCursor` when it holds a value. A `null` cursor means the last page. ## Test cards Use these test cards on the hosted card entry page to run the full payment flow in the sandbox. They behave like real cards but never move money. Reap adds more cards and networks as it supports them. These cards only work through the Agentic Payments API and are declined everywhere else. | Card number | CVC | Expiry | | - | - | - | | 4622 9431 2313 7797 | 640 | 12/27 | | 4622 9431 2313 7805 | 304 | 12/27 | | 4622 9431 2313 7847 | 698 | 12/27 | ## One-time passwords The card verification step may ask for a one-time password. Enter `456789`. The same code works whether you choose email or SMS delivery. ## Next steps Run a single purchase against the enrollment you created. Revoke a stored card and read every status value. Field level detail for every field, header, and error sits in the [API Reference](/api-reference/overview) tab. This page carries the sequence rather than repeating that detail. # FAQ Source: https://docs.reap.global/airport-lounge-access/faq Coverage, the plan, spending control and the questions prospects raise in evaluations Airport Lounge Access is coming soon and is not currently available through the Reap API. Clients must generate the QR code using the provided raw QR payload and render it in their app. The QR code or barcode is then scanned at the lounge reception. Passes admit cardholders to airport lounges in the Priority Pass network. Your Reap team shares the lounge directory and plan coverage during onboarding. One plan is available today. LOUNGE\_PP\_1PA is a single use pass in the form of QR code that covers one lounge visit and stays valid for 365 days from issuance. The lounge pass can only be used at participating lounges within the Priority Pass network. The name on the pass must match the boarding pass name exactly for access. Yes. A lounge pass can be created for another traveller. The name provided when creating the pass must exactly match the name shown on the boarding pass. The prepaid pool is the cap. Every purchase passes an explicit approval before stock is loaded, every issuance draws the pool down by exactly one, counters never go negative and a failed creation never consumes stock. The lounge records the visit and the pass turns redeemed. A redeemed pass is complete, so issue a new pass from your inventory when the cardholder needs another visit. Block and freeze disable the passes on that card. Unfreezing restores the passes when the cardholder or client unfreezes the card, while unblocking a blocked card requires Reap support. Delete and replace expire the passes permanently. Only the target card is affected, and nothing changes if the card operation fails. The [lifecycle page](/airport-lounge-access/lifecycle-and-events) maps every transition. Reap retrieves the lounge network report once a day and updates pass usage and visit records from it. Airport Lounge Access is switched on per business and requests made before enablement are rejected. Ask your Reap contact to enable the capability during onboarding. The flow is built around a general reward model, with a reward type and a plan on every request. Airport lounge access is the first benefit available through it. ## Next steps Start with inventory, then issue and track # Lifecycle and Events Source: https://docs.reap.global/airport-lounge-access/lifecycle-and-events The four pass statuses, the card operations that move them and the webhook events that announce every change A pass holds one of four statuses and stays valid for 365 days from issuance. Card operations move those statuses automatically. Webhook events report each purchase decision and each new pass, so nothing in the flow needs polling. ## The four statuses | Status | Meaning | | - | - | | Active | Ready for lounge entry, with the visit still available | | Redeemed | The visit is used and the pass is complete | | Disabled | Suspended while the card is blocked or frozen, and restored when the card returns | | Expired | Past the validity window or retired with a deleted or replaced card. Final | ```mermaid theme={null} stateDiagram-v2 direction LR [*] --> Active : Creation completes Active --> Redeemed : The visit is used Active --> Disabled : Card blocked or frozen Disabled --> Active : Card unblocked or unfrozen Active --> Expired : 365 days elapse, card deleted or replaced Disabled --> Expired : 365 days elapse, card deleted or replaced Expired --> [*] ``` Expiry runs automatically each night. It needs no call from you and applies to any pass past its expiry date regardless of its current status. ## Card events that update passes | Card operation | Effect on the passes on that card | | - | - | | Delete or replace | Passes become expired | | Block or freeze | Passes become disabled | | Unblock or unfreeze | Passes return to active | Only the passes on the target card are affected, and no pass changes if the card operation itself fails, so pass status can never drift from card status. Delete and replace retire the passes on the card permanently, and an expired pass cannot be reactivated. Check for unused passes before replacing a card. ## Webhook events Register one endpoint and handle the three events below. Delivery and [signature verification](/webhooks/signature-verification) work the same as every other [Reap event](/webhooks/overview). | Event | Fires when | What to do | | - | - | - | | `PURCHASE_REQUEST_APPROVED` | A purchase is approved and the passes land in your pool | Refresh inventory and begin issuing passes | | `PURCHASE_REQUEST_REJECTED` | A purchase is rejected with a reason | Surface the reason and raise a corrected request if needed | | `PASS_CREATION_COMPLETED` | Creation finishes and the pass becomes active | Fetch the pass and present it to the cardholder | Lounge visits do not raise an event today. Reap updates every pass from the lounge network report once a day, and a webhook that reports each visit as it happens is coming soon. ## Next steps Purchase inventory, issue passes and track lounge visits Coverage, the plan, spending control and the questions prospects raise # Overview Source: https://docs.reap.global/airport-lounge-access/overview Offer airport lounge visits as a built in benefit on the cards you issue Airport Lounge Access is coming soon and is not currently available through the Reap API. ## Four parties, one flow Purchase pass inventory, issue passes to your cardholders, monitor stock and usage and receive webhook notifications Reviews every purchase, holds the inventory pool, creates each pass on the lounge network and keeps pass status in step with the card Admits the cardholder at lounges in the Priority Pass network and reports every visit back to Reap Receives the pass inside your app and scans the QR code or barcode at the lounge door ## The pass journey Inventory is purchased in batches and passes are issued one card at a time. Redemption and expiry then run for as long as each pass lives. The flow is the same for every client, whether you operate through the Reap dashboard or a direct API integration. ```mermaid theme={null} flowchart LR A["Purchase
request"] --> B["Reap
review"] B -->|"Approved"| C["Inventory
pool"] B -->|"Rejected"| R["Reason via
webhook"] C --> D["Pass issued
to a card"] D --> E["Cardholder scans
at the lounge"] E --> F["Visit recorded
on the pass"] ``` The [working with passes](/airport-lounge-access/working-with-passes) guide walks each stage of the journey in order. ## The spending control model The prepaid inventory pool is the spending cap. A pass can only be issued while stock remains, every successful issuance draws the pool down by exactly one, and a failed creation never draws it down at all. Passes enter the pool only through an approved purchase, so exposure is agreed before a single pass reaches a cardholder. Counters never fall below zero. When one pass remains, only one issuance can succeed. Approved purchases are invoiced to your business and every pass carries its recorded cost. ## The plan One plan is available today. `LOUNGE_PP_1PA` is a single use pass that covers one lounge visit and stays valid for 365 days from issuance. Pricing is configured for your business during onboarding. A redeemed pass is complete, so issue a new pass from your inventory when a cardholder needs another visit. ## Who does what Reap operates the whole flow behind the Reap API. It reviews and approves every inventory purchase, holds the pool and creates each pass on the lounge network at the moment of issuance. It records lounge visits against the right pass, keeps pass status synchronised with the card lifecycle and delivers webhook notifications and audit records throughout. You call the rewards endpoints from your existing integration and register a [webhook endpoint](/webhooks/overview) for the purchase decision and pass creation events. The pass returns a raw QR payload and a barcode string rather than an image, so your app generates the scannable QR code from the payload and renders both to the cardholder. You decide when to purchase stock and when to issue a pass. The same flow also runs through the Reap dashboard, so a benefit can launch before any integration work begins. You request a batch of passes and Reap reviews and decides the purchase. Approved passes land in your inventory pool and appear in your counters. You issue a pass to a card and Reap creates it on the lounge network within seconds. The cardholder scans the pass at the lounge and the network reports the visit to Reap. Reap records the visit, updates the pass and expires or disables passes as the card and the calendar require. ## Before you start 1. Airport Lounge Access enabled for your business by your Reap team, since requests made before enablement are rejected 2. The lounge plan configured for your business 3. Cards issued through Reap, because every pass attaches to an [existing card](/cards/overview) 4. A registered [webhook endpoint](/webhooks/overview) for the purchase decision and pass creation events ## Next steps Purchase inventory, issue passes and track lounge visits The four pass statuses, the card operations that move them and the webhook events # Working with Passes Source: https://docs.reap.global/airport-lounge-access/working-with-passes Purchase inventory, issue passes to cards and track lounge visits Three actions cover the whole operational journey. You purchase stock into the pool, issue passes to individual cards and watch visits land as cardholders redeem at the lounge. Each action below opens with what it does, then walks the steps involved. ## Purchase inventory Passes are purchased before they are issued. You submit a purchase request for a plan and a quantity, your Reap team reviews it, and approval loads the passes straight into your inventory pool. Nothing reaches the pool from a request that is still pending or rejected. The request captures what you want to buy and nothing moves until it is decided. * Your application submits the reward type, plan name and quantity with `POST /cards/rewards/request-purchase` * Reap records the request as pending review and routes it to your Reap team * No passes are added at this stage Your Reap team approves or rejects the request and every request is decided exactly once. * Approval records the invoice detail and loads the purchased passes into your pool * Rejection records a reason and loads nothing The outcome lands on your webhook endpoint either way. * Reap delivers the decision through the `PURCHASE_REQUEST_APPROVED` or `PURCHASE_REQUEST_REJECTED` webhook * On approval, refresh your inventory and begin issuing * On rejection, surface the reason and raise a corrected request if needed ### Reading your inventory `GET /cards/rewards/inventory` returns your stock scoped to your business, with filters for reward type and plan name. Each row shows the purchased, consumed and available totals for the plan, with audit detail alongside. The Reap dashboard shows the same counters read only and flags low stock once available passes fall below your threshold, which defaults to ten. Raise the next purchase request before the pool runs dry so issuance never has to wait on a review. ## Issue a pass One call issues a pass to an existing card. The call returns immediately, creation runs in the background on the lounge network, and the pass becomes active shortly after. A webhook announces completion so you can present the pass without polling. One call starts the whole journey and returns straight away. * Your application calls `POST /cards/:id/rewards` with the reward type and plan name * Reap validates the feature, the card ownership and the available stock, then queues creation and acknowledges immediately Reap creates the pass on the lounge network, and the step typically completes in around ten seconds. * Reap records the pass against the card and draws the pool down by one * A failed creation leaves no pass record and no net change to your inventory, so issue again A webhook announces the pass so you never have to poll. * The `PASS_CREATION_COMPLETED` webhook fires once the pass is active * Your application fetches it with `GET /cards/:id/rewards` * The pass carries a raw QR payload and a barcode string rather than a ready made image, so generate the QR code from the payload and render both to the cardholder When a single pass remains in the pool and two issuances race, exactly one succeeds. Counters never fall below zero, so a burst of issuance activity can never spend more stock than you hold. Every pass returned on the card includes what your app needs to display the benefit: * The status, one of active, redeemed, disabled or expired * The visit allowance and the visits already recorded * The raw QR payload and the barcode string for lounge entry * The recorded cost, the currency and the expiry date ## Track redemptions Entry needs only the QR code and barcode rendered in your app. The cardholder carries no separate membership card and completes no enrolment with the lounge network. Reap retrieves the lounge network report once a day and updates pass usage and visit records from it, so your app can always show whether a pass has been used. The pass turns redeemed once its visit is recorded. No client action is required. ## The machinery behind the flow * `POST /cards/rewards/request-purchase` submits a purchase request for a plan and a quantity * `GET /cards/rewards/inventory` returns the purchased, consumed and available totals for your business * `POST /cards/:id/rewards` issues a pass to a card and queues creation on the lounge network * `GET /cards/:id/rewards` lists the passes on a card with status, usage and the QR and barcode payloads These guides stay at the level of names and purpose. Field level detail belongs to the API Reference. ## Next steps The four pass statuses, the card operations that move them and the webhook events Coverage, the plan, spending control and the questions prospects raise # 3ds challenge created Source: https://docs.reap.global/api-reference/3ds-challenge-created /webhooks/openapi.json webhook CARD_3DS_CHALLENGE_CREATED Fired when a new 3DS authentication challenge is received for a card. The partner must respond within the expiry window. # Account status updated Source: https://docs.reap.global/api-reference/account-status-updated /webhooks/openapi.json webhook ACCOUNT_STATUS_UPDATED Fired when an account status changes (e.g., restricted, reactivated). # Create account Source: https://docs.reap.global/api-reference/accounts/create-account /api-reference/openapi.json post /accounts/ Creates a new account for the given owner. Set `ownerType` to `USER` (default) or `COMPANY` (corporate programs only) to choose the owner type. The owner must have an approved application (KYC for users, KYB for companies). For projects that provision user wallets (`USER_FUNDED` funding model), deposit addresses are created on all enabled chains and signer data is required (address + signed auth message from `GET /accounts/auth-message`). For projects without user wallets (`PROGRAM_FUNDED` funding model), no deposit addresses are provisioned and signers are not required. # Generate signer message Source: https://docs.reap.global/api-reference/accounts/generate-signer-message /api-reference/openapi.json get /accounts/auth-message Generates a time-limited authentication message that must be signed by the end-user's wallet to prove key ownership. The signed message is required when creating an account (`POST /accounts`). Messages expire after 15 minutes. # Get account Source: https://docs.reap.global/api-reference/accounts/get-account /api-reference/openapi.json get /accounts/{id} Retrieves account details and chain addresses, each listing the assets that address accepts - send only those, as a deposit of anything else fails with `ASSET_NOT_ACCEPTED`. List responses omit the accepted assets, so read them from here. Use `GET /accounts/:id/balance` to retrieve balance information. # Get account assets Source: https://docs.reap.global/api-reference/accounts/get-account-assets /api-reference/openapi.json get /accounts/{id}/assets Retrieves per-asset balance breakdown with valuations, aggregated by symbol across all chains. Crypto assets also carry a `byChain` breakdown with the amount held, the withdrawable cap, and the withdrawal fee for each chain. All valuations are in the project's billing currency. # Get account balance Source: https://docs.reap.global/api-reference/accounts/get-account-balance /api-reference/openapi.json get /accounts/{id}/balance Retrieves the account financial position including total asset value, liabilities, and net available balance. Use `GET /accounts/:id/assets` for per-asset breakdown. All values are in the project's billing currency (see the `currency` field). # List accounts Source: https://docs.reap.global/api-reference/accounts/list-accounts /api-reference/openapi.json get /accounts/ Retrieves a paginated list of accounts. Use `GET /accounts/:id/balance` to retrieve balance details for a specific account. # List activities Source: https://docs.reap.global/api-reference/activities/list-activities /api-reference/openapi.json get /activities/ Retrieves a paginated, chronologically ordered list of fund movements. Supports filtering by resource identifiers, activity type, time window, and status. # Cancel mandate Source: https://docs.reap.global/api-reference/agentic/cancel-mandate /api-reference/openapi.json post /agentic/mandates/{id}/cancel Agentic Payments - enrollments, mandates, products, quotes, and checkouts. # Create checkout Source: https://docs.reap.global/api-reference/agentic/create-checkout /api-reference/openapi.json post /agentic/checkouts Opens the payment for an unexpired quote against an `ACTIVE` enrollment, whose stored card and owner are used for the charge. The response carries a redirect to a Reap-hosted approval page where the user reviews the order and approves the payment. After approval the user returns to `presentation.returnUrl` and Reap completes the merchant checkout. # Create enrollment Source: https://docs.reap.global/api-reference/agentic/create-enrollment /api-reference/openapi.json post /agentic/enrollments Stores a card for later agentic purchases. `EXTERNAL` returns a redirect to a hosted card-entry page and becomes `ACTIVE` once the customer submits their card. `REAP_CARD` and `BIN_SPONSOR` are coming soon. Pass the enrollment id to `POST /agentic/checkouts` to charge it. # Create quote Source: https://docs.reap.global/api-reference/agentic/create-quote /api-reference/openapi.json post /agentic/quotes Opens and prices a merchant checkout from product variants or a checkout URL assembled by the client. Send exactly one of `items` and `externalCheckout`. Prices always come from the merchant. The response itemizes the total and lists the available shipping options. Quotes are short-lived: create the checkout before `expiresAt` passes, or open a fresh quote. `shippingAddress` is required for external checkout quotes and when any item requires shipping. Use `offerCode` to price an offer before approval. Reap makes one quote attempt and does not retry a failed merchant request. The error code states whether to correct the request or create a new quote. # Get checkout Source: https://docs.reap.global/api-reference/agentic/get-checkout /api-reference/openapi.json get /agentic/checkouts/{id} Returns the checkout and its lifecycle status. Poll after sending the user to the approval page until the checkout reaches a terminal status; a completed checkout carries the merchant order reference and the final charged amount. # Get enrollment Source: https://docs.reap.global/api-reference/agentic/get-enrollment /api-reference/openapi.json get /agentic/enrollments/{id} Agentic Payments - enrollments, mandates, products, quotes, and checkouts. # Get mandate Source: https://docs.reap.global/api-reference/agentic/get-mandate /api-reference/openapi.json get /agentic/mandates/{id} Agentic Payments - enrollments, mandates, products, quotes, and checkouts. # Get product details Source: https://docs.reap.global/api-reference/agentic/get-product-details /api-reference/openapi.json post /agentic/products/details Returns full details for a product, including its purchasable variants. When a product has options, use `POST /agentic/products/variant` to resolve a variant from the selected option ids. Quote line items reference the returned variant ids. # Get quote Source: https://docs.reap.global/api-reference/agentic/get-quote /api-reference/openapi.json get /agentic/quotes/{id} Returns a previously created quote, including its shipping options and amount breakdown. Create the checkout before `expiresAt` passes. # List enrollments Source: https://docs.reap.global/api-reference/agentic/list-enrollments /api-reference/openapi.json get /agentic/enrollments Lists the enrollments of a single owner. `ownerId` is required, and a list is always scoped to exactly one owner. # Pause mandate Source: https://docs.reap.global/api-reference/agentic/pause-mandate /api-reference/openapi.json post /agentic/mandates/{id}/pause Agentic Payments - enrollments, mandates, products, quotes, and checkouts. # Resolve variant Source: https://docs.reap.global/api-reference/agentic/resolve-variant /api-reference/openapi.json post /agentic/products/variant Resolves a purchasable variant from selected option values. The returned variant id is the only id accepted by `POST /agentic/quotes`. # Resume mandate Source: https://docs.reap.global/api-reference/agentic/resume-mandate /api-reference/openapi.json post /agentic/mandates/{id}/resume Agentic Payments - enrollments, mandates, products, quotes, and checkouts. # Revoke enrollment Source: https://docs.reap.global/api-reference/agentic/revoke-enrollment /api-reference/openapi.json post /agentic/enrollments/{id}/revoke Agentic Payments - enrollments, mandates, products, quotes, and checkouts. # Search products Source: https://docs.reap.global/api-reference/agentic/search-products /api-reference/openapi.json post /agentic/products/search Searches merchant catalogs for products matching a free-text query, with optional merchant preference and result filters. Use `POST /agentic/products/details` on a result to resolve its purchasable variants. # Select shipping option Source: https://docs.reap.global/api-reference/agentic/select-shipping-option /api-reference/openapi.json post /agentic/quotes/{id}/shipping-option Selects a shipping option on an existing quote and re-prices it. The response is the updated quote with a fresh amount breakdown. `QUOTE_REPLACEMENT_REQUIRED` means the caller must create a new quote. # Authentication Source: https://docs.reap.global/api-reference/authentication How to authenticate API requests to Reap. All API requests require authentication using an API key. ## API Key Include your API key as a Bearer token in the `Authorization` header: ``` Authorization: Bearer YOUR_API_KEY ``` API keys are project-scoped. Each key grants access to resources within a single project. ## API Versioning Include the `Reap-Version` header in every request to specify which API version to use: ``` Reap-Version: 2025-02-14 ``` The version uses a date-based format (YYYY-MM-DD). Requests without this header will be rejected. ## Example ```bash theme={null} curl https://sg.sandbox.api.reap.global/users \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Reap-Version: 2025-02-14" ``` ## Environments | Environment | Base URL | | - | - | | Singapore sandbox | `https://sg.sandbox.api.reap.global` | | Singapore production | `https://sg.prod.api.reap.global` | | Mexico sandbox | `https://mx.sandbox.api.reap.global` | | Mexico production | `https://mx.prod.api.reap.global` | `https://sandbox.api.reap.global` and `https://prod.api.reap.global` remain valid aliases for Singapore. API keys are scoped to a single environment. A sandbox key cannot be used against production and vice versa. ## IP allowlist Each key can optionally be restricted to a list of IPv4/IPv6 addresses or CIDR ranges. An empty list means the key works from any IP. When a list is set, requests from any other address are rejected with `403` and code `API_KEY_IP_NOT_ALLOWED`. Reap uses the last `X-Forwarded-For` hop (the address the load balancer saw). If that hop is missing, the request is also rejected. Contact the Reap team to obtain API keys for your project. # Card authorization request Source: https://docs.reap.global/api-reference/card-authorization-request /webhooks/openapi.json webhook CARD_AUTHORIZATION_REQUEST Sent synchronously to an External-authorization project. Reap POSTs the proposed authorization and blocks on your approve/decline decision, which you return in the HTTP response body within the request timeout. This is the only webhook whose response Reap consumes: a non-2xx, timeout, or unparseable body is treated as a decline (fail closed). # Get card design Source: https://docs.reap.global/api-reference/card-designs/get-card-design /api-reference/openapi.json get /card-designs/{id} Card designs available to the project. Designs are assigned to projects out of band; clients pick a design when issuing or shipping cards. # List card designs Source: https://docs.reap.global/api-reference/card-designs/list-card-designs /api-reference/openapi.json get /card-designs/ Returns the card designs. By default only active designs (usable for new cards) are returned; pass the `status` query parameter to widen or change the filter. # Set default card design Source: https://docs.reap.global/api-reference/card-designs/set-default-card-design /api-reference/openapi.json put /card-designs/default Sets the card design used by default when issuing cards. The design should be active. # Card dispute status updated Source: https://docs.reap.global/api-reference/card-dispute-status-updated /webhooks/openapi.json webhook CARD_DISPUTE_STATUS_UPDATED Fired when a card dispute status changes. # Card fraud alert created Source: https://docs.reap.global/api-reference/card-fraud-alert-created /webhooks/openapi.json webhook CARD_FRAUD_ALERT_CREATED Fired when a fraud alert is created for a card transaction (detected by fraud monitoring, or reported by the partner). Detected alerts must be confirmed or declined within the response window. Card blocks are delivered separately via CARD_STATUS_UPDATED. # Card fraud alert status updated Source: https://docs.reap.global/api-reference/card-fraud-alert-status-updated /webhooks/openapi.json webhook CARD_FRAUD_ALERT_STATUS_UPDATED Fired when a fraud alert status changes. Confirmed fraud also blocks the card; subscribe to CARD_STATUS_UPDATED for that transition. # Card shipment status updated Source: https://docs.reap.global/api-reference/card-shipment-status-updated /webhooks/openapi.json webhook CARD_SHIPMENT_STATUS_UPDATED Fired when a card shipment status changes (e.g., in production, ready to ship, in transit, delivered). The payload matches GET /card-shipments/:id, including the cards in the shipment. # Add card to draft Source: https://docs.reap.global/api-reference/card-shipments/add-card-to-draft /api-reference/openapi.json post /card-shipments/{id}/cards Append a card to a shipment in `DRAFT` status. Rejects if the card is already in another active shipment, or if the shipment is already at the 200-card limit. # Create shipment draft Source: https://docs.reap.global/api-reference/card-shipments/create-shipment-draft /api-reference/openapi.json post /card-shipments/ Creates a new card shipment in `DRAFT` status. No manufacturer call is made until the draft is explicitly submitted. # Delete shipment draft Source: https://docs.reap.global/api-reference/card-shipments/delete-shipment-draft /api-reference/openapi.json delete /card-shipments/{id} Only shipments in `DRAFT` status can be deleted. # Get shipment Source: https://docs.reap.global/api-reference/card-shipments/get-shipment /api-reference/openapi.json get /card-shipments/{id} Unified card shipments API. Ship one card or up to 200 cards in a single draft-first shipment. # List shipments Source: https://docs.reap.global/api-reference/card-shipments/list-shipments /api-reference/openapi.json get /card-shipments/ Returns a paginated list of card shipments, most recent first. Pass `cardId` to filter to shipments that include a given card (or any of several cards). Pass `include=cards` to include every card in each matching shipment. # Remove card from draft Source: https://docs.reap.global/api-reference/card-shipments/remove-card-from-draft /api-reference/openapi.json delete /card-shipments/{id}/cards/{memberId} Remove a card from a shipment in `DRAFT` status. Removing the last remaining card is not allowed - delete the shipment instead. # Submit shipment Source: https://docs.reap.global/api-reference/card-shipments/submit-shipment /api-reference/openapi.json post /card-shipments/{id}/submit Transitions the shipment from `DRAFT` to `PLACED`. Single-card shipments hit the single-ship endpoint; multi-card shipments use the bulk-ship endpoint. On failure the shipment remains in `DRAFT` so the client can retry. Returns the full shipment detail (including `cards[]`). When the manufacturer rejects a subset of cards on a multi-card submit, the response uses HTTP **207 Multi-Status**: the surviving cards transition to `PLACED` upstream while each rejected card is returned with `productionStatus = "BULK_SHIP_REJECTED"` and a `rejectedAt` timestamp. A 200 indicates every card was accepted. # Update shipping details Source: https://docs.reap.global/api-reference/card-shipments/update-shipping-details /api-reference/openapi.json patch /card-shipments/{id} Update the box-level destination, recipient, or courier on a `DRAFT` shipment. Use `POST /:id/cards` and `DELETE /:id/cards/:memberId` to change the card set. # Card status updated Source: https://docs.reap.global/api-reference/card-status-updated /webhooks/openapi.json webhook CARD_STATUS_UPDATED Fired when a card status changes (e.g., activated, frozen, unfrozen, cancelled). # Card tokenization requested Source: https://docs.reap.global/api-reference/card-tokenization-requested /webhooks/openapi.json webhook CARD_TOKENIZATION_REQUESTED Fired when a cardholder adds a card to a digital wallet and chooses SMS verification. Contains the OTP so it can be delivered through an alternative channel if the SMS does not arrive. The OTP is valid for a short window (around 2 minutes). # Card transaction created Source: https://docs.reap.global/api-reference/card-transaction-created /webhooks/openapi.json webhook CARD_TRANSACTION_CREATED Fired when a new card transaction is created (e.g., authorization, purchase). # Card transaction updated Source: https://docs.reap.global/api-reference/card-transaction-updated /webhooks/openapi.json webhook CARD_TRANSACTION_UPDATED Fired when a card transaction receives a new lifecycle event (e.g., clearing, reversal, refund). # Get card transaction Source: https://docs.reap.global/api-reference/card-transactions/get-card-transaction /api-reference/openapi.json get /card-transactions/{id} Retrieves card transaction details including merchant information, amounts, and lifecycle events. The response shape varies by transaction status. # Activate physical card Source: https://docs.reap.global/api-reference/cards/activate-physical-card /api-reference/openapi.json post /cards/{id}/activate Activates a physical card using the 6-digit activation code from the card mailer. Use `GET /cards/:id/activation-code` only when the cardholder has lost the code that came with the card packaging. # Block card Source: https://docs.reap.global/api-reference/cards/block-card /api-reference/openapi.json post /cards/{id}/block Blocks a card. A block is the hard stop - a compromised card, a dispute, an account on hold - while freeze is the soft toggle a cardholder flips. A blocked card declines everything: online, in-store, and ATM. Blocking a frozen card keeps the freeze, so the card returns to `FROZEN` when the block lifts. Idempotent, and a block placed by Reap is never replaced: if the card already carries a block with `blockLiftable: false`, that reason stays and the card remains one only Reap support can unblock. # Create card Source: https://docs.reap.global/api-reference/cards/create-card /api-reference/openapi.json post /cards/ Creates a new virtual or physical card for a user. User must have approved application; account must be active. The phone number used for OTP verification is taken from the user record; the card is issued against the user's verified identity. # Delete card Source: https://docs.reap.global/api-reference/cards/delete-card /api-reference/openapi.json delete /cards/{id} Deletes a card. Returns `404 CARD_NOT_FOUND` if the card does not exist or has already been deleted. # Freeze card Source: https://docs.reap.global/api-reference/cards/freeze-card /api-reference/openapi.json post /cards/{id}/freeze Freezes a card. Frozen cards cannot be used for transactions. # Get 3DS challenge Source: https://docs.reap.global/api-reference/cards/get-3ds-challenge /api-reference/openapi.json get /card-3ds-challenges/{challengeId} Retrieves a 3DS challenge by ID. # Get activation code Source: https://docs.reap.global/api-reference/cards/get-activation-code /api-reference/openapi.json get /cards/{id}/activation-code Retrieves the 6-digit activation code for a physical card. Use only when the cardholder has lost the code that came with the card packaging. Verify the cardholder's identity through your own secure channel before sharing the code. # Get card Source: https://docs.reap.global/api-reference/cards/get-card /api-reference/openapi.json get /cards/{id} Retrieves a single card by its unique identifier. # List cards Source: https://docs.reap.global/api-reference/cards/list-cards /api-reference/openapi.json get /cards/ Retrieves a paginated list of cards. Use cursor-based pagination. Filter by `accountId` to get cards for a specific account. # Push-provision card Source: https://docs.reap.global/api-reference/cards/push-provision-card /api-reference/openapi.json post /cards/{id}/push-provisioning Generates the credentials needed to add a card to a digital wallet directly from your app, without the cardholder entering card details. Pass the result to the wallet SDK to complete provisioning. Each call returns fresh, single-use credentials. # Respond to 3DS challenge Source: https://docs.reap.global/api-reference/cards/respond-to-3ds-challenge /api-reference/openapi.json post /card-3ds-challenges/{challengeId}/respond Approves or rejects a 3DS authentication challenge. Must respond within 5 minutes of the challenge being created. # Reveal card details Source: https://docs.reap.global/api-reference/cards/reveal-card-details /api-reference/openapi.json post /cards/{id}/reveal Generates a short-lived URL to securely display card details (PAN, CVV, expiry) in an iframe or WebView. The URL is valid for 5 minutes and is single-use. Do not store, log, or expose this URL beyond the immediate display context. # Reveal PAN Source: https://docs.reap.global/api-reference/cards/reveal-pan /api-reference/openapi.json post /cards/{id}/reveal-pan Returns the card details (PAN, CVV, expiry) as an encrypted payload. Available only to PCI-scoped partners approved by Reap; all other projects receive `403 PAN_REVEAL_NOT_ENABLED`. Decrypt with the private half of the RSA key pair registered with Reap, using the OAEP hash named by `encryption`. Never store or log the decrypted card details. # Unblock card Source: https://docs.reap.global/api-reference/cards/unblock-card /api-reference/openapi.json post /cards/{id}/unblock Lifts a block. Only blocks reported with `blockLiftable: true` can be lifted this way; the rest return `409 CARD_BLOCK_NOT_LIFTABLE` and need Reap support. A card frozen before it was blocked returns to `FROZEN`, not `ACTIVE`, and a card unfrozen while blocked returns to `ACTIVE`. Idempotent; unblocking a card that holds no block returns it unchanged. # Unfreeze card Source: https://docs.reap.global/api-reference/cards/unfreeze-card /api-reference/openapi.json post /cards/{id}/unfreeze Unfreezes a previously frozen card. # Update 3DS challenge method Source: https://docs.reap.global/api-reference/cards/update-3ds-challenge-method /api-reference/openapi.json put /cards/{id}/3ds-challenge-method Updates the 3DS challenge verification method for a card. # Update card PIN Source: https://docs.reap.global/api-reference/cards/update-card-pin /api-reference/openapi.json put /cards/{id}/pin Updates the PIN for a card. Only supported for physical cards; virtual cards have no PIN. PIN must be 4-12 digits and cannot contain sequences of 3+ consecutive or repeated numbers. # Advance company application Source: https://docs.reap.global/api-reference/companies/advance-company-application /api-reference/openapi.json post /companies/{id}/applications/ Advances the company application. For Managed KYB, send `redirectUri` (the default when `method` is omitted) to get a verification URL for the corporate client. Returns an error if an active Managed application already exists. For Universal KYB, send `method: UNIVERSAL_KYB` and the verified business pack. The first call creates the application; later calls amend it and return the same application until it is submitted. `nextAction` is `null` for Managed KYB. # Create company Source: https://docs.reap.global/api-reference/companies/create-company /api-reference/openapi.json post /companies/ Creates a new company with the provided name. Only available in `CORPORATE` program mode. # Get company Source: https://docs.reap.global/api-reference/companies/get-company /api-reference/openapi.json get /companies/{id} Retrieves a single company by its unique identifier, including deactivated companies (`isDeactivated` is true). # Get company application Source: https://docs.reap.global/api-reference/companies/get-company-application /api-reference/openapi.json get /companies/{id}/applications/{applicationId} Retrieves a specific company application by its ID. # Get related person Source: https://docs.reap.global/api-reference/companies/get-related-person /api-reference/openapi.json get /companies/{id}/related-persons/{personId} Retrieves a related person by ID, including archived persons (`isActive` is false). # Get related person application Source: https://docs.reap.global/api-reference/companies/get-related-person-application /api-reference/openapi.json get /companies/{id}/related-person-applications/{applicationId} Retrieves a specific KYC verification application for a related person. # List companies Source: https://docs.reap.global/api-reference/companies/list-companies /api-reference/openapi.json get /companies/ Retrieves a paginated list of companies. Use cursor-based pagination by passing the `nextCursor` value from the response to get subsequent pages. # List company applications Source: https://docs.reap.global/api-reference/companies/list-company-applications /api-reference/openapi.json get /companies/{id}/applications/ Returns a paginated list of all company applications, ordered by creation date descending. Use the `status` query parameter to filter by application status. For example, to retrieve only active applications: `?status=INITIATED,IN_PROGRESS,REVIEW_REQUIRED`. # List related person applications Source: https://docs.reap.global/api-reference/companies/list-related-person-applications /api-reference/openapi.json get /companies/{id}/related-person-applications/ Returns a paginated list of related person KYC applications for the company, ordered by creation date descending. Use the `status` query parameter to filter by application status (comma-separated), or `relatedPersonId` to scope to a single related person. # List related persons Source: https://docs.reap.global/api-reference/companies/list-related-persons /api-reference/openapi.json get /companies/{id}/related-persons/ Returns a paginated list of related persons (officers and UBOs) for the company, ordered by creation date descending. Archived persons are omitted unless `includeArchived` is true. Use the `status` query parameter to filter by verification status (comma-separated), or `id` to fetch a single person by ID. Related persons are automatically discovered during company verification. # Submit company application Source: https://docs.reap.global/api-reference/companies/submit-company-application /api-reference/openapi.json post /companies/{id}/applications/submit Submits a complete Universal KYB application for review. Required company documents and every declared beneficial-owner identity document must already be uploaded. On success the company moves to `IN_REVIEW` and `nextAction` is `null`. An incomplete submission returns `COMPANY_APPLICATION_INCOMPLETE` with `missing` and `invalid` so every remaining gap can be fixed in one pass. # Upload application documents Source: https://docs.reap.global/api-reference/companies/upload-application-documents /api-reference/openapi.json post /companies/{id}/applications/documents Uploads one company supporting document type for a Universal KYB application. Set `documentType` to a pending company document from `nextAction` and attach its file(s) as `files`. Upload one type per request. Beneficial-owner identity documents use `POST /companies/:id/related-persons/:personId/documents`. # Upload related person documents Source: https://docs.reap.global/api-reference/companies/upload-related-person-documents /api-reference/openapi.json post /companies/{id}/related-persons/{personId}/documents Uploads one declared beneficial-owner identity document for a Universal KYB application. `uboDocumentType` must match a type declared for this person. Send the front image first, then the back if the document has two sides. # Company status updated Source: https://docs.reap.global/api-reference/company-status-updated /webhooks/openapi.json webhook COMPANY_STATUS_UPDATED Fired when a company status changes (e.g., KYB approved, rejected, restricted). # Crypto deposit created Source: https://docs.reap.global/api-reference/crypto-deposit-created /webhooks/openapi.json webhook CRYPTO_DEPOSIT_CREATED Fired when a new on-chain deposit is detected on an account deposit address. # Crypto deposit status updated Source: https://docs.reap.global/api-reference/crypto-deposit-status-updated /webhooks/openapi.json webhook CRYPTO_DEPOSIT_STATUS_UPDATED Fired when a crypto deposit status changes (e.g., approved, rejected). # Get crypto deposit Source: https://docs.reap.global/api-reference/crypto-deposits/get-crypto-deposit /api-reference/openapi.json get /crypto-deposits/{id} Retrieves crypto deposit details including asset metadata. # Cancel crypto withdrawal Source: https://docs.reap.global/api-reference/crypto-withdrawals/cancel-crypto-withdrawal /api-reference/openapi.json post /crypto-withdrawals/{id}/cancel Cancels a withdrawal that is still awaiting a signature and releases the reserved funds. # Get crypto withdrawal Source: https://docs.reap.global/api-reference/crypto-withdrawals/get-crypto-withdrawal /api-reference/openapi.json get /crypto-withdrawals/{id} Retrieves a crypto withdrawal by id. Pure read - does not mint a signing payload. # Initiate crypto withdrawal Source: https://docs.reap.global/api-reference/crypto-withdrawals/initiate-crypto-withdrawal /api-reference/openapi.json post /crypto-withdrawals/ Reserves funds against an in-flight withdrawal and opens the signature window. Fetch a signing payload with `GET /crypto-withdrawals/:id/payload`, then submit the owner signature with `POST /crypto-withdrawals/:id/sign`. # Mint signing payload Source: https://docs.reap.global/api-reference/crypto-withdrawals/mint-signing-payload /api-reference/openapi.json get /crypto-withdrawals/{id}/payload Mints a fresh payload for the wallet owner to sign. Call this immediately before signing; each call retires any earlier unsigned payload. Only valid while the withdrawal awaits a signature. # Submit withdrawal signature Source: https://docs.reap.global/api-reference/crypto-withdrawals/submit-withdrawal-signature /api-reference/openapi.json post /crypto-withdrawals/{id}/sign Submits the wallet owner signature over the current signing payload. The withdrawal then proceeds to broadcast. # File dispute Source: https://docs.reap.global/api-reference/disputes/file-dispute /api-reference/openapi.json post /disputes/ Files a dispute against a cleared card transaction. Card, cardholder, and currency are derived from the transaction. Status changes arrive as `CARD_DISPUTE_STATUS_UPDATED`. # Get dispute Source: https://docs.reap.global/api-reference/disputes/get-dispute /api-reference/openapi.json get /disputes/{id} Retrieves a single card dispute by its unique identifier. # List disputes Source: https://docs.reap.global/api-reference/disputes/list-disputes /api-reference/openapi.json get /disputes/ Returns a paginated list of card disputes, newest first. Filter by status, reason, category, card, transaction, or filed time range. No status default is applied. # Errors Source: https://docs.reap.global/api-reference/errors Error response format and status codes. All error responses follow a consistent format. ## Error Response Format ```json theme={null} { "error": { "code": "USER_NOT_FOUND", "message": "User not found", "detail": null } } ``` | Field | Type | Description | | - | - | - | | `code` | `string` | Machine-readable error code for programmatic handling | | `message` | `string` | Human-readable error description | | `detail` | `object \| null` | Additional context when available | ## HTTP Status Codes | Status | Meaning | | - | - | | `400` | Bad Request. The request is malformed or the endpoint rejected it. | | `401` | Unauthorized. Invalid or missing API key. | | `403` | Forbidden. Valid API key but insufficient permissions, or the client IP is outside the key's allowlist (`API_KEY_IP_NOT_ALLOWED`). | | `404` | Not Found. Resource does not exist. | | `409` | Conflict. Resource already exists or state conflict. | | `422` | Unprocessable Entity. The request failed validation. See [Validation errors](#validation-errors). | | `429` | Too Many Requests. Rate limit exceeded. See [Rate Limiting](/api-reference/rate-limiting). | | `500` | Internal Server Error. Contact support if persistent. | ## Errors any endpoint can return Each endpoint page in the API reference lists the errors specific to that endpoint. The errors below can come from any endpoint, so they are documented only here. | Status | Code | Cause | | - | - | - | | `400` | `API_VERSION_HEADER_MISSING` | The `Reap-Version` header is missing. See [Versioning](/api-reference/versioning). | | `400` | `API_VERSION_INVALID` | The `Reap-Version` header is not a supported version. | | `400` | `PARSE_ERROR` | The request body could not be parsed. | | `400` | `IDEMPOTENT_PARAMETER_MISMATCH` | A POST reused an `Idempotency-Key` with a different body. See [Idempotency](/api-reference/idempotency). | | `401` | `API_KEY_REQUIRED` | The `Authorization` header is missing. See [Authentication](/api-reference/authentication). | | `401` | `INVALID_AUTH_HEADER` | The `Authorization` header is not a Bearer token. | | `401` | `INVALID_API_KEY` | The API key is unknown or revoked, or it belongs to another environment or region. | | `403` | `API_KEY_IP_NOT_ALLOWED` | The request came from an IP address outside the key's allowlist. | | `404` | `ROUTE_NOT_FOUND` | No endpoint matches the method and path. | | `409` | `IDEMPOTENCY_REQUEST_IN_PROGRESS` | Another POST with the same `Idempotency-Key` is still running. | | `422` | `VALIDATION_FAILED` | The request does not match the endpoint's schema. See [Validation errors](#validation-errors). | | `429` | `RATE_LIMIT_EXCEEDED` | The project exceeded its rate limit. See [Rate Limiting](/api-reference/rate-limiting). | | `500` | `INTERNAL_SERVER_ERROR` | The request failed on our side. | ## Validation errors A request that does not match the endpoint's schema returns `422` with code `VALIDATION_FAILED`. The `detail` object says what failed: * `on` names the part of the request that failed: `body`, `query`, `params` or `headers`. * `errors` lists each problem. Every entry has a `path` to the field and a `message`. Most entries also carry a `code` for the rule that failed. For example, sending `cardType` instead of `type` to `POST /cards` returns: ```json theme={null} { "error": { "code": "VALIDATION_FAILED", "message": "Validation failed", "detail": { "on": "body", "errors": [ { "path": "type", "message": "Invalid option: expected one of \"VIRTUAL\"|\"PHYSICAL\"", "code": "invalid_value" } ] } } } ``` The API ignores fields it does not recognize. A misspelled field therefore shows up as an error on the required field it was meant to fill. Nested fields use dot-separated paths, such as `config.maxAmount`. A few checks run after schema validation, such as decoding a pagination `cursor`. These also return `VALIDATION_FAILED`, with a shorter `detail` in place of `errors`. ## Handling Errors Use the `code` field for programmatic error handling. Branch on `code` rather than the HTTP status, because several errors can share a status. For example, a few endpoints use `422` for their own business-rule errors alongside `VALIDATION_FAILED`. ```javascript theme={null} if (response.error.code === 'USER_NOT_FOUND') { // Handle missing user } else if (response.error.code === 'PHONE_NUMBER_ALREADY_EXISTS') { // Handle duplicate phone } ``` ## Retries For `5xx` errors, retry with exponential backoff. For `429`, wait for the `Retry-After` header. For other `4xx` errors, fix the request before retrying. # Create fee override Source: https://docs.reap.global/api-reference/fees/create-fee-override /api-reference/openapi.json post /fees/ Creates an account-level override for one fee type. An account has at most one override per fee type; creating a second returns a conflict. Project defaults cannot be created - every project already has one row per fee type. # Delete fee override Source: https://docs.reap.global/api-reference/fees/delete-fee-override /api-reference/openapi.json delete /fees/{id} Removes an account-level override so the account falls back to the project default. Project defaults cannot be deleted. # Get fee Source: https://docs.reap.global/api-reference/fees/get-fee /api-reference/openapi.json get /fees/{id} Retrieves a fee row by ID. # List effective fees Source: https://docs.reap.global/api-reference/fees/list-effective-fees /api-reference/openapi.json get /fees/effective Returns the fee an account is charged for every fee type: its own override where one exists, otherwise the project default. Each entry names the row it comes from. # List fees Source: https://docs.reap.global/api-reference/fees/list-fees /api-reference/openapi.json get /fees/ Returns a paginated list of your fee rows - project defaults and account overrides - optionally filtered by type or scope. # Update fee value Source: https://docs.reap.global/api-reference/fees/update-fee-value /api-reference/openapi.json put /fees/{id} Replaces the value of a fee row and bumps its version. Transactions authorized from then on are priced with the new value; settled transactions keep the version that priced them. Project defaults can only be updated from the dashboard. # Fiat deposit created Source: https://docs.reap.global/api-reference/fiat-deposit-created /webhooks/openapi.json webhook FIAT_DEPOSIT_CREATED Fired when an inbound bank transfer is credited to an account. The deposit is already settled when the event is sent - a fiat transfer is reported to Reap only once the receiving bank has reconciled it. # Get bank details Source: https://docs.reap.global/api-reference/fiat-deposits/get-bank-details /api-reference/openapi.json get /fiat-deposits/bank-details Retrieves where to send a bank transfer to fund the master collateral account. The project must be enabled for bank transfer funding, or the response is `FIAT_DEPOSITS_NOT_ENABLED`. Always quote the returned `reference` on the transfer, and read these details fresh before each transfer rather than storing them - an account can be closed by the bank, and a transfer to a closed account has to be recalled. Sandbox returns placeholder details for an account no wire can reach; simulate a deposit instead of sending one. # Get fiat deposit Source: https://docs.reap.global/api-reference/fiat-deposits/get-fiat-deposit /api-reference/openapi.json get /fiat-deposits/{id} Retrieves a fiat deposit credited to an account, including the amount as received by the bank and the amount credited. # Get alert Source: https://docs.reap.global/api-reference/fraud-alerts/get-alert /api-reference/openapi.json get /fraud-alerts/{id} Retrieves a single fraud alert by its unique identifier. # List alerts Source: https://docs.reap.global/api-reference/fraud-alerts/list-alerts /api-reference/openapi.json get /fraud-alerts/ Returns a paginated list of fraud alerts, newest transaction first. Filter by status, card, transaction, origin, or transaction time range. No status default is applied. # Report fraud Source: https://docs.reap.global/api-reference/fraud-alerts/report-fraud /api-reference/openapi.json post /fraud-alerts/ Files a fraud report against a past card transaction. Always confirms the transaction as fraud and blocks the card. If a pending detected alert already exists for the same transaction and is still inside the response window, this delegates to confirm. Partners learn of the block via `CARD_STATUS_UPDATED` with `blockReason: FRAUD_ALERT_CONFIRMED`. # Respond to alert Source: https://docs.reap.global/api-reference/fraud-alerts/respond-to-alert /api-reference/openapi.json post /fraud-alerts/{id}/respond Confirms or declines a pending detected fraud alert. Confirming blocks the card (`CARD_STATUS_UPDATED` with `blockReason: FRAUD_ALERT_CONFIRMED`); declining leaves the card alone. `type` is required when confirming. # Idempotency Source: https://docs.reap.global/api-reference/idempotency Safely retry POST requests with the Idempotency-Key header. Send a unique `Idempotency-Key` header on any POST request and you can safely retry on failure. The server caches the first response (status code and body) and replays it on retries. Keys expire after 24 hours. The header is **required** on the endpoints that create money movement or a chargeable resource - creating an account, a card, a posting, a dispute, or an API key, and submitting a card shipment. Omitting it there returns `422`. Every other POST accepts it and honors it when present. The API reference marks the header as required or optional on each endpoint. ```bash theme={null} curl -X POST https://api.reap.global/cards \ -H "Authorization: Bearer $REAP_API_KEY" \ -H "Reap-Version: 2026-01-01" \ -H "Idempotency-Key: 7e7f1a90-3e0e-4a7e-bd2c-9b3a3c2d8e1f" \ -H "Content-Type: application/json" \ -d '{ "userId": "...", "accountId": "...", "type": "VIRTUAL" }' ``` Generate one key per logical operation. Use a UUIDv4 or any high-entropy string up to 255 characters. Keep the same key across every retry - never generate a new key on each attempt. *** ## Behavior | Situation | Response | | - | - | | First request | Executes and caches the response. | | Retry, same key, same body | Returns the cached response. Includes `Idempotent-Replayed: true` response header. | | Retry, same key, different body | `400 IDEMPOTENT_PARAMETER_MISMATCH` - use a new key for a different request. | | Two concurrent requests, same key | `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` - retry with backoff. | | Key reused after 24 hours | Treated as a fresh request. | All outcomes are cached, including `4xx` and `5xx`. If the first attempt returned a business error, retries replay that error. Fix the input and use a new key to try again. `401`, `422`, and `429` responses are not cached and are safe to retry with the same key. *** ## Retry pattern ```ts theme={null} const idempotencyKey = crypto.randomUUID(); // generate once, reuse on retries async function createCardWithRetry(body) { for (let attempt = 0; attempt < 5; attempt++) { try { const res = await fetch('https://prod.api.reap.global/cards', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Reap-Version': '2026-01-01', 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); if (res.status === 429 || res.status >= 500) { await sleep(2 ** attempt * 1000); continue; } return await res.json(); } catch { await sleep(2 ** attempt * 1000); // network error: safe to retry same key } } } ``` *** ## Recommendations * **Retry on network errors and `429`** with the same key. These are the only cases where the original request may not have reached the server. * **Use a new key after a `4xx` business error.** The cached error replays on retries; only a new key triggers a fresh attempt. * **Persist the key before sending** if you need guaranteed retry safety after a client crash. # Overview Source: https://docs.reap.global/api-reference/overview Reap API reference. REST API with JSON request and response bodies. ## Base URLs | Environment | Base URL | | - | - | | Singapore sandbox | `https://sg.sandbox.api.reap.global` | | Singapore production | `https://sg.prod.api.reap.global` | | Mexico sandbox | `https://mx.sandbox.api.reap.global` | | Mexico production | `https://mx.prod.api.reap.global` | `https://sandbox.api.reap.global` and `https://prod.api.reap.global` remain valid aliases for Singapore. Sandbox and production are fully isolated. API keys, webhooks, and data live in one environment and never cross. ## Required Headers Every request must include: | Header | Description | | - | - | | `Authorization` | Your API key as a Bearer token: `Bearer YOUR_API_KEY` | | `Reap-Version` | API version in YYYY-MM-DD format. Current version: `2025-02-14` | | `Content-Type` | `application/json` for requests with a body | ## Example Request ```bash theme={null} curl https://sg.sandbox.api.reap.global/users \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Reap-Version: 2025-02-14" ``` ## Numerical Values Monetary and rate fields use different JSON types depending on what they represent, chosen so partners never silently lose precision: | Field shape | JSON type | Notes | | - | - | - | | Fiat amounts (USD, EUR, ...) | `number` | At the currency's native precision (2 decimal places for most fiat). Safe to read as a JS `number`. | | Crypto and virtual asset amounts | `string` | Decimal string padded to the asset's `decimals` (e.g. `"1.000000"` for 1 USDC). Lossless across all values; compare and arithmetic via a big-decimal library. | | Exchange and conversion rates | `number` | 6 decimal places. | A field's individual description always wins when it differs. The same conventions are followed on input bodies. # Create policy Source: https://docs.reap.global/api-reference/policies/create-policy /api-reference/openapi.json post /policies/ Creates a policy. The accepted configuration depends on the policy type. # Disable policy Source: https://docs.reap.global/api-reference/policies/disable-policy /api-reference/openapi.json post /policies/{id}/disable Disables a policy so it stops applying to new transactions. Recorded usage is retained. Disabling an already-disabled policy returns it unchanged. # Enable policy Source: https://docs.reap.global/api-reference/policies/enable-policy /api-reference/openapi.json post /policies/{id}/enable Enables a policy so it applies to transactions again. Enabling an already-enabled policy returns it unchanged. # Get policy Source: https://docs.reap.global/api-reference/policies/get-policy /api-reference/openapi.json get /policies/{id} Retrieves a policy by ID. # List effective policies Source: https://docs.reap.global/api-reference/policies/list-effective-policies /api-reference/openapi.json get /policies/effective Returns the policies in force for a card, cardholder, or your program, resolved up the attachment chain (a card inherits its cardholder and program policies). Limits that track a running total over a window include their current usage and remaining headroom, so you can show how much is left. Only active policies are included. # List policies Source: https://docs.reap.global/api-reference/policies/list-policies /api-reference/openapi.json get /policies/ Returns a paginated list of your policies, optionally filtered by type, status, or scope. By default only ACTIVE policies are returned; pass an explicit status filter to include disabled ones. # Rename policy Source: https://docs.reap.global/api-reference/policies/rename-policy /api-reference/openapi.json patch /policies/{id} Updates a policy label. # Replace policy configuration Source: https://docs.reap.global/api-reference/policies/replace-policy-configuration /api-reference/openapi.json put /policies/{id}/config Replaces the configuration of a policy. The `type` must match the existing policy. Properties that are fixed at creation cannot be changed. # Rate Limiting Source: https://docs.reap.global/api-reference/rate-limiting Per-project request limits, response headers, and 429 handling. The Reap API is shared infrastructure. To keep it reliable for everyone, requests are rate-limited per project on rolling per-second, per-minute, and per-day windows. Limits apply to the project as a whole, not per API key. *** ## Limits Most endpoints share the default limit. A small set of write endpoints have tighter limits based on the downstream operations they trigger. | Tier | Per second | Per minute | Per day | | - | - | - | - | | Default | 20 | 600 | 500,000 | | Card issuance | 10 | 100 | 5,000 | | Card operations | 15 | 250 | 10,000 | | Identity verification | 5 | 150 | 2,000 | | Account creation | 3 | 100 | 10,000 | | Withdrawals | 6 | 60 | 5,000 | | Tier | Per second | Per minute | Per day | | - | - | - | - | | Default | 10 | 150 | 10,000 | | Card issuance | 5 | 50 | 500 | | Card operations | 5 | 100 | 1,000 | | Identity verification | 2 | 30 | 300 | | Account creation | 2 | 50 | 2,000 | | Withdrawals | 6 | 30 | 500 | **Card issuance** covers: * `POST /cards` * `POST /card-shipments/:id/submit` **Card operations** covers: * `POST /cards/:id/freeze` * `POST /cards/:id/unfreeze` * `POST /cards/:id/block` * `POST /cards/:id/unblock` * `DELETE /cards/:id` * `POST /cards/:id/reveal` * `POST /cards/:id/reveal-pan` * `PUT /cards/:id/pin` * `POST /cards/:id/activate` * `GET /cards/:id/activation-code` * `POST /cards/:id/push-provisioning` * `PUT /cards/:id/3ds-challenge-method` * `POST /card-shipments` * `POST /disputes` **Identity verification** covers: * `POST /companies` * `POST /companies/:id/applications` * `POST /users/:id/application` * `POST /users/:id/application/documents` **Account creation** covers: * `POST /accounts` Provisioning a wallet-backed account creates keys and registers deposit addresses with our chain infrastructure, so it is paced more tightly than plain reads and writes. **Withdrawals** covers: * `POST /crypto-withdrawals` * `GET /crypto-withdrawals/:id/payload` * `POST /crypto-withdrawals/:id/sign` These limits are lower than the rest because each call reserves scarce chain and custody resources. The per-second allowance leaves room for one withdrawal's three calls back to back. Reading a withdrawal or cancelling one is not affected. All other endpoints fall under the default limit. *** ## Response headers Every response includes the following headers so you can track usage without making extra requests. | Header | Description | | - | - | | `RateLimit-Limit` | Maximum requests allowed in the current window | | `RateLimit-Remaining` | Requests remaining in the current window | | `RateLimit-Reset` | Unix timestamp (seconds) when the current window resets | | `RateLimit-Policy` | Identifier for the limit applied to this request | Legacy `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers are also returned for compatibility with older HTTP clients. *** ## Exceeding the limit When a request exceeds your limit, the API responds with `429 Too Many Requests`: * The error body uses `code: RATE_LIMIT_EXCEEDED`. * A `Retry-After` header is set to the number of seconds to wait before retrying. ```json theme={null} { "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. See the Retry-After header for when to retry." } } ``` Wait at least the number of seconds in `Retry-After` before retrying. Earlier retries will simply receive another `429` until the window resets. *** ## Best practices * **Read the headers.** Track `RateLimit-Remaining` to back off proactively before the limit hits zero. * **Honor `Retry-After`.** When you receive a `429`, wait at least the indicated number of seconds before retrying. * **Use exponential backoff with jitter.** If you must retry beyond the first attempt, increase the wait between retries and add randomness to avoid thundering herds. * **Spread out bulk operations.** When onboarding many users or issuing many cards, pace requests against the per-minute and per-day limits rather than firing them as fast as possible. * **Need higher limits?** Contact the Reap team if your use case genuinely requires more headroom. We tune limits per project where it makes sense. # Reset company application Source: https://docs.reap.global/api-reference/simulation/reset-company-application /api-reference/openapi.json post /simulation/companies/{companyId}/reset Rewinds a Universal KYB sandbox company application to pending submission, keeping the pack and uploaded documents. A submitted company returns to `PENDING` and can be submitted again. An `ACTIVE` or `RESTRICTED` company returns `SIMULATION_INVALID_STATE`. Returns `204`. # Simulate 3DS authorization Source: https://docs.reap.global/api-reference/simulation/simulate-3ds-authorization /api-reference/openapi.json post /simulation/card-transactions/authorization-with-3ds Simulates a WEBHOOK 3DS challenge for a card authorization. Returns a pending `ThreeDsChallenge` and delivers `CARD_3DS_CHALLENGE_CREATED` without creating a transaction. After `POST /card-3ds-challenges/{id}/respond` with `approve: true`, authorization completes and `CARD_TRANSACTION_CREATED` is delivered. With `approve: false`, the challenge is rejected and no transaction is created (mirrors production). The card must use `3dsChallengeMethod: WEBHOOK`. # Simulate a fiat deposit Source: https://docs.reap.global/api-reference/simulation/simulate-a-fiat-deposit /api-reference/openapi.json post /simulation/fiat-deposits Simulates a bank transfer credited to the project's master collateral account. The deposit is recorded as already settled, the master balance is credited, and `FIAT_DEPOSIT_CREATED` is delivered, matching what a reconciled transfer does in production. Only available to Program-Funded projects, which are the ones that have a master collateral account. `currency` must be one the project accepts top-ups in, which is the project's billing currency. # Simulate account status Source: https://docs.reap.global/api-reference/simulation/simulate-account-status /api-reference/openapi.json post /simulation/accounts/{id}/status Simulates an account status change between `ACTIVE` and `RESTRICTED`. `RESTRICTED` restricts the account and its deposit addresses, and declines every authorization on its cards; the cards themselves keep the status they held. `ACTIVE` lifts the restriction and leaves card statuses untouched. # Simulate authorization Source: https://docs.reap.global/api-reference/simulation/simulate-authorization /api-reference/openapi.json post /simulation/card-transactions/authorization Simulates a card authorization event. Creates a new `PENDING` transaction, or performs incremental authorization on an existing one. Checks the account balance and returns 400 if insufficient funds. Pass `triggerFraudAlert: true` to also raise a detected fraud alert against the charge. # Simulate card status Source: https://docs.reap.global/api-reference/simulation/simulate-card-status /api-reference/openapi.json post /simulation/cards/{id}/status Simulates a card status change. `ACTIVE` unfreezes a frozen or blocked card. `FROZEN` freezes an active card. `BLOCKED` blocks an active or frozen card. `EXPIRED` expires a card. # Simulate clearing Source: https://docs.reap.global/api-reference/simulation/simulate-clearing /api-reference/openapi.json post /simulation/card-transactions/clearing Simulates a clearing (settlement) event. Pass `transactionId` to clear an existing authorization, or omit to simulate offline/direct clearing. # Simulate company status Source: https://docs.reap.global/api-reference/simulation/simulate-company-status /api-reference/openapi.json post /simulation/companies/{companyId}/status Simulates a company KYB outcome. Returns `204`. Observe the change via `GET /companies/{companyId}` and `COMPANY_STATUS_UPDATED`. On Universal KYB projects the company application must be submitted first; otherwise the request returns `SIMULATION_INVALID_STATE`. An incomplete application returns `COMPANY_APPLICATION_INCOMPLETE`. # Simulate decline Source: https://docs.reap.global/api-reference/simulation/simulate-decline /api-reference/openapi.json post /simulation/card-transactions/decline Simulates a declined card transaction. Creates a new `DECLINED` transaction, or declines an existing `PENDING` one. # Simulate refund Source: https://docs.reap.global/api-reference/simulation/simulate-refund /api-reference/openapi.json post /simulation/card-transactions/refund Simulates a refund event. Pass `transactionId` to refund an existing `CLEARED` transaction, or omit to simulate a standalone refund with no matching purchase (e.g., tax refund). # Simulate reversal Source: https://docs.reap.global/api-reference/simulation/simulate-reversal /api-reference/openapi.json post /simulation/card-transactions/reversal Simulates a reversal (partial or full) of an existing `PENDING` authorization. A full reversal transitions the transaction to `VOID`. # Simulate shipment status Source: https://docs.reap.global/api-reference/simulation/simulate-shipment-status /api-reference/openapi.json post /simulation/card-shipments/{id}/status Simulates a card shipment status change in sandbox. Drives production and shipping progress for a submitted shipment. Returns `204`; observe the change via `CARD_SHIPMENT_STATUS_UPDATED`. # Simulate user application status Source: https://docs.reap.global/api-reference/simulation/simulate-user-application-status /api-reference/openapi.json post /simulation/users/{userId}/application Simulates a user application (KYC) status change. The status update is processed asynchronously via an internal webhook pipeline, so the user status may not reflect the change immediately after this call returns. Clients should poll `GET /users/{userId}` until the desired status is observed. # Statement status updated Source: https://docs.reap.global/api-reference/statement-status-updated /webhooks/openapi.json webhook STATEMENT_STATUS_UPDATED Fired when a requested statement finishes: `GENERATED` when its file is ready to download via `GET /statements/{id}/download`, or `FAILED` when it could not be produced. # Download statement Source: https://docs.reap.global/api-reference/statements/download-statement /api-reference/openapi.json get /statements/{id}/download Serves a `GENERATED` statement file. Responds `302` to a short-lived URL that serves the file directly, so a plain HTTP client that follows redirects downloads it in one call; fetch that URL without your API credentials. Send `Accept: application/json` to receive the URL and its expiry as a body instead. A statement that is `PENDING` or `FAILED` has no file and responds `STATEMENT_NOT_READY`. # Get statement Source: https://docs.reap.global/api-reference/statements/get-statement /api-reference/openapi.json get /statements/{id} Retrieves a statement by id. # List statements Source: https://docs.reap.global/api-reference/statements/list-statements /api-reference/openapi.json get /statements/ Lists statements requested for the project, most recently created first, including those Reap produces on a schedule. # Request statement Source: https://docs.reap.global/api-reference/statements/request-statement /api-reference/openapi.json post /statements/ Requests a statement over a calendar-day period. The statement is returned `PENDING` and its file is produced asynchronously; a `STATEMENT_STATUS_UPDATED` webhook follows once it is `GENERATED` or `FAILED`. The period may span at most 31 days, must start on or after 2026-06-01, and must end on a day whose rows are final: yesterday from 10:00 UTC, otherwise the day before. At most 5 statements may be pending per project at once. # User application status updated Source: https://docs.reap.global/api-reference/user-application-status-updated /webhooks/openapi.json webhook USER_APPLICATION_STATUS_UPDATED Fired when a user application status changes (e.g., KYC approved, rejected, retry required). # Advance the user application Source: https://docs.reap.global/api-reference/users/advance-the-user-application /api-reference/openapi.json post /users/{id}/application Advances the user KYC application. Only valid while the application is in a pending state. Which verification method applies is fixed by your project configuration rather than chosen per request, and determines the request you send. With no body (Reap-run verification), Reap returns a provider SDK token in `nextAction` for the client to complete and approval arrives later via webhook. For Sumsub Token Sharing, a share token is ingested and the user moves to `IN_REVIEW`; the final decision arrives on the same compliance webhook as Managed KYC. For a full verified data pack, the response `nextAction` lists the supporting documents to upload via `POST /users/:id/application/documents`. Send an `Idempotency-Key` to safely retry without re-consuming a single-use share token. # Create user Source: https://docs.reap.global/api-reference/users/create-user /api-reference/openapi.json post /users/ Creates a new user with the provided details. Phone number must be unique within the project, as is `externalId` when supplied. In `CORPORATE` mode, `companyId` is required. # Delete user Source: https://docs.reap.global/api-reference/users/delete-user /api-reference/openapi.json delete /users/{id} Deletes a user, deletes every card they hold, and restricts their accounts. Card deletion is permanent. The accounts stay open because they can still hold value and card debt to settle, but a restricted account authorizes nothing. # Get user Source: https://docs.reap.global/api-reference/users/get-user /api-reference/openapi.json get /users/{id} Retrieves a single user by their unique identifier. # List users Source: https://docs.reap.global/api-reference/users/list-users /api-reference/openapi.json get /users/ Retrieves a paginated list of users. Use cursor-based pagination by passing the `nextCursor` value from the response to get subsequent pages. Use `externalId` to resolve your own identifier back to a Reap user. In `CORPORATE` mode, use `companyId` to filter users by company. # Update user email Source: https://docs.reap.global/api-reference/users/update-user-email /api-reference/openapi.json put /users/{id}/email Updates the email address for a user. Reserved or non-routable top-level domains are rejected. # Update user phone Source: https://docs.reap.global/api-reference/users/update-user-phone /api-reference/openapi.json put /users/{id}/phone Updates the phone number for a user. Phone number must be in E.164 format and unique within the project. # Upload application documents Source: https://docs.reap.global/api-reference/users/upload-application-documents /api-reference/openapi.json post /users/{id}/application/documents Uploads one supporting document type for a user whose application is awaiting documents. Set `documentType` to one of the pending documents from the advance response's `nextAction`, and attach its file(s) as `files` (repeat for multi-page or front/back). Upload one type per request, across as many requests as there are pending documents; each response reports the remaining documents in `nextAction`. When the last required document is received the application advances to review. # Versioning & Compatibility Source: https://docs.reap.global/api-reference/versioning How the API evolves, what counts as a backwards-compatible change, and how to build a resilient integration. Every request carries a `Reap-Version` header (see [Overview](/api-reference/overview)). Backwards-incompatible changes ship under a new version, so your integration keeps working until you choose to upgrade. Within a single version we continuously ship backwards-compatible improvements, and your integration should be built to absorb them without changes. ## Backwards-compatible changes The following can happen at any time, within your current version, and are **not** considered breaking. Build your integration to tolerate them: * Adding new API endpoints and resources. * Adding new optional request parameters and request body fields. * Adding new fields to existing responses and webhook payloads. * Adding new values to an existing enum (for example a new policy `type` or decline reason `code`). * Adding a new variant to a discriminated union (for example a new policy `type` with its own `config` shape). * Adding new webhook event types. * Adding new, more specific error codes, as long as the condition could not previously be resolved by your code at runtime. * Changing the length or format of opaque identifiers (treat all IDs as variable-length strings). ## Breaking changes These ship only under a new `Reap-Version`: * Removing or renaming a field, parameter, or endpoint. * Changing the type of an existing field, or the meaning of an existing value. * Removing a value from an enum, or changing what an existing value means. * Making a previously optional field or parameter required. # Create posting Source: https://docs.reap.global/api-reference/virtual-asset-postings/create-posting /api-reference/openapi.json post /postings/ Atomically applies a posting against the target account's virtual asset balances. See the `type` field for variant semantics. For `SETTLEMENT`, the value applied to the outstanding card debt is returned as `settledAmount`, with the per-entry rate used reflected in `entries[].rateSnapshot`. # Get posting Source: https://docs.reap.global/api-reference/virtual-asset-postings/get-posting /api-reference/openapi.json get /postings/{postingId} Client-driven postings (deposit, withdrawal, settlement) against an account's virtual asset balances. # Create virtual asset Source: https://docs.reap.global/api-reference/virtual-assets/create-virtual-asset /api-reference/openapi.json post /virtual-assets/ Defines a new virtual asset for the project. Only available when virtual assets are enabled for the project. For `FIXED` rate source, an initial rate must be provided. For `HTTP` rate source, an endpoint URL is required. # Disable virtual asset Source: https://docs.reap.global/api-reference/virtual-assets/disable-virtual-asset /api-reference/openapi.json post /virtual-assets/{id}/disable Permanently disable a virtual asset. One-way operation: disabled assets cannot be reactivated. Existing ledger balances remain but are excluded from balance computation and no new allocations are allowed. Idempotent: re-disabling an already-disabled asset is a no-op. # Get virtual asset Source: https://docs.reap.global/api-reference/virtual-assets/get-virtual-asset /api-reference/openapi.json get /virtual-assets/{id} Retrieves a virtual asset by ID, including the latest known rate. # List virtual assets Source: https://docs.reap.global/api-reference/virtual-assets/list-virtual-assets /api-reference/openapi.json get /virtual-assets/ Returns all virtual assets defined for the project. # Set rate for virtual asset Source: https://docs.reap.global/api-reference/virtual-assets/set-rate-for-virtual-asset /api-reference/openapi.json put /virtual-assets/{id}/rate Sets a new exchange rate (in the project billing currency) for a virtual asset with `FIXED` rate source. Not available for `HTTP` rate source assets. # Update virtual asset Source: https://docs.reap.global/api-reference/virtual-assets/update-virtual-asset /api-reference/openapi.json patch /virtual-assets/{id} Update mutable fields (`name`, `httpEndpoint`). To disable the asset, use the dedicated `POST /:id/disable` endpoint. # Create webhook endpoint Source: https://docs.reap.global/api-reference/webhooks/create-webhook-endpoint /api-reference/openapi.json post /webhooks/ Registers a webhook endpoint for the project. The response includes a one-time `signingSecret` that the partner must store to verify incoming webhook signatures. `mode` defaults to `NOTIFICATION` (async event delivery), of which a maximum of 5 ACTIVE per project is enforced - disable an existing one to make room. Pass `REQUEST` to register the project's single synchronous authorization endpoint (exactly one per project, rotate-only, available only on projects with `EXTERNAL` authorization mode). # Disable webhook endpoint Source: https://docs.reap.global/api-reference/webhooks/disable-webhook-endpoint /api-reference/openapi.json post /webhooks/{id}/disable Permanently disable an async webhook endpoint. One-way operation: disabled endpoints cannot be reactivated. Create a new webhook to resume delivery to the same URL. Idempotent: re-disabling an already-disabled webhook is a no-op. `REQUEST`-mode endpoints are synchronous and rotate-only; they cannot be disabled. # Get webhook endpoint Source: https://docs.reap.global/api-reference/webhooks/get-webhook-endpoint /api-reference/openapi.json get /webhooks/{id} Webhook endpoint management. Configure HTTPS URLs that receive signed POST notifications when events occur in your project. Signing secrets are returned exactly once at create and at rotate-secret time - store them securely. # List webhook endpoints Source: https://docs.reap.global/api-reference/webhooks/list-webhook-endpoints /api-reference/openapi.json get /webhooks/ Returns the project's webhook endpoints. By default only `ACTIVE` endpoints are returned; pass the `status` query parameter to widen or change the filter (e.g. `ACTIVE,DISABLED`). # Rotate signing secret Source: https://docs.reap.global/api-reference/webhooks/rotate-signing-secret /api-reference/openapi.json post /webhooks/{id}/rotate-secret Mints a new HMAC signing secret and returns it exactly once. The previous secret is invalidated immediately; in-flight deliveries signed with the old secret will fail verification on your endpoint and be retried automatically by the platform. # Update webhook endpoint Source: https://docs.reap.global/api-reference/webhooks/update-webhook-endpoint /api-reference/openapi.json patch /webhooks/{id} Update mutable fields (`name`, `url`). To rotate the signing secret, use `POST /:id/rotate-secret`. To disable, use `POST /:id/disable`. # 3D Secure Source: https://docs.reap.global/cards/3d-secure Authenticate cardholders during online card payments. 3D Secure (3DS) is an authentication protocol used by Visa, Mastercard, and other networks to verify the cardholder during online card payments. When a merchant requests a 3DS check, the issuer must confirm the person making the purchase is really the cardholder. This protects against card-not-present fraud and is required by regulators like PSD2 in many regions. Reap handles 3DS for you. The only choice you need to make is how the cardholder proves their identity when a challenge is raised. *** ## Verification methods Each card carries a `3dsChallengeMethod` setting that determines how challenges are resolved. You can switch the method per card at any time. | Method | Who verifies the cardholder | Integration work | | - | - | - | | `SMS` (default) | Reap sends a one-time code to the cardholder's phone and verifies the response. | None. | | `WEBHOOK` | Reap forwards the challenge to your backend. You authenticate the cardholder in your app (biometric, in-app PIN, push approval) and call back. | Subscribe to a webhook and respond within 5 minutes. | Use `SMS` if you want zero integration. Use `WEBHOOK` if you want to drive the verification step yourself, typically because your app already has a strong, in-app authentication flow you would rather reuse. *** ## Configuring the method Set `3dsChallengeMethod` when [creating a card](/api-reference/cards/create-card). If omitted, the card defaults to `SMS`. Switch later by calling [Update 3DS challenge method](/api-reference/cards/update-3ds-challenge-method). The change takes effect for subsequent transactions. The current setting is exposed on the card resource as `3dsChallengeMethod`. *** ## SMS flow When a merchant triggers a 3DS check: 1. The card network requests authentication from Reap. 2. Reap sends a one-time code to the cardholder's phone number on file. 3. The cardholder enters the code on the merchant's 3DS page. 4. Reap verifies the code and confirms (or denies) the authentication with the network. You receive a [`CARD_TRANSACTION_CREATED`](/api-reference/card-transaction-created) webhook for the resulting authorization, with `channel: ECOMMERCE`. There are no extra integration steps. The phone number used for SMS comes from the user record. Keep it up to date so challenges reach the cardholder. A wrong or stale number means the cardholder cannot complete the OTP step and the transaction is declined. *** ## Webhook flow Use the `WEBHOOK` method when you want to authenticate the cardholder in your own app instead of relying on SMS OTP. Listen for [`CARD_3DS_CHALLENGE_CREATED`](/api-reference/3ds-challenge-created). The payload includes the challenge ID, card ID, merchant name and country, transaction amount and currency, and an `expiresAt` timestamp. Show the merchant name and amount in your app and verify the cardholder using your preferred method (biometric, PIN, push approval, etc.). Surface the time remaining so the cardholder knows the window is finite. Call [Respond to 3DS challenge](/api-reference/cards/respond-to-3ds-challenge) with `approve: true` to approve, or `approve: false` to reject. You must respond within 5 minutes of the challenge being created. Missing the window expires the challenge and the purchase fails at the merchant. On **approve**, the card network proceeds with authorization and you receive a [`CARD_TRANSACTION_CREATED`](/api-reference/card-transaction-created) webhook. On **reject** (or expiry), no authorization is sent, so you will **not** receive a transaction webhook. ```mermaid theme={null} sequenceDiagram participant Cardholder participant Merchant participant Reap participant Backend as Your backend Cardholder->>Merchant: Pay with card Merchant->>Reap: 3DS authentication request Reap->>Backend: CARD_3DS_CHALLENGE_CREATED Backend->>Cardholder: Authenticate (biometric / PIN / push) Cardholder->>Backend: Approves Backend->>Reap: POST /card-3ds-challenges/:id/respond Reap->>Merchant: Authentication result Merchant->>Cardholder: Payment outcome ``` ### Challenge status A challenge moves through one of four states. Use [Get 3DS challenge](/api-reference/cards/get-3ds-challenge) to fetch the latest state by ID at any time. | Status | Meaning | | - | - | | `PENDING` | Awaiting your response | | `APPROVED` | You approved the challenge | | `REJECTED` | You rejected the challenge | | `EXPIRED` | No response was received within the 5-minute window. The transaction was declined | *** ## Best practices * **Show context to the cardholder.** Surface the merchant name, country, and amount from the challenge payload. This helps the cardholder spot fraudulent purchases before approving. * **Respect the 5-minute window.** Treat `expiresAt` as authoritative. If your authentication step might block (push notification, deep link, app cold start), make sure the cardholder can complete it well before the timeout. * **Start with SMS.** Cards default to `SMS`, so 3DS works out of the box. Switch individual cards to `WEBHOOK` once your in-app authentication flow is ready. * **Handle rejects.** A rejected or expired challenge means the purchase fails at the merchant and you will **not** receive a `CARD_TRANSACTION_CREATED` webhook. Tell the cardholder so they can retry if needed. # Encrypted Retrieval Source: https://docs.reap.global/cards/card-details/encrypted-retrieval Retrieve card PAN, CVV, and expiry as an encrypted payload for PCI-compliant partners. Retrieve the full card details (PAN, CVV, expiry date) as an encrypted payload that only you can decrypt. You generate the RSA key pair and register the public half with Reap; the details are encrypted with it and you decrypt them with the private half on your backend. **Approval required.** This capability is enabled per program by Reap. Your system handles raw card data after decryption, so **your environment must be PCI DSS compliant**, and Reap enables it only after reviewing your compliance status. Projects without it receive `403 PAN_REVEAL_NOT_ENABLED`. You are responsible for the decrypted values: never store, log, cache, or transmit them beyond the immediate use. Most integrations do not need this. To show card details to the cardholder, use [Secure Display](/cards/card-details/secure-display) instead - see [Choosing an approach](/cards/card-details/overview#choosing-an-approach). *** ## Prerequisites * The capability is enabled for your project by Reap. Talk to your account manager to request it; Reap reviews your PCI DSS compliance status before enabling. Calls from projects without it receive `403 PAN_REVEAL_NOT_ENABLED`. * You hold the RSA private key: * **Production**: you generate the key pair (RSA, 2048 bits or larger) and send **only the public key** to your implementation manager, who registers it against your program. The private key never leaves your systems and Reap never holds a copy. * **Sandbox**: a single, publicly documented key pair is shared by all sandbox programs (see [Sandbox keys](#sandbox-keys)). It provides no confidentiality, sandbox cards are test cards. *** ## Request See [Reveal PAN](/api-reference/cards/reveal-pan) for the full endpoint reference. ```bash theme={null} curl -X POST https://sandbox.api.reap.global/cards/{cardId}/reveal-pan \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Reap-Version: 2025-02-14" ``` No request body. The card must exist, must not be in a terminal or inactive status, and its account must be active. *** ## Response ```json theme={null} { "encryptedData": "hEJ2X8QeZ...base64...", "encryption": "RSA_OAEP_SHA256" } ``` | Field | Description | | - | - | | `encryptedData` | Base64-encoded ciphertext of the card details JSON. | | `encryption` | Encryption scheme of `encryptedData`. Dispatch your decryption on this value; new schemes may be added later. | `encryption` is one of: | Value | Meaning | | - | - | | `RSA_OAEP_SHA256` | RSA-OAEP with SHA-256. Used for new keys. | | `RSA_OAEP_SHA1` | RSA-OAEP with SHA-1. | The scheme is a property of the public key registered for your program, so it stays fixed until that key is replaced. Read it from every response rather than hardcoding it, so a key rotation cannot break your decryption. Decrypting `encryptedData` yields a JSON document: ```json theme={null} { "pan": "5200000000001005", "cvv": "123", "expiryDate": "12/28" } ``` *** ## Decryption Base64-decode `encryptedData`, then decrypt with your RSA private key using OAEP padding and the hash named by `encryption`: SHA-256 for `RSA_OAEP_SHA256`, SHA-1 for `RSA_OAEP_SHA1`. ```bash theme={null} # Use rsa_oaep_md:sha1 when encryption is RSA_OAEP_SHA1 echo "$ENCRYPTED_DATA" | base64 -d > /tmp/blob.bin openssl pkeyutl -decrypt -inkey private-key.pem -in /tmp/blob.bin \ -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 ``` ```javascript theme={null} import { privateDecrypt, constants } from 'node:crypto'; // `response` is the parsed JSON body of POST /cards/{cardId}/reveal-pan, // `privateKeyPem` the private half of the key registered with Reap. const { encryptedData, encryption } = response; const OAEP_HASH_BY_SCHEME = { RSA_OAEP_SHA256: 'sha256', RSA_OAEP_SHA1: 'sha1', }; const cardDetails = JSON.parse( privateDecrypt( { key: privateKeyPem, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: OAEP_HASH_BY_SCHEME[encryption], }, Buffer.from(encryptedData, 'base64') ).toString('utf8') ); ``` Always decrypt on your backend. Never ship the private key to a browser or mobile client, and never proxy the decrypted details through systems outside your PCI scope. *** ## Errors | Status | Code | Meaning | | - | - | - | | 403 | `PAN_REVEAL_NOT_ENABLED` | The capability is not enabled for your project. | | 404 | `CARD_NOT_FOUND` | The card does not exist or has been deleted. | | 400 | `CARD_OPERATION_NOT_ALLOWED` | The card status does not permit revealing its details. | | 400 | `ACCOUNT_NOT_ACTIVE` | The account the card belongs to is not active. | *** ## Sandbox keys All sandbox programs share one RSA key pair. Use this private key to decrypt sandbox responses: ``` -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAzn88MgWItkxYeUt5jWeRBYIiQEUFxstgYJx8tYblZ55psnYd HwhsyoPDwG6eq0r60WFSEeP2QFqV1qceYuDNM2RziIaMMGYgYlCPjgYaiMda3wlX 0w6pMv/5VaJA6N0jcb2m5xBC3I7XqKkP3l9AfL5xCtWN71wmVA/WXVZQ755pC3YE A4IMoOcXs/+143HWZarX7wULhK55c8uAeA1q3ZmL7boLkzBZdMQM6Qlv9DDVcgUH dahk4MiibmOubXrOk12KAyEXZlLH8Sc3qTDQnYP60ZOz95Ffly2c5/6/vLEBP99y J9baXwgoBpQ7amA2o+X7v+eomV5CUItJITR0FwIDAQABAoIBAQDJvT529DcjOqU5 I0aGoobpJcEGnyhMlkb7PAcOZdLbj8Vdp809k0KN+3sUFj6HxMIUKws+FUNwjc9T nYOvQEbZjard1+1AH5ZF1sTZERd9R558xij6NpmRDMlwIwtdNSa6qXlUxus7hYYl 7b1mGBLtjDTuS8LV9WYwvCRGndyGq6489o5OHzkHrCSabPXhBWai9LLgYkjrF1v4 mo4LCCqJEjaLAdqOlUXLiM1DrDWYlGUqJhdqYmcdl85BBQjisfOrQoET5yKb+afP r8ZNyY3AFKhwmSl5wHNcu1aY7E8UCzLYBboXd1vaWsXzo28UrUEk6jAQFW3otd3r bUM+RhqBAoGBAP1r8fhSTh/DBaCU3CGPX6p3tRNhfWbpMt6nYVvuDVVWb46BvSmJ W2BHK4Me8W+Bx6TKFGFI9eBsa6mevAT1R4283c1ji2pMe6W9uuojAe2ei3nRIYIi eHrmMGLv/3EVO8ctn9OyevQuBXjEIcaNoxkTNwgln4mKonVwJctkDIlXAoGBANCZ EkO840ZRQV8rYjEYJrE7W6hP+7FdTc/O62Gx5g6Kv11M7WY0dGJ0V/deio5sgmmV +Hi4SYQEzG30VZS8QIzaXFOO3etuIbobf7HW7wP3sCsY0kYZpxGiu30luO3OOnvs MLZ6wkdaArxAn1CPpmC3VngPy09cUVjAmun4mPNBAoGADP0eXsrhJDGbtTpL+hBe J9+Q0Vh60+554/2SOOL/nfMbLqOprVpT66BaM5M/bqapM+IDn2Tc7U7BSeVP37Uu oK4gdCl4+M7Iu5r43I1EyQgKQAke/YuEv0/TBQ8l+YbMFI5bFb+dG6zMLP9od8PE q0FX3bwH7EXFw90mosES/2sCgYBKlcDTGZHGDb2rEq8MUM9c5ZVzTA35sBvnZtwA 1YQlxPSBnsTvm2fV4DWnzFUMcb37lNUADRy0KKcT1Z5YDSujcVw7wESXLoy+p6Bz 7R8PMxv5fOmKiReUlbQmHjvOQ8PjlWMhjCk1K/j4aL7OJl7eDA/N5A/U83ReoJeD exDtQQKBgQD51uzxVgwUZNLkJbXCp64TNqUXdvy8+PlhqD9zU9QSpdsf2F0xM4gX eF2r3FU8Ps9WD6SijD715A4o4/xVV7vUXKn11nVqeEkaEkn3KIZpFQiEdWQR2J9e vErFndT9x8gfBgnKj4o6XvqHHPOPoqAC2piaXkLMhQGupqF922bj+Q== -----END RSA PRIVATE KEY----- ``` *** ## Security obligations * Decrypt only on PCI DSS compliant backend systems. * Never log, persist, or cache the decrypted PAN, CVV, or expiry date. * Never expose the private key or decrypted details to client-side code. * Every retrieval is audited by Reap. # Overview Source: https://docs.reap.global/cards/card-details/overview Two ways to reveal a card's PAN, CVV, and expiry, and how to choose between them. A card's full details (PAN, CVV, expiry date) are sensitive data that Reap holds on your behalf. There are two ways to get at them, and the right one depends on whether the values need to reach your own systems. *** ## Choosing an approach | | [Secure Display](/cards/card-details/secure-display) | [Encrypted Retrieval](/cards/card-details/encrypted-retrieval) | | - | - | - | | **What you get** | A short-lived URL you render in an iframe | An RSA-encrypted payload you decrypt | | **Where details land** | Only in the cardholder's browser or app | On your backend, in plaintext after decryption | | **PCI DSS scope** | Stays with Reap | Moves to you | | **Availability** | Every program | Off by default, enabled after a compliance review | **Start with Secure Display.** It covers the common case: showing a cardholder their own card details in your app. Raw card data never enters your systems, so your PCI DSS obligations do not change. **Use Encrypted Retrieval only when the raw values must reach your backend** and your environment is already PCI DSS compliant. Reap reviews your compliance status before enabling it per program. Projects without it receive `403 PAN_REVEAL_NOT_ENABLED`. The two are not exclusive. A program approved for Encrypted Retrieval can still use Secure Display for its in-app reveal, and most do. *** ## What both have in common * **The card must be usable.** It has to exist, not be in a terminal or inactive status, and belong to an active account. * **Authenticate the cardholder first.** Neither approach checks who is asking beyond your API key, so verify the user's identity in your application before revealing anything. * **Treat the output as short-lived.** Do not cache, log, or persist reveal URLs or decrypted values. * **Every retrieval is audited** by Reap. # Secure Display Source: https://docs.reap.global/cards/card-details/secure-display Show card PAN, CVV, and expiry to cardholders in an iframe, without handling raw card data. Display sensitive card details (PAN, CVV, expiry date) to your users without handling raw card data. Reap API provides an iframe-based reveal flow that keeps your integration PCI-compliant. *** ## How it works Call [Create reveal session](/api-reference/cards/reveal-card-details) from your server. Reap returns a short-lived, single-use `revealUrl`. Pass the `revealUrl` to your client and render it as the `src` of an ` ``` ```tsx theme={null} function CardReveal({ revealUrl }: { revealUrl: string }) { return (