Banking API payments

API 1.0.0

An MCP tool wraps this operation: Banking tools.

The Payments group covers a payment's full cycle: create it, fetch it by identifier, list it with filters and search it by any of its references.

POST /api/public/v1/transactions/payments
GET  /api/public/v1/transactions/payments
GET  /api/public/v1/transactions/payments/{payment_id}
POST /api/public/v1/transactions/payments/search

Resource rules#

Methods. payment_method_code accepts PIN and SINPE_MOVIL. Direction. At creation, direction only accepts OUT (outbound); IN (inbound) payments remain available in listings and webhooks. Destination account. it is identified by IBAN or by PHONE, depending on the method. Validate it before transferring with account validation.

Your own reference. client_reference is your payment identifier and is unique per tenant: two payments of the same customer cannot share it. Use it to look a payment up without storing the payment_id.

Safe retries. Creation accepts Idempotency-Key; resending the same key with the same body returns the original payment instead of creating another one. See safe retries.

States#

A payment moves through five states: pending, processing, confirmed, posted and failed.

A reversal is not a state: a reversed payment stays in posted and is recognized by result: reversed and has_reversal: true, plus the payment.reversed event. Always check those two fields before treating a payment as final.

Operations#

List customer payments

GET /api/public/v1/transactions/payments

Host: https://api-baas-sandbox.tilopay.com — requires Authorization: Bearer <access_token>.

Simple REST list of payments for an owned account (IBAN).

Filters are query parameters. Ownership (tenant_id, owner_type, owner_id) is derived from the access token; clients must not send ownership filters.

Account identification (required): type + value (identifier scheme + value, e.g. IBAN + IBAN string). This is not the internal account classification.

Pagination is cursor-based (limit, cursor). Default limit is 20 (max 100).

status in the response and as a filter uses the public lowercase catalog (pending, processing, confirmed, posted, failed). Same values as webhooks. REST also returns status_detail (uppercase operational name).

Each item includes a consolidated result (succeeded, failed, reversed, pending) for reconciliation when a webhook was missed. status is only the processing/lifecycle catalog and does not change to failed when a posted payment is later reversed — use result=reversed and has_reversal=true (webhook payment.reversed). error is null when there is no failure information; it is an object only when domain / platform is present (result is failed or reversed), same shape as webhook payment.failed payment.error. source / destination use null for unknown account, name, and identification; account values are IBAN or PHONE only (never internal account UUIDs). For PIN payments, identification.number is the partner-submitted public number (no SINPE hyphens); older payments may still show the hyphenated SINPE form.

Parameters

ParameterInTypeRequiredDescription
X-Correlation-IdheaderstringnoOptional client-supplied correlation id for end-to-end tracing. Echoed back as correlation_id in the response envelope. If omitted, the API generates one and still returns it.
typequerystringyesAccount identifier scheme (e.g. IBAN). This is not the internal account classification (e.g. operational).Values: IBAN
valuequerystringyesAccount identifier value (IBAN). Spaces are ignored.
statusquerystringnoOptional payment status filter. Public catalog is lowercase: pending, processing, confirmed, posted, failed. Uppercase aliases of the same values are accepted. Legacy values such as INITIATED map to pending.Values: pending, processing, confirmed, posted, failed
date_fromquerystring (date-time)noInclusive lower bound for created_at. RFC 3339 (UTC Z or offset; optional fractional seconds).
date_toquerystring (date-time)noInclusive upper bound for created_at. RFC 3339 (UTC Z or offset; optional fractional seconds).
client_referencequerystringnoFilter by partner client reference (exact match against the value sent at payment creation).
currencyquerystringnoFilter by payment currency (ISO 4217, e.g. CRC).
payment_method_codequerystringnoFilter by public payment method code.Values: PIN, SINPE_MOVIL
directionquerystringnoFilter by payment direction. Accepts public values OUT/IN or PAYOUT/PAYIN (internally normalized to payout/payin).Values: OUT, IN, PAYOUT, PAYIN
limitqueryintegernoPage size (default 20, max 100).
cursorquerystringnoOpaque cursor from a previous response pagination.next_cursor.

Response 200

The request was processed successfully.

