TuCOPRamp

Rules for AI agents

Read this before an AI coding agent writes or changes a TuCOP Ramp integration.

This page is for AI coding agents. Follow every rule here. When a rule and your own idea of the API disagree, the rule and the linked page win. Do not guess endpoints, fields or error codes: if a page does not list it, it does not exist.

Which page to read

TaskRead
Any taskAPI conventions, Errors, Authentication, Services
First call, test keysQuickstart, Sandbox
Sign a user in, create or link their party, KYCParties and sessions
Register a Bre-B key, check the holderDestinations
Pay a third party in COP from USDCPayments
Pay with the API key alone, from your own payments provider accountPay from your own payments provider account
Estimate a price before a quotePayments
Pay out USDC to the user's own accountWithdrawals
Receive COP as USDCDeposits
Receive eventsWebhooks
P2P orders, wallet on-ramp, wallet signaturesNot open to integrators: P2P is available only to TuCOP's own apps for now.
Exact request and response schemasThe API reference tab (openapi.yaml)

Hard rules

  1. Server side only. Call TuCOP Ramp from a backend. Never put an API key in browser or mobile code.
  2. Never log or commit an API key. Read it from an environment variable such as TUCOPRAMP_API_KEY. Keys match tcr_(live|test)_(sk|rk|pk)_[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{43}[0-9A-Za-z]{6}. Treat party session tokens (tps_...) and webhook secrets (whsec_...) the same way.
  3. Sandbox first. Build and test with a tcr_test_sk_ key against https://sandbox.api.ramp.tucop.xyz. Production is https://api.ramp.tucop.xyz and takes only tcr_live_sk_ keys. Never run automated tests against production.
  4. Services must be enabled. Before calling a route, read GET /consumer: consumer.services groups your services by category (on_off_ramp, p2p). A route your services do not open answers 403 product_not_enabled with required and category; do not work around it. A service of one category never opens the routes of the other: deposit does not open /p2p/onramp/*. pay and deposit are requested in the console; withdraw is turned on by TuCOP only. See Services.
  5. Idempotency-Key on every platform POST (all except /sessions/*). Use a new random value (a UUID) per operation. Reuse it only to retry that same operation with the same body.
  6. Use the paths without a version (/payments, not /v1/payments). Do not send a TuCOPRamp-Version header.
  7. Amounts are objects with a decimal string and an asset: { "amount": "200000.00", "asset": "COP" }. Use exactly the asset's decimals (COP 2, USDC 6, COPm 18). Never send JSON numbers or floats, and never parse amounts into a floating-point number.
  8. Ids are public ids with a type prefix (pty_, dst_, qte_, op_, wdr_, dep_, evt_, ...). Store them as opaque strings. Never build or parse them.
  9. Request bodies are strict. Send only the fields the page or the API reference lists. An unknown field answers 400 invalid_body.
  10. Branch on code only. Errors are application/problem+json. Never match on detail.
  11. Never retry a 4xx unchanged. Fix the request, and use a new Idempotency-Key. Retry a 5xx with backoff and the same key. After 429, or 409 idempotency_in_progress, wait for Retry-After.
  12. Verify every webhook over the raw body bytes, before any JSON parsing, with the endpoint's whsec_ secret. Reject a timestamp more than 5 minutes off. Store webhook-id and skip repeats. Events can arrive out of order and more than once.
  13. Pay in the fixed order: register the destination, wait until it is verified, show the payer the holder and confirm, quote, create the payment, send exactly deposit.amount. owner_name is optional on a third-party destination: without it the key is looked up and, after about a minute, holder says who holds it. Never confirm a holder the payer has not seen. GET /rates?amount_cop=... may come first to show an estimate; it is never the amount to send.
  14. A quote lasts 2 minutes and pays once. Never reuse one; ask for a new quote.
  15. Do not hard-code identity documents. Read GET /document-types. A party never uses TI.
  16. 15 to 30 minutes from deposit to pesos is normal. After the USDC land on-chain, the pesos usually reach the Bre-B key 15 to 30 minutes later (pay and withdraw). Most of that time a payment still reads awaiting_funds (the provider is detecting the deposit, about 15 minutes), then processing until completed. Neither is a failure: do not retry, do not cancel a payment you already funded and do not send the USDC again; wait for the webhook. Tell the user the window from settlement_time (min_minutes, max_minutes) on the payment, the withdrawal, the quote or the GET /rates estimate, instead of hard-coding it.
  17. Respect the rate limits. All your keys share one budget of 1,000 requests per minute on the platform API, and GET /rates has its own 120 per minute. A 429 carries Retry-After: wait that long. More keys do not mean more budget.
  18. A read-only key cannot write. A key created as read-only gets 403 api_key_scope_missing on any POST, PATCH, PUT or DELETE, including opening a party session. Use a full-access key for writes, and read-only keys for reporting.
  19. Never treat the 202 from POST /destinations as final. It always says pending. Store the destination again after it leaves pending: from GET /destinations/{id} after the destination.* webhook, by polling, or from GET /events. See Keep your copy in sync.

Do

  • Check GET /consumer with the key before anything else.
  • Open a party session for each user and send it in X-TuCOPRamp-Party-Session, unless TuCOP linked your own payments provider account.
  • Call DELETE /sessions/current when the user signs out.
  • Read GET /limits before you offer an amount to pay.
  • Use GET /rates?amount_cop=<whole pesos> to show the payer an estimate in USDC before the quote. What the payer sends is always the payment's deposit.amount (2 decimals). It equals the quote's source_amount; the estimate is not binding.
  • On 409 quote_expired from POST /payments, ask for a new quote and pay with it: the quote expired or the rate moved and it no longer pays the destination.
  • Cancel an unfunded payment with POST /payments/{id}/cancel.
  • Send each Bre-B key in the format of its type (see Key formats): a mobile number without +57, an alphanumeric key with its @. Check the format in your app; TuCOP checks it again.
  • Register a key with POST /destinations only when the user submits it, and keep its destination_id. Registering the same key again returns the same destination.
  • Follow payments with operation.updated webhooks, and fall back to GET /payments/{id} or GET /events. Follow withdrawal payouts with withdraw.drained, or GET /withdrawals/{id} (the list leaves payouts out).
  • Treat completed as "the money was sent": the provider paid it out through Bre-B, which is instant. Payments and withdrawal payouts use the same statuses (processing, completed, failed, refunded, canceled). Up to 30 minutes from deposit to completed is normal; do not resend.
  • Show links.kyc from GET /parties/me whenever it is not null, even after status.kyc is approved: the COP enablement can still need the person.
  • To stop a withdrawal address, call DELETE /withdrawals/{id} and stop showing the address. USDC sent to it afterwards is still paid out to its Bre-B key. POST /withdrawals with the same destination re-enables the same address.
  • Manage webhook endpoints in the console (Avisos), up to 10 per environment.
  • Answer 2xx to webhook event types you do not handle, and ignore response fields you do not know.
  • Send a new X-Request-Id (a UUID) on each request and log it, or log the X-Request-Id TuCOP returns. Include it (or request_id from the error body) when you report a problem to TuCOP. See Request ids.

Do not

  • Do not call /p2p/*: P2P is available only to TuCOP's own apps, the console does not let integrators request it, and it is not in the API reference.
  • Do not use Google sign-in for party sessions: it works only on TuCOP's own sites. Use an email code or a wallet signature.
  • Do not expect a collect (business pay-in) product: there is none.
  • Do not use /deposits in the sandbox: the payments provider's sandbox does not create COP deposit accounts.
  • Do not swap straight into deposit.address: swap into the payer's wallet, then send exactly deposit.amount from it in one transfer. A swap's output is never exact and its sender is the pool, so the deposit is not credited.
  • Do not register a key while the user is still typing it: each registration is a Bre-B directory check, and a partial key is someone else's or no one's.
  • Do not keep your own "confirmed" state for a destination: it is confirmed when confirmed_at is set. Read GET /destinations/{id} before quoting, and never overwrite a stored holder with an empty value (the destination.* webhooks carry no holder, on purpose).
  • Do not leave a destination pending in your own copy after TuCOP settled it, and do not stop polling a pending destination after a fixed time of your own: it can take up to about 10 minutes.
  • Do not register a key again after directory_no_match: the key does not exist. TuCOP already asks the directory a second time when its answer was a passing failure; only directory_unavailable and verification_timeout are worth retrying, minutes later.
  • Do not call DELETE /payments/{id}: it does not exist. Cancel with POST /payments/{id}/cancel.
  • Do not send an amount below the minimum or with cents: pay needs whole pesos, at least the minimum in GET /limits.
  • Do not store or log Bre-B keys, identity documents or holder names longer than the user needs.
  • Do not put real people's data in the sandbox.
  • Do not poll faster than needed: a destination every few seconds, a payment every 15 to 30 seconds.