# Pagos del API Bancario

> Operaciones de pagos del API Bancario: crear, consultar, listar y buscar pagos PIN y SINPE Móvil.

- kind: api-operation
- status: stable
- api_version: 1.0.0
- last_verified: 2026-09-02
- url: https://www.tilopay.com/developers/api-bancario/pagos

El grupo `Payments` cubre el ciclo completo de un pago: crearlo, consultarlo por
identificador, listarlo con filtros y buscarlo por cualquiera de sus referencias.

```http
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
```

## Reglas del recurso [#reglas]

**Métodos.** `payment_method_code` admite `PIN` y `SINPE_MOVIL`. **Dirección.**
`direction` admite `OUT` (salida) e `IN` (entrada). **Cuenta destino.** se identifica por
`IBAN` o por `PHONE`, según el método.

**Referencia propia.** `client_reference` es tu identificador del pago y es único por
tenant: dos pagos del mismo cliente no pueden compartirlo. Sirve para buscar el pago sin
guardar el `payment_id`.

**Reintentos seguros.** La creación acepta `Idempotency-Key`; reenviar la misma llave con
el mismo cuerpo devuelve el pago original en vez de crear otro. Ver
[reintentos seguros](/developers/api-bancario/convenciones#reintentos).

## Estados [#estados]

Un pago recorre cinco estados: `pending`, `processing`, `confirmed`, `posted` y `failed`.

La reversión **no es un estado**: un pago revertido sigue en `posted` y se reconoce por
`result: reversed` y `has_reversal: true`, además del evento
[`payment.reversed`](/developers/api-bancario/webhooks). Consultá siempre esos dos campos
antes de dar un pago por definitivo.

## Operaciones [#operaciones]

### List customer payments [#get-transactions-payments]

```http
GET /api/public/v1/transactions/payments
```

Host: `https://api-baas-sandbox.tilopay.com` — requiere `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 ledger UUIDs).
For PIN payments created after public identification persistence,
`identification.number` is the partner-submitted public number (no SINPE hyphens);
older payments may still show the hyphenated SINPE form. See `docs/PAYMENT-IDENTIFICATION.md`.

**Parámetros**

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `X-Correlation-Id` | header | string | — | Optional 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. |
| `type` | query | string | sí | Account **identifier scheme** (e.g. `IBAN`). This is **not** the internal account classification (e.g. operational). Values: IBAN |
| `value` | query | string | sí | Account identifier value (IBAN). Spaces are ignored. |
| `status` | query | string | — | Optional 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_from` | query | string (date-time) | — | Inclusive lower bound for `created_at`. RFC 3339 (UTC `Z` or offset; optional fractional seconds). |
| `date_to` | query | string (date-time) | — | Inclusive upper bound for `created_at`. RFC 3339 (UTC `Z` or offset; optional fractional seconds). |
| `client_reference` | query | string | — | Filter by partner client reference (exact match against the value sent at payment creation). |
| `currency` | query | string | — | Filter by payment currency (ISO 4217, e.g. CRC). |
| `payment_method_code` | query | string | — | Filter by public payment method code. Values: PIN, SINPE_MOVIL |
| `direction` | query | string | — | Filter by payment direction. Accepts public values `OUT`/`IN` or `PAYOUT`/`PAYIN` (internally normalized to `payout`/`payin`). Values: OUT, IN, PAYOUT, PAYIN |
| `limit` | query | integer | — | Page size (default 20, max 100). |
| `cursor` | query | string | — | Opaque cursor from a previous response `pagination.next_cursor`. |

**200** — The request was processed successfully.

`response_code`: `OK`

Campos de `data`:

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `items` | array<object> | sí | — |
| `items[].amount` | object | sí | — |
| `items[].amount.amount` | string | sí | — |
| `items[].amount.currency` | string | sí | — |
| `items[].client_reference` | string | sí | — |
| `items[].created_at` | string | sí | — |
| `items[].destination` | object | sí | — |
| `items[].destination.account` | object | sí | — |
| `items[].destination.identification` | object | sí | — |
| `items[].destination.name` | string | sí | — |
| `items[].error` | object | sí | — |
| `items[].error.domain` | string | sí | — |
| `items[].error.platform` | object | sí | — |
| `items[].external_reference` | string | sí | — |
| `items[].has_reversal` | boolean | sí | — |
| `items[].payment_id` | string | sí | — |
| `items[].payment_method_code` | string | sí | — |
| `items[].provider` | object | sí | — |
| `items[].provider.correlation_id` | string | sí | — |
| `items[].provider.occurred_at` | string | sí | — |
| `items[].provider.provider_status_code` | string | sí | — |
| `items[].provider.provider_status_desc` | string | sí | — |
| `items[].provider.provider_status_semantic` | string | sí | — |
| `items[].public_id` | string | sí | — |
| `items[].result` | string | sí | — |
| `items[].reversed_at` | string | sí | — |
| `items[].schema_version` | string | sí | — |
| `items[].source` | object | sí | — |
| `items[].source.account` | object | sí | — |
| `items[].source.identification` | object | sí | — |
| `items[].source.name` | string | sí | — |
| `items[].status` | string | sí | — |
| `items[].status_detail` | string | — | — |
| `items[].succeeded_at` | string | sí | — |
| `items[].type` | string | sí | — |
| `items[].updated_at` | string | sí | — |
| `pagination` | object | — | — |
| `pagination.limit` | integer | sí | — |
| `pagination.next_cursor` | string | — | — |
| `schema_version` | string | sí | — |

**Respuestas de error**

| HTTP | response_code | Descripción |
|---|---|---|
| 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. |
| 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. |
| 403 | `FORBIDDEN`, `ACCOUNT_ACCESS_DENIED` | You do not have permission to perform this action. |
| 429 | `TOO_MANY_REQUESTS` | Too many requests. Please retry after a short delay. |
| 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. |
| 502 | `PROVIDER_ERROR` | Unable to complete the request with the payment provider. Please try again later. |
| 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. |

### Create payment [#post-transactions-payments]

```http
POST /api/public/v1/transactions/payments
```

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

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 GX/SINPE hyphenated 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.

**Parámetros**

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `X-Correlation-Id` | header | string | — | Optional 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-Key` | header | string | — | Optional 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. |

**Cuerpo del request**

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `payment_method_code` | string | sí | Public payment method catalog (`PIN`, `SINPE_MOVIL`). Values: PIN, SINPE_MOVIL |
| `direction` | string | sí | Operation direction (OUT = payout from internal source) Values: OUT, IN |
| `country_code` | string | — | Operational country when multi-country applies |
| `amount` | object | sí | — |
| `amount.amount` | string | — | — |
| `amount.currency` | string | — | — |
| `source` | object | sí | — |
| `source.account` | object | sí | — |
| `source.account.type` | string | sí | Account **identifier scheme**. Only `IBAN` is supported here. This is not the internal account classification (e.g. operational). Values: IBAN |
| `source.account.value` | string | sí | IBAN (spaces allowed; normalized server-side) |
| `source.name` | string | sí | Account holder name |
| `source.identification` | object | sí | Counterparty 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 GX/SINPE; that internal form is not echoed on public payment reads for payments created after public identification persistence. 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.type` | string | — | Identification 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). |
| `source.identification.number` | string | — | Public 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 GX/SINPE display form. Supported types include `cedula`, `juridica`, `dimex`, `gobierno`, `institucion_autonoma`, `didi`, and `pasaporte`. |
| `destination` | object | sí | — |
| `destination.account` | object | sí | — |
| `destination.account.type` | string | sí | Counterparty account **identifier scheme** (`IBAN` for PIN, `PHONE` for SINPE_MOVIL). Not the internal account classification. Values: IBAN, PHONE |
| `destination.account.value` | string | sí | — |
| `destination.name` | string | sí | Counterparty name |
| `destination.identification` | object | sí | Counterparty 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 GX/SINPE; that internal form is not echoed on public payment reads for payments created after public identification persistence. 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.type` | string | — | Identification 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). |
| `destination.identification.number` | string | — | Public 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 GX/SINPE display form. Supported types include `cedula`, `juridica`, `dimex`, `gobierno`, `institucion_autonoma`, `didi`, and `pasaporte`. |
| `detail_reference` | string | sí | Operational / transactional description stored on the payment |
| `client_reference` | string | — | Partner functional reference. Unique per tenant among in-flight and completed payments; a failed attempt can be retried with the same value. |
| `channel_reference` | string | — | — |
| `originator` | object | — | Optional Ultimate Debtor / Travel Rule originator information. Informational only in this release; no validation is applied. Stored under payment metadata when provided. |
| `originator.company` | object | — | — |
| `originator.company.legal_name` | string | — | — |
| `originator.company.id_type` | string | — | — |
| `originator.company.id_number` | string | — | — |
| `originator.customer` | object | — | — |
| `originator.customer.name` | string | — | — |
| `originator.customer.id_type` | string | — | — |
| `originator.customer.id_number` | string | — | — |

**201** — The resource was created successfully.

`response_code`: `CREATED`

Campos de `data`:

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `created_at` | string | sí | — |
| `payment_id` | string | sí | — |
| `payment_method_code` | string | sí | — |
| `public_id` | string | sí | — |
| `status` | string | sí | — |
| `status_detail` | string | sí | — |

**Respuestas de error**

| HTTP | response_code | Descripción |
|---|---|---|
| 400 | `PAYMENT_INVALID_PAYLOAD`, `INVALID_PAYMENT_METHOD`, `INVALID_ACCOUNT_TYPE`, `ACCOUNT_INVALID`, `ACCOUNT_HOLDER_MISMATCH` | Invalid payment request payload. |
| 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. |
| 403 | `FORBIDDEN`, `ACCOUNT_ACCESS_DENIED` | You do not have permission to perform this action. |
| 404 | `ACCOUNT_NOT_FOUND` | Account not found. |
| 409 | `IDEMPOTENCY_CONFLICT`, `PAYMENT_DUPLICATE`, `CONFLICT` | Idempotency conflict: the same Idempotency-Key was reused with a different request body, or a previous request is still in progress. |
| 422 | `INSUFFICIENT_FUNDS`, `LIMIT_EXCEEDED`, `UNPROCESSABLE_ENTITY` | Insufficient funds to complete the payment. |
| 429 | `TOO_MANY_REQUESTS` | Too many requests. Please retry after a short delay. |
| 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. |
| 502 | `PROVIDER_ERROR` | Unable to complete the request with the payment provider. Please try again later. |
| 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. |

### Search payment by identifier [#post-transactions-payments-search]

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

Host: `https://api-baas-sandbox.tilopay.com` — requiere `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 public `identification.number` for PIN
payments created after public identification persistence.
If several payments share a `client_reference` (failed attempt plus retry), the in-flight or completed payment is returned when one exists.