FieldTypeRequiredDescription
schema_versionstringyesSame value as webhook envelope schema_version (1.0.0).
itemsarray<object>yes—
items[].schema_versionstringyesPayment-read contract version. Same value as webhook envelope schema_version (1.0.0) for payment.succeeded / payment.failed.
items[].payment_idstring (uuid)yesPayment UUID returned by create. Partner-facing payment key for GET /transactions/payments/{payment_id} and search. Not an internal account id.
items[].public_idstringnoNumeric public payment identifier returned by create. null if not yet assigned. Not an internal account UUID. The same identifier is a number in webhooks and text in REST. Always treat it as text.Can be null.
items[].typestringyesPayment type derived from direction (PAYIN / PAYOUT).
items[].payment_method_codestringyesPublic payment method catalog (PIN, SINPE_MOVIL). Same values as webhooks.Values: PIN, SINPE_MOVIL
items[].statusstringyesPublic payment status catalog. Always lowercase. Same values on REST (create, list, get) and webhooks (payment.status). Internal lifecycle states are collapsed: - pending: initiated, validated, accepted - processing: processing, pending_processing, posting - confirmed: confirmed by the rail, not yet ledger-posted - posted: ledger posted (stays posted after a later reversal) - failed: failed, rejected, posting_failed, cancelled, expired A reversal is not a status. Use REST result=reversed / has_reversal=true and webhook payment.reversed. Operational detail remains in status_detail (uppercase internal name) on REST create/list/get. Webhook payment.failed uses error.platform.code to distinguish posting failures from provider rejections.Values: pending, processing, confirmed, posted, failed
items[].status_detailstringnoOperational (internal) status in uppercase, e.g. VALIDATED, POSTED, POSTING_FAILED. Companion to the public status catalog. Independent of result (posted is the debit/posting milestone, not provider success).
items[].resultstringyesConsolidated business outcome for reconciliation (backup to webhooks). Independent of processing status. Derived as: reversed when has_reversal is true (highest priority); succeeded when succeeded_at is set and there is no reversal; failed when processing status is failed or posting_failed; otherwise pending. A posted payment that was later reversed has status=posted and result=reversed.Values: succeeded, failed, reversed, pending
items[].succeeded_atstring (date-time)noProvider success confirmation timestamp (RFC 3339 UTC, second precision, Z). Null while the payment may already be posted internally but the provider has not confirmed success.Can be null.
items[].has_reversalbooleanyesTrue when a reversal was applied. Independent of status. Correlates with webhook payment.reversed.
items[].reversed_atstring (date-time)noWhen the reversal was applied. Null when has_reversal is false.Can be null.
items[].created_atstring (date-time)noRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
items[].updated_atstring (date-time)noRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
items[].amountobjectnoSame monetary shape as payment create (PaymentAmount).
items[].amount.amountstringyes—
items[].amount.currencystringyes—
items[].sourceobjectnoPayment party on read APIs. Nested fields are always present: null when the value is unknown. account is a public identifier (IBAN or PHONE); internal account UUIDs are never returned.
items[].source.accountobjectyesPublic account identifier (type + value). null when the IBAN or phone cannot be resolved. Never an internal account UUID.Can be null.
items[].source.namestringyesParty name from processing metadata when available; otherwise null.Can be null.
items[].source.identificationobjectyesParty identification (type catalog key + number). Object only when at least one field is known; otherwise null. Does not expose internal SINPE type codes (0–9); unmapped types are omitted (type: null). number is the partner-submitted public value (digits, no SINPE hyphens). Historical payments may still return the hyphenated SINPE display form.Can be null.
items[].destinationobjectnoPayment party on read APIs. Nested fields are always present: null when the value is unknown. account is a public identifier (IBAN or PHONE); internal account UUIDs are never returned.
items[].destination.accountobjectyesPublic account identifier (type + value). null when the IBAN or phone cannot be resolved. Never an internal account UUID.Can be null.
items[].destination.namestringyesParty name from processing metadata when available; otherwise null.Can be null.
items[].destination.identificationobjectyesParty identification (type catalog key + number). Object only when at least one field is known; otherwise null. Does not expose internal SINPE type codes (0–9); unmapped types are omitted (type: null). number is the partner-submitted public value (digits, no SINPE hyphens). Historical payments may still return the hyphenated SINPE display form.Can be null.
items[].client_referencestringnoPartner reference sent at payment creation (stable after processing). Same field as webhook payment.client_reference.
items[].external_referencestringnoRail/SINPE transaction id. Same field, location, and meaning as webhook payment.external_reference on payment.succeeded / payment.failed Sibling of client_reference. null if none.Can be null.
items[].providerobjectnoShared provider snapshot on REST payment reads and webhook envelopes. Rail reference is only on payment.external_reference, not duplicated here. provider_status_* match webhook rejection.reason_code / message / code when the provider rejected the operation. null when there is no rejection.
items[].provider.correlation_idstringyesProvider transaction correlation identifier. Sent to SINPE as the correlation id. Not channel_reference (e.g. MOBILE_APP). Distinct from webhook event.correlation_id (HTTP request id for this delivery). null when unknown.Can be null.
items[].provider.occurred_atstring (date-time)yesRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
items[].provider.provider_status_codestringyesRaw provider Motivo/code (e.g. 31). Same meaning as webhook rejection.reason_code. null when there is no provider rejection.Can be null.
items[].provider.provider_status_descstringyesProvider Motivo text (prefers Detalle). Same meaning as webhook rejection.message. null when there is no provider rejection.Can be null.
items[].provider.provider_status_semanticstringyesNormalized platform semantic (e.g. ACCOUNT_BLOCKED). Same meaning as webhook rejection.code. null when there is no provider rejection.Can be null.
items[].errorobjectnoAlways present. null when there is no failure information (succeeded, pending, or failed/reversed without taxonomy). Object uses the same domain / platform shape as webhook payment.failed.Can be null.
items[].error.domainstringyesSame catalog as webhook payment.error.domain.Values: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal
items[].error.platformobjectyes—
paginationobjectnoCursor pagination. Pass next_cursor as cursor on the next request.
pagination.next_cursorstringnoOpaque cursor for the next page; omitted or empty when there is no next page
pagination.limitintegernoPage size applied to this response

Response example

