Pay-ins
A pay-in is money coming in. There are four methods, in two families:
debit_otp: the payer’s bank sends a code to your user; your user types it on your screen; you confirm it. Three steps with a person in the middle.direct_debit: a direct debit, with amandate(agreement) the payer signed with their bank. One step.incoming_mobile_paymentandincoming_transfer: the payer sends the money on their own (a mobile payment or a transfer from their bank) to the receiving account; you wait for it with a pay-in created beforehand.
Incoming payment, step by step
Section titled “Incoming payment, step by step”POST /v2/payinswithmethod: incoming_mobile_payment(orincoming_transfer), the exactamount, and inpayerwho is going to pay: document, bank and, for mobile payments, phone.201withstatus: pendingandpending_reason: awaiting_payment;expires_atsays how long it waits (48 h by default;expires_in_hoursshortens it, never more than 72).- Show your payer the receiving account:
GET /v2/capabilitiesreturns it inreceiving_accountfor everyincoming_*method (bank, phone or account, document and holder). - Your payer pays from their bank. When a payment arrives that matches in
everything (exact amount, sending bank and the payer’s phone or document,
after the pay-in was created and within the window), the pay-in moves to
confirmedwithbank_referenceandconfirmed_at, your balance goes up by the net amount and you receivepayin.confirmed. - If the window expires without a payment:
failed/expiredandpayin.failed. While it waits, you can cancel it withPOST /v2/payins/{id}/cancel.
A payment that arrives with no pay-in waiting for it is not attributed to anyone: create the pay-in before asking your payer to pay. The fee for a received mobile payment is deducted from the amount (your balance goes up by the net); a received transfer has no fee.
One-time-code pay-in, step by step
Section titled “One-time-code pay-in, step by step”POST /v2/payinswithmethod: debit_otp→201withstatus: pendingandpending_reason: awaiting_code. The bank sends the code to the payer.code_expires_atsays until when it is valid.- Your user types the code on your screen.
POST /v2/payins/{id}/confirmwith{"code": "…"}→ the debit runs.confirmedcarriesbank_reference;failedcarriesfailure.code.
The code has a single attempt. A wrong one fails the operation with
failure.code: code_rejected: create another pay-in, or request a new code
with POST /v2/payins/{id}/resend-code before confirming (the previous
one stops working; at most five per operation). The code is never stored.
If the result didn’t arrive right away, the operation stays pending /
processing: the API resolves it on its own and notifies you by webhook.
The concept
Section titled “The concept”What the payer sees in their bank is concept, for example «Cobro TCP7K2M9Q»:
the system sets it with your company’s prefix and six characters of the
operation id. Your reference is not sent to the bank.
Routes
Section titled “Routes”Create a pay-in
Section titled “Create a pay-in”/v2/payinsScopepayins:crear
Creates a pay-in. With method: debit_otp, your user’s bank sends them a code and the operation stays pending / awaiting_code until you confirm it.
With method: incoming_mobile_payment or incoming_transfer you declare the amount and who is going to pay, and the operation stays pending / awaiting_payment until that payment reaches the receiving account (the one GET /v2/capabilities shows in receiving_account) or the window expires (expires_at; expires_in_hours shortens it, up to 72 h). When a payment arrives that matches in amount, bank and payer, the operation moves to confirmed with bank_reference and you receive payin.confirmed; if it expires, failed / expired. While it waits, you can cancel it with POST /v2/payins/{id}/cancel.
Requires the payins:crear scope.
The payer fields are the ones the method declares in GET /v2/capabilities; they are validated against that same declaration, and a field the method does not declare is rejected (details[].code = unknown).
Retrying this POST is safe with the same Idempotency-Key: it returns the same pay-in and does not request another code.
| Parameter | In | Required | Description |
|---|---|---|---|
Idempotency-Key |
header | yes | A unique key per request, 8 to 128 characters. The same key returns the same operation. |
| Code | Meaning |
|---|---|
201 |
The pay-in exists. status says what happened: with awaiting_code your user has to confirm it; with awaiting_payment the payment is awaited until expires_at. |
400 |
The request failed validation. details names each field. Codes: amount_out_of_range, bank_unsupported, body_invalid, country_unsupported, currency_unsupported, field_invalid, field_required, idempotency_key_required, method_unavailable. |
401 |
The key is missing or invalid. Codes: unauthorized. |
403 |
The key is valid but lacks the scope. Codes: scope_missing. |
409 |
The same Idempotency-Key with a different body. Codes: idempotency_key_reused. |
503 |
It could not be processed right now. Retry later with the same Idempotency-Key. Codes: temporarily_unavailable. |
Look up a pay-in
Section titled “Look up a pay-in”/v2/payins/{id}Scopeoperaciones:leer
The current state. Requires the operaciones:leer scope.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The operation id. |
| Code | Meaning |
|---|---|
200 |
The pay-in. |
401 |
The key is missing or invalid. Codes: unauthorized. |
403 |
The key is valid but lacks the scope. Codes: scope_missing. |
404 |
There is no operation with that id in your company. Codes: not_found. |
Cancel a pay-in awaiting payment
Section titled “Cancel a pay-in awaiting payment”/v2/payins/{id}/cancelScopepayins:crear
Only for an incoming pay-in (incoming_mobile_payment / incoming_transfer) that is still pending / awaiting_payment. It ends failed with failure.code = cancelled and you receive payin.failed. A payment that arrives afterwards is no longer attributed to it.
Any other pay-in, or one that already finished, gets 409 not_cancellable.
Requires the payins:crear scope.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The operation id. |
| Code | Meaning |
|---|---|
200 |
The pay-in, cancelled. |
401 |
The key is missing or invalid. Codes: unauthorized. |
403 |
The key is valid but lacks the scope. Codes: scope_missing. |
404 |
There is no operation with that id in your company. Codes: not_found. |
409 |
The pay-in is not awaiting a payment. Codes: not_cancellable. |
Confirm with the code
Section titled “Confirm with the code”/v2/payins/{id}/confirmScopepayins:crear
Runs the debit with the code your user typed. The code is not stored.
confirmed carries bank_reference. failed with code_rejected means a wrong or expired code: request another with POST /v2/payins/{id}/resend-code on a new operation, because a failed pay-in is final.
Requires the payins:crear scope.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The operation id. |
| Code | Meaning |
|---|---|
200 |
The result of the debit. |
400 |
The code is missing or the body could not be read. Codes: body_invalid, field_required. |
401 |
The key is missing or invalid. Codes: unauthorized. |
403 |
The key is valid but lacks the scope. Codes: scope_missing. |
404 |
There is no operation with that id in your company. Codes: not_found. |
409 |
The operation is not awaiting a code. Codes: code_not_expected. |
503 |
It could not be processed right now. Retry later with the same Idempotency-Key. Codes: temporarily_unavailable. |
Request a new code
Section titled “Request a new code”/v2/payins/{id}/resend-codeScopepayins:crear
For when the message didn’t arrive. There is a cap per operation. Requires the payins:crear scope.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The operation id. |
| Code | Meaning |
|---|---|
200 |
The operation, with the new code expiry. |
401 |
The key is missing or invalid. Codes: unauthorized. |
403 |
The key is valid but lacks the scope. Codes: scope_missing. |
404 |
There is no operation with that id in your company. Codes: not_found. |
409 |
The operation is not awaiting a code, or too many have been requested. Codes: code_not_expected, too_many_codes. |
503 |
It could not be processed right now. Retry later with the same Idempotency-Key. Codes: temporarily_unavailable. |
Pay-in fields
Section titled “Pay-in fields”| Field | Type | Required | Description |
|---|---|---|---|
amount |
string^[0-9]+(\.[0-9]+)?$ |
yes | Decimal string with a point and the currency’s decimals. Never a JSON number. |
country |
string^[A-Za-z]{2}$ |
yes | ISO 3166-1 alpha-2. |
currency |
string^[A-Za-z]{3}$ |
yes | ISO 4217. |
expires_in_hours |
integer |
no | Incoming pay-ins only: how many hours to wait for the payment. Without it, the default window (48 h). Never more than 72. |
mandate |
Mandato |
no | |
metadata |
object |
no | Anything you want to store, up to 4 KB. Comes back as is. |
method |
CodigoDeMetodo |
yes | |
payer |
Pagador |
yes | |
reference |
string |
no | Your reference. Comes back as is in the operation. |
| Field | Type | Required | Description |
|---|---|---|---|
account_number |
string |
no | |
account_type |
mobile | account |
no | |
bank_code |
string |
no | |
document |
object |
no | |
email |
string |
no | |
name |
string |
no | |
phone |
string |
no |
Which fields each method and each bank requires is stated by
GET /v2/capabilities (fields): send them exactly, and only those. An
incoming pay-in has no name: the payment is recognized by document, bank
and phone.
mandate (direct_debit only)
Section titled “mandate (direct_debit only)”| Field | Type | Required | Description |
|---|---|---|---|
contract_date |
string |
no | |
contract_id |
string^[A-Za-z0-9]{1,30}$ |
no | Alphanumeric, up to 30: that is what the bank accepts. |
Examples
Section titled “Examples”curl -X POST https://api.tucapi.app/v2/payins \ -H "Authorization: Bearer $API_KEY" \ -H "Idempotency-Key: order-4821" \ -H "Content-Type: application/json" \ -d '{ "amount": "1500.50", "country": "VE", "currency": "VES", "metadata": { "order": "A-1" }, "method": "debit_otp", "payer": { "account_number": "04121234567", "account_type": "mobile", "bank_code": "0102", "document": { "number": "12345678", "type": "V" }, "name": "Jane Doe" }, "reference": "invoice 123" }'{ "amount": "1500.50", "bank_reference": null, "code_expires_at": "2026-09-23T15:04:05Z", "country": "VE", "created_at": "2026-09-23T14:59:05Z", "created_by": "company", "currency": "VES", "failure": null, "id": "6f1c2a9e-3b4d-4c5e-8f70-1a2b3c4d5e6f", "metadata": { "order": "A-1" }, "method": "debit_otp", "pending_reason": "awaiting_code", "reference": "invoice 123", "status": "pending", "type": "payin", "updated_at": "2026-09-23T14:59:05Z"}curl -X POST https://api.tucapi.app/v2/payins/$ID/confirm \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "123456" }'{ "amount": "1500.50", "bank_reference": "000123456789", "confirmed_at": "2026-09-23T15:04:05Z", "country": "VE", "created_at": "2026-09-23T14:59:05Z", "created_by": "company", "currency": "VES", "failure": null, "id": "6f1c2a9e-3b4d-4c5e-8f70-1a2b3c4d5e6f", "metadata": { "order": "A-1" }, "method": "debit_otp", "reference": "invoice 123", "status": "confirmed", "type": "payin", "updated_at": "2026-09-23T14:59:05Z"}curl -X POST https://api.tucapi.app/v2/payins/$ID/cancel \ -H "Authorization: Bearer $API_KEY"{ "amount": "1500.50", "bank_reference": null, "country": "VE", "created_at": "2026-09-23T14:59:05Z", "created_by": "company", "currency": "VES", "expires_at": null, "failure": { "code": "cancelled", "message": "La operación fue cancelada." }, "id": "6f1c2a9e-3b4d-4c5e-8f70-1a2b3c4d5e6f", "metadata": { "order": "A-1" }, "method": "incoming_mobile_payment", "pending_reason": null, "reference": "invoice 123", "status": "failed", "type": "payin", "updated_at": "2026-09-23T14:59:05Z"}