**Parámetros**

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `X-Correlation-Id` | header | string | — | Optional 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. |

**Cuerpo del request**

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `account` | object | sí | — |
| `account.type` | string | sí | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE |
| `account.value` | string | sí | — |
| `account.currency` | string | — | Optional account currency in response payloads. |
| `payment_id` | string (uuid) | — | Internal payment UUID. |
| `public_id` | string | — | Numeric public payment identifier. |
| `client_reference` | string | — | Partner reference sent at payment creation. |

**Ejemplo de request** — byPaymentId

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

**Ejemplo de request** — byPublicId

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

**Ejemplo de request** — byClientReference

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

**200** — The request was processed successfully.

`response_code`: `OK`

Campos de `data`:

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | object | sí | — |
| `amount.amount` | string | sí | — |
| `amount.currency` | string | sí | — |
| `client_reference` | string | sí | — |
| `created_at` | string | sí | — |
| `destination` | object | sí | — |
| `destination.account` | object | sí | — |
| `destination.account.currency` | string | — | — |
| `destination.account.type` | string | sí | — |
| `destination.account.value` | string | sí | — |
| `destination.identification` | object | sí | — |
| `destination.identification.number` | string | sí | — |
| `destination.identification.type` | string | sí | — |
| `destination.name` | string | sí | — |
| `error` | object | sí | — |
| `error.domain` | string | sí | — |
| `error.platform` | object | sí | — |
| `error.platform.code` | string | sí | — |
| `error.platform.message` | string | sí | — |
| `external_reference` | string | sí | — |
| `has_reversal` | boolean | sí | — |
| `payment_id` | string | sí | — |
| `payment_method_code` | string | sí | — |
| `provider` | object | sí | — |
| `provider.correlation_id` | string | sí | — |
| `provider.occurred_at` | string | sí | — |
| `provider.provider_status_code` | string | sí | — |
| `provider.provider_status_desc` | string | sí | — |
| `provider.provider_status_semantic` | string | sí | — |
| `public_id` | string | sí | — |
| `result` | string | sí | — |
| `reversed_at` | string | sí | — |
| `schema_version` | string | sí | — |
| `source` | object | sí | — |
| `source.account` | object | sí | — |
| `source.account.currency` | string | — | — |
| `source.account.type` | string | sí | — |
| `source.account.value` | string | sí | — |
| `source.identification` | object | sí | — |
| `source.identification.number` | string | sí | — |
| `source.identification.type` | string | sí | — |
| `source.name` | string | sí | — |
| `status` | string | sí | — |
| `status_detail` | string | — | — |
| `succeeded_at` | string | sí | — |
| `type` | string | sí | — |
| `updated_at` | string | sí | — |