{
  "success": true,
  "http_status_code": 200,
  "response_code": "OK",
  "message": "The request was processed successfully.",
  "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "data": {
    "schema_version": "1.0.0",
    "items": [
      {
        "schema_version": "1.0.0",
        "payment_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
        "public_id": "10234",
        "type": "PAYOUT",
        "payment_method_code": "PIN",
        "status": "posted",
        "status_detail": "POSTED",
        "result": "succeeded",
        "succeeded_at": "2026-01-15T12:00:00Z",
        "has_reversal": false,
        "reversed_at": null,
        "created_at": "2026-01-15T12:00:00Z",
        "updated_at": "2026-01-15T12:00:00Z",
        "amount": {
          "amount": "1000.00",
          "currency": "CRC",
          "fees": "0.00",
          "taxes": "0.00"
        },
        "source": {
          "account": {
            "type": "IBAN",
            "value": "CR26036900101010588061",
            "currency": "CRC"
          },
          "name": "María González",
          "identification": {
            "type": "cedula",
            "number": "0205620043"
          }
        },
        "destination": {
          "account": {
            "type": "IBAN",
            "value": "CR16010400000000000031",
            "currency": "CRC"
          },
          "name": "Juan Perez",
          "identification": {
            "type": "cedula",
            "number": "0101010101"
          }
        },
        "client_reference": "INV-1001",
        "external_reference": "SINPE-20260115-000123",
        "provider": {
          "correlation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "occurred_at": "2026-01-15T12:00:00Z",
          "provider_status_code": "00",
          "provider_status_desc": "Aprobada",
          "provider_status_semantic": "SUCCESS"
        },
        "error": null
      }
    ],
    "pagination": {
      "next_cursor": "",
      "limit": 20
    }
  }
}

Error responses

HTTPresponse_codeDescription
400INVALID_REQUESTInvalid request. Check the required fields and try again.
401UNAUTHORIZEDUnauthorized. Verify your session or credentials.
403FORBIDDENYou do not have permission to perform this action.
403ACCOUNT_ACCESS_DENIEDThe actor does not have access to the requested account.
500INTERNAL_ERRORAn unexpected error occurred. Please try again later.
502PROVIDER_ERRORUnable to complete the request with the payment provider. Please try again later.
503SERVICE_UNAVAILABLEA required service is temporarily unavailable. Please try again later.

Create payment

POST /api/public/v1/transactions/payments

Host: https://api-baas-sandbox.tilopay.com — requires Authorization: Bearer <access_token>. Accepts Idempotency-Key.

Creates a payment using an authorized internal source account. Processing may be asynchronous; HTTP 201 does not imply the payment is finalized. source.identification.number and destination.identification.number are stored in public form (the submitted digits, without SINPE hyphens) and returned on later payment reads. The hyphenated SINPE form is used only internally and is not the public contract. Idempotency-Key is optional; if omitted, each request is treated as a new attempt. A repeated client_reference returns 409 PAYMENT_DUPLICATE when an in-flight or completed payment already uses it; a prior failed (or rejected/cancelled/expired) payment does not block a retry with the same client_reference. If Idempotency-Key is provided, the same key with the same payload replays the prior response; a different payload with the same key returns 409 IDEMPOTENCY_CONFLICT.

There is no dual control in API payment creation: a payment is processed directly when the token, permissions and data are valid. Only direction OUT is accepted. Limits apply per transaction, daily and monthly, in amount and count. If any limit is exceeded, processing fails and the payment.failed event notifies the result.

Parameters

ParameterInTypeRequiredDescription
X-Correlation-IdheaderstringnoOptional client-supplied correlation id for end-to-end tracing. Echoed back as correlation_id in the response envelope. If omitted, the API generates one and still returns it.
Idempotency-KeyheaderstringnoOptional opaque client key for safe retries. When omitted, each call is a new attempt (duplicates are detected by client_reference on in-flight or completed payments). Reusing a key with a different body returns 409.

Request body

FieldTypeRequiredDescription
payment_method_codestringyesPublic payment method catalog (PIN, SINPE_MOVIL).Values: PIN, SINPE_MOVIL
directionstringyesCreation only accepts OUT (outbound). IN payments remain available in listings and webhooks.Values: OUT
country_codestringnoOperational country when multi-country applies
amountobjectyes—
amount.amountstringyesAmount as a decimal string with exactly 2 fraction digits (e.g. "1000.00").
amount.currencystringyesISO 4217 currency of the payment.Values: CRC, USD
sourceobjectyes—
source.accountobjectyes—
source.account.typestringyesAccount identifier scheme. Only IBAN is supported here. This is not the internal account classification (e.g. operational).Values: IBAN
source.account.valuestringyesIBAN (spaces allowed; normalized server-side)
source.namestringyesAccount holder name
source.identificationobjectyesCounterparty identification for payment create. type uses platform identification-catalog keys (e.g. cedula, juridica, dimex). Same vocabulary as holder.id_type from POST /accounts/validate. number is the public identification (digits, no SINPE hyphens). The platform may reformat it internally for SINPE; that internal form is not echoed on public payment reads. For SINPE_MOVIL, account validate may return unknown because the wallet operation does not expose identification type; do not assume a confirmed type.
source.identification.typestringnoIdentification catalog key (lowercase), e.g. cedula, juridica. May be copied from holder.id_type after account validate. For SINPE_MOVIL that value may be unknown (expected limitation, not an error).Values: cedula, juridica, dimex, gobierno, institucion_autonoma, didi, pasaporte
source.identification.numberstringnoPublic identification number as submitted by the partner. Digits only; do not send SINPE hyphenation (e.g. 01-1234-3005) or treat this field as the internal SINPE display form. Supported types include cedula, juridica, dimex, gobierno, institucion_autonoma, didi, and pasaporte.
destinationobjectyes—
destination.accountobjectyes—
destination.account.typestringyesCounterparty account identifier scheme (IBAN for PIN, PHONE for SINPE_MOVIL). Not the internal account classification.Values: IBAN, PHONE
destination.account.valuestringyesWhen type is PHONE (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). Pattern for PHONE: ^\d{8}$.
destination.namestringyesCounterparty name
destination.identificationobjectyesCounterparty identification for payment create. type uses platform identification-catalog keys (e.g. cedula, juridica, dimex). Same vocabulary as holder.id_type from POST /accounts/validate. number is the public identification (digits, no SINPE hyphens). The platform may reformat it internally for SINPE; that internal form is not echoed on public payment reads. For SINPE_MOVIL, account validate may return unknown because the wallet operation does not expose identification type; do not assume a confirmed type.
destination.identification.typestringnoIdentification catalog key (lowercase), e.g. cedula, juridica. May be copied from holder.id_type after account validate. For SINPE_MOVIL that value may be unknown (expected limitation, not an error).Values: cedula, juridica, dimex, gobierno, institucion_autonoma, didi, pasaporte
destination.identification.numberstringnoPublic identification number as submitted by the partner. Digits only; do not send SINPE hyphenation (e.g. 01-1234-3005) or treat this field as the internal SINPE display form. Supported types include cedula, juridica, dimex, gobierno, institucion_autonoma, didi, and pasaporte.
detail_referencestringyesOperational / transactional description stored on the payment
client_referencestringnoPartner functional reference. Unique per tenant among in-flight and completed payments; a failed attempt can be retried with the same value.
channel_referencestringno—
originatorobjectnoOptional Ultimate Debtor / Travel Rule originator information. Informational only in this release; no validation is applied. Stored under payment metadata when provided.Can be null.
originator.companyobjectno—Can be null.
originator.company.legal_namestringno—
originator.company.id_typestringno—
originator.company.id_numberstringno—
originator.customerobjectno—Can be null.
originator.customer.namestringno—
originator.customer.id_typestringno—
originator.customer.id_numberstringno—

