Skip to content

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 a mandate (agreement) the payer signed with their bank. One step.
  • incoming_mobile_payment and incoming_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.
  1. POST /v2/payins with method: incoming_mobile_payment (or incoming_transfer), the exact amount, and in payer who is going to pay: document, bank and, for mobile payments, phone. 201 with status: pending and pending_reason: awaiting_payment; expires_at says how long it waits (48 h by default; expires_in_hours shortens it, never more than 72).
  2. Show your payer the receiving account: GET /v2/capabilities returns it in receiving_account for every incoming_* method (bank, phone or account, document and holder).
  3. 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 confirmed with bank_reference and confirmed_at, your balance goes up by the net amount and you receive payin.confirmed.
  4. If the window expires without a payment: failed / expired and payin.failed. While it waits, you can cancel it with POST /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.

  1. POST /v2/payins with method: debit_otp → 201 with status: pending and pending_reason: awaiting_code. The bank sends the code to the payer. code_expires_at says until when it is valid.
  2. Your user types the code on your screen.
  3. POST /v2/payins/{id}/confirm with {"code": "…"} → the debit runs. confirmed carries bank_reference; failed carries failure.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.

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.

POST/v2/payins

Scopepayins: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.
GET/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.
POST/v2/payins/{id}/cancel

Scopepayins: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.
POST/v2/payins/{id}/confirm

Scopepayins: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.
POST/v2/payins/{id}/resend-code

Scopepayins: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.
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.

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.
POST /v2/payins
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"
}'
Response 201
{
"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"
}
POST /v2/payins/{id}/confirm
curl -X POST https://api.tucapi.app/v2/payins/$ID/confirm \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "123456"
}'
Response 200
{
"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"
}
POST /v2/payins/{id}/cancel
curl -X POST https://api.tucapi.app/v2/payins/$ID/cancel \
-H "Authorization: Bearer $API_KEY"
Response 200
{
"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"
}