**Respuestas de error**

| HTTP | response_code | Descripción |
|---|---|---|
| 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. |
| 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. |
| 403 | `FORBIDDEN`, `ACCOUNT_ACCESS_DENIED` | You do not have permission to perform this action. |
| 404 | `PAYMENT_NOT_FOUND` | Payment not found. |
| 429 | `TOO_MANY_REQUESTS` | Too many requests. Please retry after a short delay. |
| 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. |
| 502 | `PROVIDER_ERROR` | Unable to complete the request with the payment provider. Please try again later. |
| 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. |

### Get payment by id [#get-transactions-payments-payment-id]

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

Host: `https://api-baas-sandbox.tilopay.com` — requiere `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 ledger UUIDs.
`identification.number` on PIN payments created after public identification persistence
is the partner-submitted public number (digits, no SINPE hyphens). Older payments may
still return the hyphenated SINPE form from processing metadata.
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`.

**Parámetros**

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `X-Correlation-Id` | header | string | — | Optional 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_id` | path | string | sí | Internal payment UUID or numeric public_id. |

**200** — The request was processed successfully.

`response_code`: `OK`

Campos de `data`:

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | object | sí | — |
| `amount.amount` | string | sí | — |
| `amount.currency` | string | sí | — |
| `client_reference` | string | sí | — |
| `created_at` | string | sí | — |
| `destination` | object | sí | — |
| `destination.account` | object | sí | — |
| `destination.account.currency` | string | — | — |
| `destination.account.type` | string | sí | — |
| `destination.account.value` | string | sí | — |
| `destination.identification` | object | sí | — |
| `destination.identification.number` | string | sí | — |
| `destination.identification.type` | string | sí | — |
| `destination.name` | string | sí | — |
| `error` | object | sí | — |
| `error.domain` | string | sí | — |
| `error.platform` | object | sí | — |
| `error.platform.code` | string | sí | — |
| `error.platform.message` | string | sí | — |
| `external_reference` | string | sí | — |
| `has_reversal` | boolean | sí | — |
| `payment_id` | string | sí | — |
| `payment_method_code` | string | sí | — |
| `provider` | object | sí | — |
| `provider.correlation_id` | string | sí | — |
| `provider.occurred_at` | string | sí | — |
| `provider.provider_status_code` | string | sí | — |
| `provider.provider_status_desc` | string | sí | — |
| `provider.provider_status_semantic` | string | sí | — |
| `public_id` | string | sí | — |
| `result` | string | sí | — |
| `reversed_at` | string | sí | — |
| `schema_version` | string | sí | — |
| `source` | object | sí | — |
| `source.account` | object | sí | — |
| `source.account.currency` | string | — | — |
| `source.account.type` | string | sí | — |
| `source.account.value` | string | sí | — |
| `source.identification` | object | sí | — |
| `source.identification.number` | string | sí | — |
| `source.identification.type` | string | sí | — |
| `source.name` | string | sí | — |
| `status` | string | sí | — |
| `status_detail` | string | — | — |
| `succeeded_at` | string | sí | — |
| `type` | string | sí | — |
| `updated_at` | string | sí | — |

**Respuestas de error**

| HTTP | response_code | Descripción |
|---|---|---|
| 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. |
| 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. |
| 403 | `FORBIDDEN`, `ACCOUNT_ACCESS_DENIED` | You do not have permission to perform this action. |
| 404 | `PAYMENT_NOT_FOUND` | Payment not found. |
| 429 | `TOO_MANY_REQUESTS` | Too many requests. Please retry after a short delay. |
| 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. |
| 502 | `PROVIDER_ERROR` | Unable to complete the request with the payment provider. Please try again later. |
| 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. |

### Validate external account for a payment method [#post-accounts-validate]

```http
POST /api/public/v1/accounts/validate
```

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

Validates a counterparty account (IBAN or phone) via the SINPE provider for the given payment method.
Does not validate internal ledger accounts.
When valid, `data.holder.id_type` uses the same identification catalog keys as
`identification.type` in `POST /payments` (e.g. `cedula`, `juridica`, `dimex`).

## SINPE_MOVIL field availability (expected, stable)

For `payment_method_code = SINPE_MOVIL`, the backing wallet operation
(`ObtenerInfoMonedero`) does not return identification type or institution name.
A successful validation **may** therefore return:

- `data.holder.id_type` = `unknown`
- `data.financial_institution.entity_name` = `""` (empty string)

This is **expected, stable behavior**, not a mapping bug or a transient provider error.
PIN (`ObtenerInformacionCuentaSinpe`) does populate both fields.

Integrators must not report these values as defects, and must not assume that
`holder.id_type` or `financial_institution.entity_name` are always populated
when `is_valid` is true.

## SINPE rejection codes (`data.rejection`)

When `is_valid` is `false`, `data.rejection` explains why (see the `rejected`
example on the `200` response below). `rejection.code` is one of a fixed set of
normalized semantics (see the `WebhookRejection.code` schema enum). **Most rows in
the table below do NOT get a distinct `code`** — only a subset of `reason_code`
values has a dedicated semantic; every other `reason_code`, even one listed below
with a specific `message`, returns `code = SINPE_REJECTED` (e.g. `reason_code = 21`
"fondos insuficientes" still returns `SINPE_REJECTED`). This same object and
catalog is also used by the `payment.failed` webhook's `rejection` field.
A `reason_code` not listed here at all still produces a response — `message` falls
back to a generic text and `code` falls back to `SINPE_REJECTED`.

**Cuenta / perfil / límites**

| Code | Message |
|---|---|
| 21 | Cuenta Cliente con fondos insuficientes |
| 22 | Cuenta Cliente no admite créditos |
| 23 | Cuenta Cliente cerrada |
| 24 | Cuenta Cliente inactiva |
| 25 | Cuenta Cliente no admite débitos |
| 26 | Cuenta Cliente no es de fondos |
| 27 | Moneda de la Cuenta Cliente no corresponde |
| 28 | Cuenta cliente no existe |
| 29 | Cuenta Cliente no registrada en el SINPE |
| 30 | Cuenta Cliente no habilitada para el servicio |
| 31 | Cuenta Cliente bloqueada |
| 32 | Id cliente destino no coincide con registrado en la entidad |
| 33 | Nombre del cliente destino no coincide con el registrado en la entidad |
| 34 | Cuenta Cliente en proceso de cierre |
| 35 | Cuenta Cliente embargada |
| 36 | Cuenta Cliente con retención judicial |
| 37 | Cuenta de expediente simplificado no permite el monto indicado |
| 38 | Límite transaccional de la Cuenta Cliente excedido |
| 39 | Cuenta Cliente incorrecta |
| 40 | IBAN de la cuenta destino inválido |
| 41 | IBAN de la cuenta origen inválido |
| 42 | Tipo de cuenta no permite la transacción |
| 43 | Cuenta Cliente no pertenece a la entidad indicada |
| 44 | Producto de la cuenta no admite el servicio |
| 45 | Cuenta Cliente en estado de cancelación |
| 46 | Cuenta Cliente restringida por política de la entidad |
| 47 | Cuenta Cliente no permite pagos inmediatos |
| 48 | Cuenta Cliente no permite SINPE Móvil |
| 49 | Titular de la cuenta destino fallecido |
| 50 | Cuenta Cliente consolidada o migrada |
| 51 | Identificación del cliente origen no coincide |
| 52 | Identificación del cliente destino inválida |
| 53 | Cliente destino no autorizado para recibir el pago |
| 54 | Cliente origen no autorizado para enviar el pago |
| 55 | Perfil del cliente origen no permite la transacción |
| 56 | Monto inferior al mínimo permitido |
| 57 | Monto superior al máximo permitido |
| 58 | Cantidad de transacciones diarias excedida |
| 59 | Cantidad de transacciones mensuales excedida |
| 60 | Límite acumulado diario excedido |
| 61 | Límite acumulado mensual excedido |
| 62 | Comisión no pudo ser aplicada |
| 63 | Tipo de cambio no disponible |
| 64 | Transacción rechazada por control de lavado de dinero |
| 65 | Transacción rechazada por listas de control |
| 66 | Transacción en revisión de cumplimiento |
| 67 | Documento de respaldo requerido no presente |
| 68 | Firma o autenticación inválida |
| 69 | Token o segundo factor inválido |
| 70 | Sesión de usuario expirada |
| 71 | Usuario no autorizado para el canal |
| 72 | Dispositivo no registrado |
| 73 | Geolocalización no permitida |
| 74 | Operación no soportada en la moneda indicada |
| 75 | Operación no soportada para el tipo de cliente |
| 76 | Problemas de comunicación |
| 77 | Tiempo de espera agotado en la entidad origen |
| 78 | Tiempo de espera agotado en el SINPE |
| 79 | Error interno de la entidad origen |
| 80 | Error interno de la entidad destino |
| 81 | Entidad origen no disponible |
| 82 | Entidad destino no encontrada |
| 83 | Problemas en la respuesta del destino |
| 84 | Respuesta de la entidad origen incorrecta |
| 85 | Mensaje con formato electrónico inválido |
| 86 | Versión del estándar electrónico no soportada |
| 87 | Campo obligatorio no informado |
| 88 | Campo con valor fuera de catálogo |
| 89 | Checksum o integridad del mensaje inválida |
| 90 | Referencia SINPE duplicada |
| 91 | Moneda no corresponde |
| 92 | Transacción no autorizada por entidad destino |
| 93 | Transacción no autorizada por cliente destino |
| 94 | Transacción no autorizada por entidad origen |
| 95 | Transacción no autorizada por cliente origen |
| 96 | Reverso no permitido para el estado de la transacción |
| 97 | Reverso ya aplicado |
| 98 | Confirmación no permitida para el estado de la transacción |
| 99 | Liquidación no permitida para el estado de la transacción |
| 100 | Entidad Destino no disponible para procesar en tiempo real |
| 101 | Entidad origen no disponible para procesar en tiempo real |
| 102 | Servicio PIN no habilitado para la entidad destino |
| 103 | Servicio PIN no habilitado para la entidad origen |
| 104 | Código de entidad destino inválido |
| 105 | Código de entidad origen inválido |
| 106 | Código de país de la entidad destino inválido |
| 107 | Código de país de la entidad origen inválido |
| 108 | Número de referencia interna inválido |
| 109 | Número de referencia SINPE inválido |
| 110 | Transacción no se encuentra en un estado que permita la consulta |

**Compensación con entidad destino**

| Code | Message |
|---|---|
| 201 | Tiempo respuesta excedido por la entidad destino |
| 202 | Respuesta de la entidad destino incorrecta según el estándar electrónico |
| 203 | Se recibió una excepción de la entidad destino |
| 204 | Error de comunicación con la entidad destino |
| 205 | Falló procesamiento en el SINPE |
| 206 | Transacción no autorizada por cliente destino |
| 207 | Perfil transaccional del cliente destino no permite recibir el pago |
| 208 | Falló la acreditación en la cuenta destino |
| 209 | Falló el débito en la cuenta origen |
| 210 | Conciliación de la transacción no fue posible |

**Identificación**

| Code | Message |
|---|---|
| 801 | Identificación inválida |
| 802 | Identificación del cliente origen no encontrada |
| 803 | Identificación del cliente destino no encontrada |
| 804 | Identificación no vigente |
| 805 | Identificación vencida |
| 806 | Identificación no corresponde al tipo indicado |
| 807 | Tipo de identificación inválido |
| 808 | País de la identificación no soportado |
| 809 | Identificación de menor de edad no permitida |
| 810 | Identificación de persona jurídica no permitida para el servicio |

**Validación de formato / Core Bancario**

| Code | Message |
|---|---|
| 1001 | Cuenta cliente activa |
| 1002 | El Id de cliente destino no cumple con el formato esperado por el SINPE |
| 1003 | El Id de cliente origen no fue informado |
| 1004 | El Id de cliente origen no cumple con el formato esperado por el SINPE |
| 1005 | Monto con formato inválido |
| 1006 | Moneda con formato inválido |
| 1007 | Problemas de comunicación con el Core Bancario |
| 1008 | Core Bancario no disponible |
| 1009 | El valor para el campo no puede ser nulo o infringir su longitud mínima o máxima |
| 1010 | El valor para el campo no corresponde al tipo de dato esperado |
| 1011 | El valor para el campo no corresponde al catálogo permitido |
| 1012 | Fecha con formato inválido |
| 1013 | Hora con formato inválido |
| 1014 | Número de referencia con formato inválido |
| 1015 | IBAN con formato inválido |

**Canal / tipo de identificación**

| Code | Message |
|---|---|
| 1040 | Canal no informado |
| 1041 | Canal inválido |
| 1042 | Canal no corresponde |
| 1043 | Canal no habilitado para la entidad |
| 1044 | Canal no habilitado para el servicio |
| 1045 | El formato de la identificación es inválido |
| 1046 | Tipo de identificación no informado |
| 1080 | Tipo de identificación no corresponde al cliente origen |
| 1081 | Tipo de identificación no corresponde al cliente destino |
| 1082 | Tipo de identificación no vigente |
| 1083 | Tipo de identificación no soportado por el servicio |
| 1084 | Tipo de identificación inválido |
| 1085 | Tipo de identificación no soportado por la entidad |

**SINPE Móvil (monedero)**

| Code | Message |
|---|---|
| 15300 | El número de teléfono origen indicado es inválido |
| 15301 | El número de teléfono origen no tiene activo el Servicio Monedero |
| 15302 | El número de teléfono destino indicado es inválido |
| 15303 | El número de teléfono destino no está registrado en el padrón móvil del BCCR |
| 15304 | No es posible inactivar el monedero indicado pues no existe |
| 15305 | El número de teléfono indicado ya se encuentra activo como monedero en el padrón local |

⚠️ This catalog is under review — some entries (notably `208` and `209`)
are known to be pending verification against the official SINPE source and
may be corrected in a future revision without notice.

**Parámetros**

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `X-Correlation-Id` | header | string | — | Optional 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. |

**Cuerpo del request**

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `payment_method_code` | string | sí | Payment method used to route validation (PIN for external IBAN, SINPE_MOVIL for phone wallet). Values: PIN, SINPE_MOVIL |
| `account` | object | sí | — |
| `account.type` | string | sí | Values: IBAN, PHONE |
| `account.value` | string | sí | — |

**Ejemplo de request** — pin

```json
{
  "payment_method_code": "PIN",
  "account": {
    "type": "IBAN",
    "value": "CR16010400000000000031"
  }
}
```

**Ejemplo de request** — sinpe_movil

```json
{
  "payment_method_code": "SINPE_MOVIL",
  "account": {
    "type": "PHONE",
    "value": "88887777"
  }
}
```

**200** — The request was processed successfully.

`response_code`: `OK`

Campos de `data`:

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `account` | object | sí | — |
| `account.currency` | string | sí | — |
| `account.product_name` | string | sí | — |
| `account.type` | string | sí | — |
| `account.value` | string | sí | — |
| `financial_institution` | object | sí | — |
| `financial_institution.country_code` | string | sí | — |
| `financial_institution.entity_code` | string | sí | — |
| `financial_institution.entity_name` | string | sí | — |
| `holder` | object | sí | — |
| `holder.id_number` | string | sí | — |
| `holder.id_type` | string | sí | — |
| `holder.name` | string | sí | — |
| `is_valid` | boolean | sí | — |
| `provider` | object | sí | — |
| `provider.correlation_id` | string | — | — |
| `provider.occurred_at` | string | — | — |
| `provider.operation` | string | sí | — |
| `rejection` | object | sí | — |
| `rejection.code` | string | sí | — |
| `rejection.message` | string | sí | — |
| `rejection.reason_code` | string | sí | — |

**Respuestas de error**

| HTTP | response_code | Descripción |
|---|---|---|
| 400 | `INVALID_REQUEST`, `INVALID_ACCOUNT_TYPE`, `INVALID_ACCOUNT_FORMAT`, `INVALID_PAYMENT_METHOD` | Invalid request. Check the required fields and try again. |
| 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. |
| 403 | `FORBIDDEN`, `ACCOUNT_ACCESS_DENIED` | You do not have permission to perform this action. |
| 422 | `UNPROCESSABLE_ENTITY` | The request is well-formed but cannot be processed due to business rules. |
| 429 | `TOO_MANY_REQUESTS` | Too many requests. Please retry after a short delay. |
| 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. |
| 502 | `PROVIDER_ERROR` | Unable to complete the request with the payment provider. Please try again later. |