Request example

{
  "payment_method_code": "PIN",
  "direction": "OUT",
  "country_code": "CR",
  "amount": {
    "amount": "1000.00",
    "currency": "CRC"
  },
  "source": {
    "account": {
      "type": "IBAN",
      "value": "CR26036900101010588061"
    },
    "name": "María González",
    "identification": {
      "type": "cedula",
      "number": "0205620043"
    }
  },
  "destination": {
    "account": {
      "type": "IBAN",
      "value": "CR16010400000000000031"
    },
    "name": "Juan Perez",
    "identification": {
      "type": "cedula",
      "number": "0101010101"
    }
  },
  "detail_reference": "Pago factura 1001",
  "client_reference": "INV-1001",
  "channel_reference": "MOBILE_APP"
}

Response 201

The resource was created successfully.

FieldTypeRequiredDescription
payment_idstring (uuid)noInternal payment UUID
public_idstringnoPublic numeric payment identifier The same identifier is a number in webhooks and text in REST. Always treat it as text.
statusstringnoPublic payment status catalog. Always lowercase. Same values on REST (create, list, get) and webhooks (payment.status). Internal lifecycle states are collapsed: - pending: initiated, validated, accepted - processing: processing, pending_processing, posting - confirmed: confirmed by the rail, not yet ledger-posted - posted: ledger posted (stays posted after a later reversal) - failed: failed, rejected, posting_failed, cancelled, expired A reversal is not a status. Use REST result=reversed / has_reversal=true and webhook payment.reversed. Operational detail remains in status_detail (uppercase internal name) on REST create/list/get. Webhook payment.failed uses error.platform.code to distinguish posting failures from provider rejections.Values: pending, processing, confirmed, posted, failed
status_detailstringnoOperational status detail (e.g. PROCESSING)
payment_method_codestringnoPublic payment method catalog (PIN, SINPE_MOVIL).Values: PIN, SINPE_MOVIL
created_atstring (date-time)noRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.

Response example

{
  "success": true,
  "http_status_code": 201,
  "response_code": "CREATED",
  "message": "The resource was created successfully.",
  "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "data": {
    "payment_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "public_id": "10234",
    "status": "pending",
    "status_detail": "PROCESSING",
    "payment_method_code": "PIN",
    "created_at": "2026-01-15T12:00:00Z"
  }
}

Error responses

HTTPresponse_codeDescription
400PAYMENT_INVALID_PAYLOADInvalid payment request payload.
400INVALID_PAYMENT_METHODInvalid or unsupported payment method.
400INVALID_ACCOUNT_TYPEUnsupported or invalid account type.
400ACCOUNT_INVALIDThe account is invalid or not enabled for the service.
400ACCOUNT_HOLDER_MISMATCHAccount holder does not match the provided identification or name.
401UNAUTHORIZEDUnauthorized. Verify your session or credentials.
403FORBIDDENYou do not have permission to perform this action.
403ACCOUNT_ACCESS_DENIEDThe actor does not have access to the requested account.
404ACCOUNT_NOT_FOUNDAccount not found.
409IDEMPOTENCY_CONFLICTIdempotency conflict: the same Idempotency-Key was reused with a different request body, or a previous request is still in progress.
409PAYMENT_DUPLICATEAn in-flight or completed payment with this client_reference already exists.
409CONFLICTThe request conflicts with the current state.
422INSUFFICIENT_FUNDSInsufficient funds to complete the payment.
422LIMIT_EXCEEDEDA transaction or accumulation limit was exceeded.
422UNPROCESSABLE_ENTITYThe request is well-formed but cannot be processed due to business rules.
500INTERNAL_ERRORAn unexpected error occurred. Please try again later.
502PROVIDER_ERRORUnable to complete the request with the payment provider. Please try again later.
503SERVICE_UNAVAILABLEA required service is temporarily unavailable. Please try again later.

Search payment by identifier

POST /api/public/v1/transactions/payments/search

Host: https://api-baas-sandbox.tilopay.com — requires Authorization: Bearer <access_token>.

Looks up a single payment within the supplied customer account by exactly one of:

  • payment_id (internal UUID)
  • public_id (numeric public identifier)
  • client_reference (partner reference sent at creation)

Sending more than one identifier returns 400. Response shape matches get-by-id (schema_version, payment-level external_reference, shared provider object, provider.provider_status_*, webhook-aligned error). Same public contract as GET /transactions/payments, including the public identification.number of PIN payments. If several payments share a client_reference (failed attempt plus retry), the in-flight or completed payment is returned when one exists.

Parameters

ParameterInTypeRequiredDescription
X-Correlation-IdheaderstringnoOptional client-supplied correlation id for end-to-end tracing. Echoed back as correlation_id in the response envelope. If omitted, the API generates one and still returns it.

Request body

FieldTypeRequiredDescription
accountobjectyes—
account.typestringyesAccount identifier scheme (e.g. IBAN, PHONE). Not the internal account classification (e.g. operational).Values: IBAN, PHONE
account.valuestringyesWhen type is PHONE (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777").
account.currencystringnoOptional account currency in response payloads.
payment_idstring (uuid)noInternal payment UUID.
public_idstringnoNumeric public payment identifier. The same identifier is a number in webhooks and text in REST. Always treat it as text.
client_referencestringnoPartner reference sent at payment creation.

Request example

byPaymentId

{
  "account": {
    "type": "IBAN",
    "value": "CR08036900101010933608"
  },
  "payment_id": "11111111-1111-1111-1111-111111111111"
}

byPublicId

{
  "account": {
    "type": "IBAN",
    "value": "CR08036900101010933608"
  },
  "public_id": "134729"
}

byClientReference

{
  "account": {
    "type": "IBAN",
    "value": "CR08036900101010933608"
  },
  "client_reference": "INV-01082"
}

Response 200

The request was processed successfully.

FieldTypeRequiredDescription
schema_versionstringyesPayment-read contract version. Same value as webhook envelope schema_version (1.0.0) for payment.succeeded / payment.failed.
payment_idstring (uuid)yesPayment UUID returned by create. Partner-facing payment key for GET /transactions/payments/{payment_id} and search. Not an internal account id.
public_idstringnoNumeric public payment identifier returned by create. null if not yet assigned. Not an internal account UUID. The same identifier is a number in webhooks and text in REST. Always treat it as text.Can be null.
typestringyesPayment type derived from direction (PAYIN / PAYOUT).
payment_method_codestringyesPublic payment method catalog (PIN, SINPE_MOVIL). Same values as webhooks.Values: PIN, SINPE_MOVIL
statusstringyesPublic payment status catalog. Always lowercase. Same values on REST (create, list, get) and webhooks (payment.status). Internal lifecycle states are collapsed: - pending: initiated, validated, accepted - processing: processing, pending_processing, posting - confirmed: confirmed by the rail, not yet ledger-posted - posted: ledger posted (stays posted after a later reversal) - failed: failed, rejected, posting_failed, cancelled, expired A reversal is not a status. Use REST result=reversed / has_reversal=true and webhook payment.reversed. Operational detail remains in status_detail (uppercase internal name) on REST create/list/get. Webhook payment.failed uses error.platform.code to distinguish posting failures from provider rejections.Values: pending, processing, confirmed, posted, failed
status_detailstringnoOperational (internal) status in uppercase, e.g. VALIDATED, POSTED, POSTING_FAILED. Companion to the public status catalog. Independent of result (posted is the debit/posting milestone, not provider success).
resultstringyesConsolidated business outcome for reconciliation (backup to webhooks). Independent of processing status. Derived as: reversed when has_reversal is true (highest priority); succeeded when succeeded_at is set and there is no reversal; failed when processing status is failed or posting_failed; otherwise pending. A posted payment that was later reversed has status=posted and result=reversed.Values: succeeded, failed, reversed, pending
succeeded_atstring (date-time)noProvider success confirmation timestamp (RFC 3339 UTC, second precision, Z). Null while the payment may already be posted internally but the provider has not confirmed success.Can be null.
has_reversalbooleanyesTrue when a reversal was applied. Independent of status. Correlates with webhook payment.reversed.
reversed_atstring (date-time)noWhen the reversal was applied. Null when has_reversal is false.Can be null.
created_atstring (date-time)noRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
updated_atstring (date-time)noRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
amountobjectnoSame monetary shape as payment create (PaymentAmount).
amount.amountstringyes—
amount.currencystringyes—
sourceobjectnoPayment party on read APIs. Nested fields are always present: null when the value is unknown. account is a public identifier (IBAN or PHONE); internal account UUIDs are never returned.
source.accountobjectyesPublic account identifier (type + value). null when the IBAN or phone cannot be resolved. Never an internal account UUID.Can be null.
source.account.typestringyesAccount identifier scheme (e.g. IBAN, PHONE). Not the internal account classification (e.g. operational).Values: IBAN, PHONE
source.account.valuestringyesWhen type is PHONE (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777").
source.account.currencystringnoOptional account currency in response payloads.
source.namestringyesParty name from processing metadata when available; otherwise null.Can be null.
source.identificationobjectyesParty identification (type catalog key + number). Object only when at least one field is known; otherwise null. Does not expose internal SINPE type codes (0–9); unmapped types are omitted (type: null). number is the partner-submitted public value (digits, no SINPE hyphens). Historical payments may still return the hyphenated SINPE display form.Can be null.
source.identification.typestringnoIdentification catalog key (e.g. cedula, juridica). null when unknown.Can be null.
source.identification.numberstringnoPublic identification number. New PIN payments return the partner-submitted value (digits, no SINPE hyphens). Historical payments may still return the SINPE display form (hyphenated, possibly padded).Can be null.
destinationobjectnoPayment party on read APIs. Nested fields are always present: null when the value is unknown. account is a public identifier (IBAN or PHONE); internal account UUIDs are never returned.
destination.accountobjectyesPublic account identifier (type + value). null when the IBAN or phone cannot be resolved. Never an internal account UUID.Can be null.
destination.account.typestringyesAccount identifier scheme (e.g. IBAN, PHONE). Not the internal account classification (e.g. operational).Values: IBAN, PHONE
destination.account.valuestringyesWhen type is PHONE (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777").
destination.account.currencystringnoOptional account currency in response payloads.
destination.namestringyesParty name from processing metadata when available; otherwise null.Can be null.
destination.identificationobjectyesParty identification (type catalog key + number). Object only when at least one field is known; otherwise null. Does not expose internal SINPE type codes (0–9); unmapped types are omitted (type: null). number is the partner-submitted public value (digits, no SINPE hyphens). Historical payments may still return the hyphenated SINPE display form.Can be null.
destination.identification.typestringnoIdentification catalog key (e.g. cedula, juridica). null when unknown.Can be null.
destination.identification.numberstringnoPublic identification number. New PIN payments return the partner-submitted value (digits, no SINPE hyphens). Historical payments may still return the SINPE display form (hyphenated, possibly padded).Can be null.
client_referencestringnoPartner reference sent at payment creation (stable after processing). Same field as webhook payment.client_reference.
external_referencestringnoRail/SINPE transaction id. Same field, location, and meaning as webhook payment.external_reference on payment.succeeded / payment.failed Sibling of client_reference. null if none.Can be null.
providerobjectnoShared provider snapshot on REST payment reads and webhook envelopes. Rail reference is only on payment.external_reference, not duplicated here. provider_status_* match webhook rejection.reason_code / message / code when the provider rejected the operation. null when there is no rejection.
provider.correlation_idstringyesProvider transaction correlation identifier. Sent to SINPE as the correlation id. Not channel_reference (e.g. MOBILE_APP). Distinct from webhook event.correlation_id (HTTP request id for this delivery). null when unknown.Can be null.
provider.occurred_atstring (date-time)yesRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
provider.provider_status_codestringyesRaw provider Motivo/code (e.g. 31). Same meaning as webhook rejection.reason_code. null when there is no provider rejection.Can be null.
provider.provider_status_descstringyesProvider Motivo text (prefers Detalle). Same meaning as webhook rejection.message. null when there is no provider rejection.Can be null.
provider.provider_status_semanticstringyesNormalized platform semantic (e.g. ACCOUNT_BLOCKED). Same meaning as webhook rejection.code. null when there is no provider rejection.Can be null.
errorobjectnoAlways present. null when there is no failure information (succeeded, pending, or failed/reversed without taxonomy). Object uses the same domain / platform shape as webhook payment.failed.Can be null.
error.domainstringyesSame catalog as webhook payment.error.domain.Values: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal
error.platformobjectyes—
error.platform.codestringyesStable platform public code (same as webhook payment.error.platform.code).
error.platform.messagestringyesActionable message (prefers provider Detalle). Same as webhook payment.error.platform.message.

Response example

{
  "success": true,
  "http_status_code": 200,
  "response_code": "OK",
  "message": "The request was processed successfully.",
  "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "data": {
    "schema_version": "1.0.0",
    "payment_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "public_id": "10234",
    "type": "PAYOUT",
    "payment_method_code": "PIN",
    "status": "posted",
    "status_detail": "POSTED",
    "result": "succeeded",
    "succeeded_at": "2026-01-15T12:00:00Z",
    "has_reversal": false,
    "reversed_at": null,
    "created_at": "2026-01-15T12:00:00Z",
    "updated_at": "2026-01-15T12:00:00Z",
    "amount": {
      "amount": "1000.00",
      "currency": "CRC",
      "fees": "0.00",
      "taxes": "0.00"
    },
    "source": {
      "account": {
        "type": "IBAN",
        "value": "CR26036900101010588061",
        "currency": "CRC"
      },
      "name": "María González",
      "identification": {
        "type": "cedula",
        "number": "0205620043"
      }
    },
    "destination": {
      "account": {
        "type": "IBAN",
        "value": "CR16010400000000000031",
        "currency": "CRC"
      },
      "name": "Juan Perez",
      "identification": {
        "type": "cedula",
        "number": "0101010101"
      }
    },
    "client_reference": "INV-1001",
    "external_reference": "SINPE-20260115-000123",
    "provider": {
      "correlation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "occurred_at": "2026-01-15T12:00:00Z",
      "provider_status_code": "00",
      "provider_status_desc": "Aprobada",
      "provider_status_semantic": "SUCCESS"
    },
    "error": null
  }
}

Error responses

HTTPresponse_codeDescription
400INVALID_REQUESTInvalid request. Check the required fields and try again.
401UNAUTHORIZEDUnauthorized. Verify your session or credentials.
403FORBIDDENYou do not have permission to perform this action.
403ACCOUNT_ACCESS_DENIEDThe actor does not have access to the requested account.
404PAYMENT_NOT_FOUNDPayment not found.
500INTERNAL_ERRORAn unexpected error occurred. Please try again later.
502PROVIDER_ERRORUnable to complete the request with the payment provider. Please try again later.
503SERVICE_UNAVAILABLEA required service is temporarily unavailable. Please try again later.

Get payment by id

GET /api/public/v1/transactions/payments/{payment_id}

Host: https://api-baas-sandbox.tilopay.com — requires Authorization: Bearer <access_token>.

Returns a single payment using the same public contract as the list endpoint, including consolidated result, succeeded_at, has_reversal, and error. error is null when there is no failure information; it is an object only when domain / platform is present (result is failed or reversed), same shape as webhook payment.failed payment.error. source / destination use null for unknown nested fields and never expose internal account UUIDs. identification.number on PIN payments is the partner-submitted public number (digits, no SINPE hyphens). Older payments may still return the hyphenated SINPE form. Use this as a webhook backup.

Looks up a payment by either identifier:

  • payment_id (internal UUID)
  • public_id (numeric public identifier)

For client_reference, use POST /api/public/v1/transactions/payments/search.

Parameters

ParameterInTypeRequiredDescription
X-Correlation-IdheaderstringnoOptional client-supplied correlation id for end-to-end tracing. Echoed back as correlation_id in the response envelope. If omitted, the API generates one and still returns it.
payment_idpathstringyesInternal payment UUID or numeric public_id.

Response 200

The request was processed successfully.

FieldTypeRequiredDescription
schema_versionstringyesPayment-read contract version. Same value as webhook envelope schema_version (1.0.0) for payment.succeeded / payment.failed.
payment_idstring (uuid)yesPayment UUID returned by create. Partner-facing payment key for GET /transactions/payments/{payment_id} and search. Not an internal account id.
public_idstringnoNumeric public payment identifier returned by create. null if not yet assigned. Not an internal account UUID. The same identifier is a number in webhooks and text in REST. Always treat it as text.Can be null.
typestringyesPayment type derived from direction (PAYIN / PAYOUT).
payment_method_codestringyesPublic payment method catalog (PIN, SINPE_MOVIL). Same values as webhooks.Values: PIN, SINPE_MOVIL
statusstringyesPublic payment status catalog. Always lowercase. Same values on REST (create, list, get) and webhooks (payment.status). Internal lifecycle states are collapsed: - pending: initiated, validated, accepted - processing: processing, pending_processing, posting - confirmed: confirmed by the rail, not yet ledger-posted - posted: ledger posted (stays posted after a later reversal) - failed: failed, rejected, posting_failed, cancelled, expired A reversal is not a status. Use REST result=reversed / has_reversal=true and webhook payment.reversed. Operational detail remains in status_detail (uppercase internal name) on REST create/list/get. Webhook payment.failed uses error.platform.code to distinguish posting failures from provider rejections.Values: pending, processing, confirmed, posted, failed
status_detailstringnoOperational (internal) status in uppercase, e.g. VALIDATED, POSTED, POSTING_FAILED. Companion to the public status catalog. Independent of result (posted is the debit/posting milestone, not provider success).
resultstringyesConsolidated business outcome for reconciliation (backup to webhooks). Independent of processing status. Derived as: reversed when has_reversal is true (highest priority); succeeded when succeeded_at is set and there is no reversal; failed when processing status is failed or posting_failed; otherwise pending. A posted payment that was later reversed has status=posted and result=reversed.Values: succeeded, failed, reversed, pending
succeeded_atstring (date-time)noProvider success confirmation timestamp (RFC 3339 UTC, second precision, Z). Null while the payment may already be posted internally but the provider has not confirmed success.Can be null.
has_reversalbooleanyesTrue when a reversal was applied. Independent of status. Correlates with webhook payment.reversed.
reversed_atstring (date-time)noWhen the reversal was applied. Null when has_reversal is false.Can be null.
created_atstring (date-time)noRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
updated_atstring (date-time)noRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
amountobjectnoSame monetary shape as payment create (PaymentAmount).
amount.amountstringyes—
amount.currencystringyes—
sourceobjectnoPayment party on read APIs. Nested fields are always present: null when the value is unknown. account is a public identifier (IBAN or PHONE); internal account UUIDs are never returned.
source.accountobjectyesPublic account identifier (type + value). null when the IBAN or phone cannot be resolved. Never an internal account UUID.Can be null.
source.account.typestringyesAccount identifier scheme (e.g. IBAN, PHONE). Not the internal account classification (e.g. operational).Values: IBAN, PHONE
source.account.valuestringyesWhen type is PHONE (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777").
source.account.currencystringnoOptional account currency in response payloads.
source.namestringyesParty name from processing metadata when available; otherwise null.Can be null.
source.identificationobjectyesParty identification (type catalog key + number). Object only when at least one field is known; otherwise null. Does not expose internal SINPE type codes (0–9); unmapped types are omitted (type: null). number is the partner-submitted public value (digits, no SINPE hyphens). Historical payments may still return the hyphenated SINPE display form.Can be null.
source.identification.typestringnoIdentification catalog key (e.g. cedula, juridica). null when unknown.Can be null.
source.identification.numberstringnoPublic identification number. New PIN payments return the partner-submitted value (digits, no SINPE hyphens). Historical payments may still return the SINPE display form (hyphenated, possibly padded).Can be null.
destinationobjectnoPayment party on read APIs. Nested fields are always present: null when the value is unknown. account is a public identifier (IBAN or PHONE); internal account UUIDs are never returned.
destination.accountobjectyesPublic account identifier (type + value). null when the IBAN or phone cannot be resolved. Never an internal account UUID.Can be null.
destination.account.typestringyesAccount identifier scheme (e.g. IBAN, PHONE). Not the internal account classification (e.g. operational).Values: IBAN, PHONE
destination.account.valuestringyesWhen type is PHONE (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777").
destination.account.currencystringnoOptional account currency in response payloads.
destination.namestringyesParty name from processing metadata when available; otherwise null.Can be null.
destination.identificationobjectyesParty identification (type catalog key + number). Object only when at least one field is known; otherwise null. Does not expose internal SINPE type codes (0–9); unmapped types are omitted (type: null). number is the partner-submitted public value (digits, no SINPE hyphens). Historical payments may still return the hyphenated SINPE display form.Can be null.
destination.identification.typestringnoIdentification catalog key (e.g. cedula, juridica). null when unknown.Can be null.
destination.identification.numberstringnoPublic identification number. New PIN payments return the partner-submitted value (digits, no SINPE hyphens). Historical payments may still return the SINPE display form (hyphenated, possibly padded).Can be null.
client_referencestringnoPartner reference sent at payment creation (stable after processing). Same field as webhook payment.client_reference.
external_referencestringnoRail/SINPE transaction id. Same field, location, and meaning as webhook payment.external_reference on payment.succeeded / payment.failed Sibling of client_reference. null if none.Can be null.
providerobjectnoShared provider snapshot on REST payment reads and webhook envelopes. Rail reference is only on payment.external_reference, not duplicated here. provider_status_* match webhook rejection.reason_code / message / code when the provider rejected the operation. null when there is no rejection.
provider.correlation_idstringyesProvider transaction correlation identifier. Sent to SINPE as the correlation id. Not channel_reference (e.g. MOBILE_APP). Distinct from webhook event.correlation_id (HTTP request id for this delivery). null when unknown.Can be null.
provider.occurred_atstring (date-time)yesRFC 3339 timestamp in UTC with second precision and a Z suffix. Fractional seconds are never emitted. Example: 2026-01-15T12:00:00Z.
provider.provider_status_codestringyesRaw provider Motivo/code (e.g. 31). Same meaning as webhook rejection.reason_code. null when there is no provider rejection.Can be null.
provider.provider_status_descstringyesProvider Motivo text (prefers Detalle). Same meaning as webhook rejection.message. null when there is no provider rejection.Can be null.
provider.provider_status_semanticstringyesNormalized platform semantic (e.g. ACCOUNT_BLOCKED). Same meaning as webhook rejection.code. null when there is no provider rejection.Can be null.
errorobjectnoAlways present. null when there is no failure information (succeeded, pending, or failed/reversed without taxonomy). Object uses the same domain / platform shape as webhook payment.failed.Can be null.
error.domainstringyesSame catalog as webhook payment.error.domain.Values: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal
error.platformobjectyes—
error.platform.codestringyesStable platform public code (same as webhook payment.error.platform.code).
error.platform.messagestringyesActionable message (prefers provider Detalle). Same as webhook payment.error.platform.message.

Response example

{
  "success": true,
  "http_status_code": 200,
  "response_code": "OK",
  "message": "The request was processed successfully.",
  "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "data": {
    "schema_version": "1.0.0",
    "payment_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "public_id": "10234",
    "type": "PAYOUT",
    "payment_method_code": "PIN",
    "status": "posted",
    "status_detail": "POSTED",
    "result": "succeeded",
    "succeeded_at": "2026-01-15T12:00:00Z",
    "has_reversal": false,
    "reversed_at": null,
    "created_at": "2026-01-15T12:00:00Z",
    "updated_at": "2026-01-15T12:00:00Z",
    "amount": {
      "amount": "1000.00",
      "currency": "CRC",
      "fees": "0.00",
      "taxes": "0.00"
    },
    "source": {
      "account": {
        "type": "IBAN",
        "value": "CR26036900101010588061",
        "currency": "CRC"
      },
      "name": "María González",
      "identification": {
        "type": "cedula",
        "number": "0205620043"
      }
    },
    "destination": {
      "account": {
        "type": "IBAN",
        "value": "CR16010400000000000031",
        "currency": "CRC"
      },
      "name": "Juan Perez",
      "identification": {
        "type": "cedula",
        "number": "0101010101"
      }
    },
    "client_reference": "INV-1001",
    "external_reference": "SINPE-20260115-000123",
    "provider": {
      "correlation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "occurred_at": "2026-01-15T12:00:00Z",
      "provider_status_code": "00",
      "provider_status_desc": "Aprobada",
      "provider_status_semantic": "SUCCESS"
    },
    "error": null
  }
}

Error responses

HTTPresponse_codeDescription
400INVALID_REQUESTInvalid request. Check the required fields and try again.
401UNAUTHORIZEDUnauthorized. Verify your session or credentials.
403FORBIDDENYou do not have permission to perform this action.
403ACCOUNT_ACCESS_DENIEDThe actor does not have access to the requested account.
404PAYMENT_NOT_FOUNDPayment not found.
500INTERNAL_ERRORAn unexpected error occurred. Please try again later.
502PROVIDER_ERRORUnable to complete the request with the payment provider. Please try again later.
503SERVICE_UNAVAILABLEA required service is temporarily unavailable. Please try again later.

Processing and limits#

API payment creation has no dual control: the payment is processed directly when the token, permissions and data are valid. Limits apply per transaction, daily and monthly, in amount and count. If any limit is exceeded, processing fails and the payment.failed event notifies the result.

The MCP server transfer-confirmation flow is separate from this direct API creation.

Last verified: 2026-10-22

View as raw Markdown