# Tilopay Developer and AI Agent Portal — full text > Tilopay technical documentation (SDK, API, webhooks, testing) in Markdown, with nothing normative locked inside images. Language: English. Index: https://tilopay.com/en/llms.txt ## Machine-readable artifacts - [openapi.json](https://tilopay.com/developers/openapi.json): OpenAPI 3.1 spec of the Tilopay API, source of truth for endpoints, parameters and responses. - [openapi.yaml](https://tilopay.com/developers/openapi.yaml): Same spec, in YAML. - [mcp.json](https://tilopay.com/developers/mcp.json): Catalog of the tools of the Tilopay MCP server, with parameters, outputs and access level. - [api-bancario/openapi.json](https://tilopay.com/developers/api-bancario/openapi.json): OpenAPI 3.0.3 spec of the Tilopay Banking API (BaaS), version 1.0.0, a separate artifact from the acquiring API. - [api-bancario/openapi.yaml](https://tilopay.com/developers/api-bancario/openapi.yaml): Same spec, in YAML. - [skills/index.md](https://tilopay.com/en/developers/skills/index.md) - [cierre-de-caja/SKILL.md](https://tilopay.com/developers/skills/cierre-de-caja/SKILL.md) - [cobrar-por-whatsapp/SKILL.md](https://tilopay.com/developers/skills/cobrar-por-whatsapp/SKILL.md) - [rechazos-y-recuperacion/SKILL.md](https://tilopay.com/developers/skills/rechazos-y-recuperacion/SKILL.md) - [conciliacion-mensual/SKILL.md](https://tilopay.com/developers/skills/conciliacion-mensual/SKILL.md) - [cfo-de-bolsillo/SKILL.md](https://tilopay.com/developers/skills/cfo-de-bolsillo/SKILL.md) - [catalogo-y-cobros-fijos/SKILL.md](https://tilopay.com/developers/skills/catalogo-y-cobros-fijos/SKILL.md) --- # Tilopay for developers > The four integration paths, the field contract, the SDK reference, the API and webhooks. - kind: guide - status: stable - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers Step 0: there are no shared sandbox credentials. Every developer registers and receives their own at https://tilopay.com/en/developers/registro ## Four integration paths - **No code** — extensions and plugins for existing platforms: /en/developers/sin-codigo - **Hosted payment page** — your server requests a URL from the API and redirects the customer to a Tilopay page: /en/developers/hosted-payment-page - **JavaScript SDK** — the card fields live in your page and the SDK sends them straight to Tilopay; it accepts cards, SINPE Móvil and Apple Pay: /en/developers/sdk - **Server-to-server API** — restricted to PCI-certified merchants; the URL is provisioned per merchant: /en/developers/server-to-server ## Reference - OpenAPI 3.1 spec: /developers/openapi.json - Acquiring API: /en/developers/api/autenticacion - Webhooks: /en/developers/api/webhooks - Form fields: /en/developers/sdk/campos-del-formulario - Apple Pay with the SDK: /en/developers/sdk/apple-pay - Banking API (restricted access): /en/developers/api-bancario - Banking API OpenAPI 3.0.3 spec: /developers/api-bancario/openapi.json --- # Developer registration > How to get sandbox credentials to test Tilopay integrations. - kind: guide - status: stable - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/registro There are no shared or public sandbox credentials: every developer registers and receives their own. This is the step before any of the four integration paths. ## How to get them 1. Complete the developer registration at https://tilopay.com/en/developers/registro 2. Tilopay enables a test account and issues that merchant's credentials. 3. With those credentials you obtain the API token (/en/developers/api/autenticacion) or the SDK token. ## Test and production There is no separate sandbox host: test mode is switched from the merchant account. See /en/developers/entornos --- # No-code integration > No-code integration: plugins and connectors for Shopify, WooCommerce, Wix, VTEX and hotel PMS. - kind: guide - status: stable - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/sin-codigo This is the lowest-friction path: the merchant installs the plugin or connector for their platform and configures Tilopay as a payment method from that platform's admin. No code is written and card data never passes through the merchant. ## Platforms with a plugin or connector The directory lists 83 platforms, each linking to its setup guide when one exists. ### E-commerce - **Shopify** — La única integración nativa en Centroamérica certificada por Shopify desde 2022. Activa Tilopay en tu tienda, recibe tarjetas de cualquier banco y expande tus ventas por la región sin límites. (https://tilopay.com/plataformas/shopify) - **Wix** — Integra el plug-in nativo más confiable de Centroamérica y el Caribe a tu página en Wix. Acepta tarjetas Mastercard, Visa y American Express sin complicaciones. (https://tilopay.com/plataformas/wix) - **WooCommerce** — Integra el plug-in certificado de Tilopay por WooCommerce en tu página web. Acepta tarjetas Mastercard, Visa y American Express sin complicaciones. (https://tilopay.com/plataformas/woocommerce) - **VTEX** — Acepta tarjetas Mastercard, Visa y American Express sin complicaciones en tu tienda online VTEX de Centroamérica y el Caribe. (https://tilopay.com/plataformas/vtex) - **Avify** — La integración de Tilopay a Avify para cobrar con tarjeta por WhatsApp para Centroamérica y el Caribe. (https://tilopay.com/plataformas/avify) - **Take.App** — Vende por WhatsApp con tu catálogo en línea de Take.App y cobra con tarjetas Mastercard, Visa y American Express mediante Tilopay. - **Blume** — Acepta tarjetas Mastercard, Visa y American Express sin complicaciones en tu tienda online Blume de Centroamérica y el Caribe. - **BigCommerce** — Acepta tarjetas Mastercard, Visa y American Express sin complicaciones en tu tienda online en BigCommerce de Centroamérica y el Caribe. - **Adobe Commerce / Magento** — Extensión oficial de Tilopay para Adobe Commerce y Magento Open Source 2.4. Cobra con tarjeta dentro de tu propio checkout, con 3DS, cuotas Tasa 0, SINPE Móvil, Yappy y Apple Pay. (https://tilopay.com/plataformas/adobe-commerce-cloud) - **Ecwid** — Acepta tarjetas Mastercard, Visa y American Express sin complicaciones en tu tienda online Ecwid de Centroamérica y el Caribe. ### Lodging and PMS - **Cloudbeds** — Centraliza la gestión de tu hotel y acepta pagos de todo el mundo con tarjetas Mastercard, Visa y American Express de forma fluida a través de Cloudbeds. (https://tilopay.com/plataformas/cloudbeds) - **Bookinglayer** — Acepta tarjetas Mastercard, Visa y American Express de todo el mundo, sin complicaciones a través de Bookinglayer. (https://tilopay.com/plataformas/bookinglayer) - **Orbe Booking** — Acepta tarjetas Mastercard, Visa y American Express de todo el mundo en tus reservas, sin complicaciones utilizando el motor de reservas en línea Orbe. (https://tilopay.com/plataformas/orbe-booking) - **Mangobeds** — Impulsa las reservas directas de tu alojamiento y acepta las principales tarjetas del mundo sin complicaciones utilizando el motor de Mangobeds. - **ChargeAutomation** — Acepta tarjetas Mastercard, Visa y American Express de todo el mundo, sin complicaciones a través de ChargeAutomation. (https://tilopay.com/plataformas/chargeautomation) - **ThinkReservations** — Aumenta tus reservas, simplifica operaciones y construye un negocio más rentable con nuestra integración en ThinkReservations. - **SimpleBooking** — Acepta tarjetas Mastercard, Visa y American Express de todo el mundo en tus reservas, sin complicaciones utilizando el motor de reservas en línea SimpleBooking. - **Avantio** — Acepta tarjetas Mastercard, Visa y American Express sin complicaciones en tu negocio en Avantio de Centroamérica y el Caribe. - **Opera** — Optimiza la operación de tu hotel y procesa los pagos de tus huéspedes con tarjetas Mastercard, Visa y American Express de forma segura a través de Opera. - **Booking Automation** — Integración disponible a través de ChargeAutomation. - **Smoobu** — Integración disponible a través de ChargeAutomation. - **Clock** — Integración disponible a través de ChargeAutomation. - **Beds24** — Integración disponible a través de ChargeAutomation. - **Guesty** — Integración disponible a través de ChargeAutomation. - **Tokeet** — Integración disponible a través de ChargeAutomation. - **Hostaway** — Integración disponible a través de ChargeAutomation. - **Hostfully** — Integración disponible a través de ChargeAutomation. - **OwnerRez** — Integración disponible a través de ChargeAutomation. - **Hospitable** — Integración disponible a través de ChargeAutomation. - **Godo** — Integración disponible a través de ChargeAutomation. - **Hosthub (Syncbnb)** — Integración disponible a través de ChargeAutomation. - **Hostify** — Integración disponible a través de ChargeAutomation. - **Uplisting** — Integración disponible a través de ChargeAutomation. - **Lodgify** — Integración disponible a través de ChargeAutomation. - **BookingSync** — Integración disponible a través de ChargeAutomation. - **myVR** — Integración disponible a través de ChargeAutomation. - **Protel** — Integración disponible a través de ChargeAutomation. - **AbodeBooking** — Integración disponible a través de ChargeAutomation. - **Brillantez Solutions** — Integración disponible a través de ChargeAutomation. - **GuestCentrix** — Integración disponible a través de ChargeAutomation. - **Easyrez** — Integración disponible a través de ChargeAutomation. - **eZee Absolute** — Integración disponible a través de ChargeAutomation. - **eZee FrontDesk** — Integración disponible a través de ChargeAutomation. - **FNSROOMS** — Integración disponible a través de ChargeAutomation. - **Front2Go** — Integración disponible a través de ChargeAutomation. - **Hotellinking** — Integración disponible a través de ChargeAutomation. - **Hoteliga** — Integración disponible a través de ChargeAutomation. - **Hoteltime Solutions** — Integración disponible a través de ChargeAutomation. - **InnKey** — Integración disponible a través de ChargeAutomation. - **Little Hotelier** — Integración disponible a través de ChargeAutomation. - **Mews** — Integración disponible a través de ChargeAutomation. - **MisterBooking** — Integración disponible a través de ChargeAutomation. - **NewBook** — Integración disponible a través de ChargeAutomation. - **Occupancy Plus** — Integración disponible a través de ChargeAutomation. - **Preno** — Integración disponible a través de ChargeAutomation. - **Resort Data Processing** — Integración disponible a través de ChargeAutomation. - **Roomraccoon** — Integración disponible a través de ChargeAutomation. - **Room Ranger** — Integración disponible a través de ChargeAutomation. - **Shalom** — Integración disponible a través de ChargeAutomation. - **Sirvoy** — Integración disponible a través de ChargeAutomation. - **Ulyses Cloud** — Integración disponible a través de ChargeAutomation. - **VHP** — Integración disponible a través de ChargeAutomation. - **Winhms** — Integración disponible a través de ChargeAutomation. - **W2** — Integración disponible a través de ChargeAutomation. - **Zavia ERP** — Integración disponible a través de ChargeAutomation. - **3RPMSa** — Integración disponible a través de ChargeAutomation. - **Autoclerk** — Integración disponible a través de ChargeAutomation. - **Hansaworld** — Integración disponible a través de ChargeAutomation. - **Hotel Link Solutions** — Integración disponible a través de ChargeAutomation. - **Ibelsa** — Integración disponible a través de ChargeAutomation. - **Revenue Control Data** — Integración disponible a través de ChargeAutomation. - **Roomer** — Integración disponible a través de ChargeAutomation. - **Servo IT Solutions** — Integración disponible a través de ChargeAutomation. - **Softmogul** — Integración disponible a través de ChargeAutomation. - **Suitech** — Integración disponible a través de ChargeAutomation. - **Thais Soft** — Integración disponible a través de ChargeAutomation. - **TK Technology** — Integración disponible a través de ChargeAutomation. - **Sympl** — Integración disponible a través de ChargeAutomation. - **5Stelle** — Integración disponible a través de ChargeAutomation. - **Planyo** — Integración disponible a través de ChargeAutomation. ### Services and bookings - **Wstudio.app** — Acepta tarjetas Mastercard, Visa y American Express de todo el mundo, sin complicaciones a través de Wstudio.app. (https://tilopay.com/plataformas/wstudio-app) - **SimplyBook** — Facilita la agenda de tus clientes y automatiza el cobro de tus citas con tarjetas Mastercard, Visa y American Express mediante SimplyBook. ### ERP and CRM - **Odoo ERP & CRM** — Integra el software empresarial Odoo con Tilopay para aceptar pagos en diversos métodos. --- # API · Authentication > How to get the API token and the SDK token, how long each lasts, and how to send them on every call. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/autenticacion The whole API and the SDK live on a single host: `https://app.tilopay.com`. There is no separate host for testing — see [environments](/en/developers/entornos). ## The two operations [#operaciones] | Operation | Path | What for | |---|---|---| | API token | `POST /api/v1/login` | Server-to-server API calls | | SDK token | `POST /api/v1/loginSdk` | The token passed to `Tilopay.Init()` | ## Get the API token [#token-api] ```bash curl -X POST https://app.tilopay.com/api/v1/login \ -H "Content-Type: application/json" \ -d '{ "apiuser": "YOUR_APIUSER", "password": "YOUR_PASSWORD" }' ``` ### Body [#cuerpo] The merchant API user. The merchant API password. ### Response [#respuesta] The token to send on subsequent calls. Always `bearer`. Token lifetime as an **integer in seconds** (for example `86400`). The exact value comes in this response: do not assume it on the client. ## Get the SDK token [#token-sdk] ```bash curl -X POST https://app.tilopay.com/api/v1/loginSdk \ -H "Content-Type: application/json" \ -d '{ "apiuser": "YOUR_APIUSER", "password": "YOUR_PASSWORD", "key": "YOUR_API_KEY" }' ``` The resulting token is the one passed to the `token` parameter of [`Tilopay.Init()`](/en/developers/sdk/reference/init). ## Lifetime [#vigencia] - **API token: 24 hours.** `expires_in` arrives as an **integer in seconds** (for example `86400`). - **SDK token: 1 hour.** `expires_in` arrives as a **date-time string** (for example `"2023-06-05 13:32:06"`). The exact value always arrives in `expires_in`, in each operation's format. Cache it server-side and renew on expiry. ## How it is sent [#envio] ```http Authorization: bearer ``` ## Revocation and limits [#revocacion] - **Tokens cannot be revoked.** If a token leaks there is no operation to invalidate it: treat the leak as an incident and rotate credentials with Tilopay. - **There is no limit** on concurrent tokens per merchant. You can request one per process without invalidating the previous ones. ## Where credentials come from [#credenciales] `apiuser`, `password` and `key` are obtained in the merchant panel, at [Admin panel · Platform integrations](https://admin.tilopay.com/platform-integrations). If you do not have access yet, start with [developer registration](/en/developers/registro). > **Warning:** Never put `apiuser` or `password` in the browser. The login runs on your server; only the > SDK token reaches the browser. --- # Hosted payment page > The integration path where the customer pays on a page hosted by Tilopay and your server receives the result. - kind: guide - status: stable - api_version: v1 - last_verified: 2026-08-28 - url: https://tilopay.com/en/developers/hosted-payment-page ## What it is [#que-es] Your server requests a payment URL from the API, redirects the customer to that Tilopay page and receives the result on your callback URL. Card data is typed on a Tilopay page: **you never see a card number** and your merchant server stays out of that flow. The API operation for this path lives in [API · processPayment](/en/developers/api/hosted-payment-page/process-payment). ## Difference from a payment link [#diferencia-link] - **Hosted payment page**: your server requests a payment URL via API and redirects the customer to it. Best for integrated checkout flows in your site or app. - **Payment link**: a reusable URL created via API or the dashboard and shared by message, email, social media or QR. The customer pays when convenient; no redirect from your flow is needed. - In short: HPP for integrated, one-time checkout; payment links for shared, low-front-end charges. ## Who it is for [#para-quien] For teams that take payments with their own code but prefer not to build or maintain the card form. What you need is backend work: request the URL and handle the callback. ## What you need before starting [#requisitos] 1. **Your own credentials.** There are no shared sandbox credentials: every developer gets theirs through [developer registration](/en/developers/registro). 2. **API authentication.** See [authentication](/en/developers/api/autenticacion). 3. **A callback URL of yours** that can receive the transaction result. ## How to confirm the state of a charge [#confirmacion] The result arriving on your callback is not the only signal available, and not every error response travels with an HTTP 4xx: - Read [how to read an error response](/en/developers/api/procesos-operativos/errors) before declaring success. - Configure [webhooks](/en/developers/api/webhooks) for the events you care about. - If a call fails without a clear response, resolve the state with [safe retries](/en/developers/reintentos-seguros). ## Environments [#entornos] Testing and production share the same host. See [environments](/en/developers/entornos) before taking real money. ## Compliance [#cumplimiento] Together with the no-code path, this is the one that exposes your infrastructure the least. Confirm the applicable scope with your compliance team. ## If you prefer the form on your own site [#alternativa] Use the [JavaScript SDK](/en/developers/sdk): the design is yours and the data still does not pass through your server. --- # Environments: testing and production > Testing and production share one host. The mode is switched from the account and verified with the environment field returned by Init(). - kind: concept - status: stable - api_version: v1 - last_verified: 2026-10-21 - url: https://tilopay.com/en/developers/entornos > **Warning:** There is no sandbox host. Testing and production use the same host: > `app.tilopay.com`. The URL does not tell you which mode you are in. ## How the mode is switched [#como-se-cambia] The mode is switched **from the account in the Tilopay portal**, not from the integration. There is no parameter, header or alternate URL that changes it from code. ## How to verify it [#como-se-verifica] `Tilopay.Init()` returns an **`environment`** field: | Value | Meaning | |---|---| | `"PROD"` | Production | | `"TEST"` | Testing | That is the only signal available at runtime. Read it and surface it in your own checkout while developing. ## The real risk [#riesgo] > **Warning:** Because the host is the same, an integrator can believe they are testing while actually > being in production, taking real money. Verify that `environment` is `"TEST"` before assuming > you are testing; make an unexpected `"PROD"` fail loudly in development. ## Test cards [#tarjetas] In testing mode you charge with the [test cards](/en/developers/tarjetas-de-prueba): each one lists the number, brand, expected result and whether it opens the 3DS challenge screen. --- # Test cards > The 27 cards of the Tilopay testing environment, with the expected result and the 3DS friction of each one. - kind: guide - status: stable - api_version: v1 - last_verified: 2026-10-21 - url: https://tilopay.com/en/developers/tarjetas-de-prueba These cards only work when the account is in **testing mode**. The mode is switched from the account and verified with the `environment` field returned by `Init()` (`"TEST"` in testing): see [environments and testing](/en/developers/entornos). ## How to use them [#uso] | Field | What to enter | | --- | --- | | Expiry date | Any date in the future | | CVV | Any value | | Code on the 3DS challenge screen | `3ds2` | > **Warning:** On the 3DS challenge screen the code to enter is `3ds2`. There is no other code and nothing is > sent over SMS in testing. ## Cards [#tarjetas] The brand comes from the prefix: `4` Visa, `51`–`55` Mastercard, `34` American Express. | Number | Brand | Expected result | Friction | | --- | --- | --- | --- | | `4111111111111111` | Visa | Approved | No friction | | `4012000000020089` | Visa | Approved | No friction | | `4012000000020006` | Visa | Approved | No friction | | `4012010000020088` | Visa | Approved | No friction | | `5100270000000072` | Mastercard | Approved | No friction | | `5100270000000031` | Mastercard | Approved | No friction | | `341111000000009` | American Express | Approved | No friction | | `341111000000011` | American Express | Approved | No friction | | `341112000000001` | American Express | Approved | No friction | | `341111000000037` | American Express | Approved | No friction | | `341112000008012` | American Express | Approved | No friction | | `4012000000020071` | Visa | Approved | 3DS challenge | | `4012010000020070` | Visa | Approved | 3DS challenge | | `4012010000020005` | Visa | Approved | 3DS challenge | | `5100270000000023` | Mastercard | Approved | 3DS challenge | | `5100271000000120` | Mastercard | Approved | 3DS challenge | | `4112613451591116` | Visa | Insufficient funds | 3DS challenge | | `4523080346468525` | Visa | Incorrect CVV | No friction | | `4549179990476733` | Visa | Restricted card | No friction | | `4012000000020121` | Visa | Declined | No friction | | `4111111111119999` | Visa | Declined | No friction | | `4111111111110000` | Visa | Declined | No friction | | `5100270000000056` | Mastercard | Declined | No friction | | `5111111111113333` | Mastercard | Declined | No friction | | `4666666666662222` | Visa | Declined | No friction | | `5555666666662222` | Mastercard | Declined | No friction | | `5100270000000098` | Mastercard | Declined | 3DS challenge | Cards with **3DS challenge** friction open the authentication screen; **No friction** cards resolve without it. The expected result is always the same: the card determines the response, not the amount. --- # Safe retries and orderNumber > The acquiring API is not idempotent. Exactly what to do when a payment call dies on a timeout. - kind: concept - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/reintentos-seguros > **Warning:** The Tilopay acquiring API is not idempotent. There is no idempotency key and no retry that returns the > original transaction. If you come from other processors, this is the assumption to dismantle > before writing code. ## `orderNumber` is unique forever [#ordernumber] `orderNumber` is unique per merchant **across their entire operation**, forever. It does not reset daily, monthly or seasonally. If it repeats, **the new transaction is rejected** with the response **"Transacción duplicada"** (duplicate transaction). It does not return the original transaction: it returns a rejection. ## The timeout problem [#timeout] When a payment call dies on a network error or timeout, you do not know whether the transaction was created. And neither intuitive way out works: | What you would do | What happens | |---|---| | Retry with the same `orderNumber` | Duplicate rejection, even if the first one went through | | Retry with a new `orderNumber` | Risk of double charge | **On a network timeout there is no safe retry.** ## What to do instead [#que-hacer] Query the state before deciding: 1. Call `POST /api/v1/consult` with the original `orderNumber`. 2. If the transaction exists, use its result. Do not retry. 3. If it does not exist, then you can retry — and you can reuse the same `orderNumber`, because it was not consumed. ```text payment → timeout │ └─→ consult(original orderNumber) ├─ exists → use that result └─ not found → retry ``` ## Design consequences [#diseno] - Generate the `orderNumber` in your system **before** calling the payment and persist it. If you generate it on the fly, after a timeout you have nothing to query with. - Never derive the `orderNumber` from something that can repeat (recycled cart number, truncated timestamp, resettable counter). - A "Transacción duplicada" does not mean the charge failed: it means that `orderNumber` was already used. Query before showing the customer an error. --- # API · How to read an error response > An HTTP 200 does not mean success. Where the issuer decline arrives and how to write error handling. - kind: concept - status: stable - api_version: v1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api/procesos-operativos/errors ## The issuer decline [#rechazo] When the issuer declines a transaction, the response carries two fields: The issuer decline code. The decline text. ## An HTTP 200 does not mean success [#estado-http] > **Warning:** There is no defined standard for when the API responds HTTP 4xx and when it responds HTTP 200 > with the error in the body. Both forms coexist, and there are five different error envelopes > depending on the endpoint. > > Practical consequence: your error handling must always look at the body, not only the status > code, and tolerate more than one body shape. ## Error body shapes [#formas-del-cuerpo] `processModification` error (`type` 300 or 400): ```json {"type":"400","message":"Order number: TYP974_172 does not exist"} ``` Five different envelopes, depending on the endpoint: | Envelope | Endpoints | |---|---| | `{"error": "Unauthorized"}` | `login`, `loginSdk` | | `{"type", "message"}` | `processPayment`, `processModification` | | `{"type", "result"}` | `processRecurrentPayment`, `processTokenize` | | `{"code", "description", "error"}` | server to server | | `{"code", "description"}` | issuer decline | ## How to write error handling [#como-manejar] - Treat `code` and `description` as data to log and display, not as an enumeration to branch on. - Do not write `if (status === 200) success`. Check the body. - Store the raw response of every decline: it is what lets you reconstruct your merchant's decline pattern. - To determine the real state of a transaction after a failure, use [safe retries](/en/developers/reintentos-seguros). --- # API · Four integration paths > The four ways to take payments with Tilopay, and the difference that decides which one fits you: where the card data travels. - kind: concept - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/caminos-de-integracion ## Four paths [#cuatro-caminos] There are four ways to take payments with Tilopay. They differ in two things: how much you build yourself, and where the card fields appear. Pick one and follow its guide; all of them start with your sandbox credentials, requested at [developer registration](/en/developers/registro). ### 1 · No code [#sin-codigo] Extensions and plugins for existing platforms. You do not write a line of code. [See path](/en/developers/sin-codigo) ### 2 · Hosted payment page [#hosted-payment-page] Your server requests a URL from the API, redirects the customer to that Tilopay page and receives the result on your callback URL. [See path](/en/developers/hosted-payment-page) ### 3 · JavaScript SDK [#sdk-javascript] The card fields live in your page; the SDK captures them and sends them straight to Tilopay. [See path](/en/developers/sdk) ### 4 · Server-to-server API [#servidor-a-servidor] Restricted access: a service exclusive to PCI-certified merchants. The URL is provisioned per merchant and is not public. [Requirements and request](/en/developers/server-to-server) ## How to choose [#como-elegir] Choosing the wrong path is expensive. The difference that matters most is where the card data travels. | Path | Who builds the form | Where card data travels | What you need to know | Compliance implication | | --- | --- | --- | --- | --- | | 1 · No code | The platform | Never pass through your systems | Setup inside the platform admin | Check with your compliance team | | 2 · Hosted payment page | Tilopay | Your customer types them on a secure Tilopay page; you never see a card number | Backend: request the URL and handle the callback | Check with your compliance team | | 3 · JavaScript SDK | Your site | Typed on your page but sent from the browser straight to Tilopay; they do not pass through your server | JavaScript frontend, plus backend for credentials | Check with your compliance team | | 4 · Server-to-server (restricted access) | Your site | Card data passes through your server | Requires your PCI certification and a URL provisioned by Tilopay | Check with your compliance team | > **Warning:** Path 4 is not self-serve: it is exclusive to merchants holding PCI certification, and the URL > is provisioned individually. It is also the only path that puts your server inside PCI DSS > scope. Paths 2 and 3 avoid that cost and you can start them today. ## After you choose [#despues-de-elegir] With the path picked, continue with [response conventions](/en/developers/api/convenciones) to learn how amounts are typed, and [how to read an error response](/en/developers/api/procesos-operativos/errors) before you put the code into production. [Saved card payments reference](/en/developers/api/cobros-tarjetas-guardadas). --- # API · Response conventions > How amounts are typed, where a JSON payload hides inside a string and why the same entity changes shape between fetching one and listing many. - kind: concept - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/convenciones The acquiring API responses have several details that you cannot infer by reading each operation on its own, and that break typed integrations. They live here because they apply to several operations at once. For error handling, see [how to read an error response](/en/developers/api/procesos-operativos/errors). ## Amounts come as strings, not numbers [#montos-cadena] > **Warning:** Amounts, discounts and totals are serialized as strings: `"discount": "40.00"`, > `"amount": "0.00"`, `"first_amount": "0.00"`, `"amount_max_daily": "1000.00"`. Any typed deserialization that declares those fields as numbers fails. Declare them as strings and convert them yourself, with your language's decimal type, not with floating point. It shows up, among others, in [get a coupon](/en/developers/api/recurrentes/cupones/get-coupon), [list coupons](/en/developers/api/recurrentes/cupones/get-coupons), [list plans](/en/developers/api/recurrentes/planes/get-plans) and [subscriber payments](/en/developers/api/recurrentes/suscriptores/suscriptor-payments). ## There is a JSON payload inside a string [#doble-encoding] In [subscriber payments](/en/developers/api/recurrentes/suscriptores/suscriptor-payments), the `data` field of each payment **is not an object: it is a string containing JSON**. ```json "data": "{\"first\":1,\"expire\":\"2024-12-28T19:05:29.572648Z\",\"amount\":0,\"activeAmount\":0,\"originalAmount\":0,\"key\":\"XXXX-XXXX-XXXX-XXXX-XXXX\"}" ``` You have to deserialize twice: first the response, then the contents of `data`. Treat that second deserialization the way you treat any external input: wrap it and log the raw string when it does not parse. ## The same entity changes shape between "get one" and "list" [#uno-vs-lista] This is not a documentation slip: the two operations return different shapes of the same coupon, and each needs its own mapping. | | [`getCoupon`](/en/developers/api/recurrentes/cupones/get-coupon) (one) | [`getRepeatCoupons`](/en/developers/api/recurrentes/cupones/get-coupons) (list) | | --- | --- | --- | | Creation date | `created_at`, ISO format | `create`, `Y-m-d H:i:s` format | | Other fields | includes `recurrent_id`, `updated_at` and `deleted_at` | does not include them | The same happens in [subscriber payments](/en/developers/api/recurrentes/suscriptores/suscriptor-payments), which also uses `create` with the `Y-m-d H:i:s` format. ## Two ways of saying "not configured" [#no-configurado] In [list plans](/en/developers/api/recurrentes/planes/get-plans), a plan's webhook fields come as an empty string when they are not configured, while `return_url` comes as `null`. Those are two representations of the same state in the same object: when checking whether a webhook is configured, treat the empty string and `null` as the same case. ## `status` and `type` may be numbers or strings [#status-type] In acquiring API responses, `status` and `type` may be numbers or strings. For example, [create payments](/en/developers/api/cobros-tarjetas-guardadas/set-payments) returns `"status": 200` in the success example and `"status": "400"` in the error example; [create a group](/en/developers/api/cobros-tarjetas-guardadas/set-group) returns `"type": "200"`. Accept both types when reading these indicators; do not assume a single type across all operations. --- # API · Get API token > API login operation: returns the bearer token that signs every other call. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/autenticacion/token-api ```http POST /api/v1/login ``` ## What it does [#que-hace] Returns the security token needed to consume the API services. The API user and password come from [Admin panel · Platform integrations](https://admin.tilopay.com/platform-integrations). ## Authentication [#autenticacion] It does not require a token: this is the operation that issues it. See [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Merchant API user, obtained in Admin panel · Platform integrations. Merchant API password, obtained in Admin panel · Platform integrations. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "apiuser": "", "password": "" } ``` ## Response [#respuesta] ```json { "access_token": "", "token_type": "bearer", "expires_in": 86400 } ``` ## Error responses [#respuestas-de-error] Invalid credentials → HTTP 401: ```json { "error": "Unauthorized" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · processPayment > Parameters, request example and response of processPayment, the operation that opens the payment form. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/hosted-payment-page/process-payment ```http POST /api/v1/processPayment ``` ## What it does [#que-hace] Returns the URL of the payment form for a purchase. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Site where you wait for the transaction response, for example `mywebsite.com`. It must support the **GET** method. Example response to the redirect: `mywebsite.com?code=1&description=Transaction%20is%20approved.&auth=123456&order=TPYS-881WROIBQA1405468&tpt=4300&crd=&tilopay-transaction=4300&OrderHash=b02927cb...&returnData=Tilopay-dataReturn123456789&form_update=ok`. When `code = 1` the transaction is approved; anything else is declined. `OrderHash` is a unique string per transaction: its algorithm is not public and is requested by writing to `soporte@tilopay.com`. Key associated with the customer. Obtained in Admin panel · Platform integrations. Purchase amount. Purchase currency in ISO format, for example USD, CRC, GTQ. Order number, it can be alphanumeric. Capture and authorize: 1 yes, 0 no. First name. Last names. Address. Address 2. City. State in ISO format, for example CR-SJ (San José, Costa Rica) or US-CA (California, USA). Post code. Country in ISO Alpha-2 code, for example CR (Costa Rica), US (USA) or GT (Guatemala). Telephone. Buyer email. First name. Last names. Address. Address 2. City. State in ISO format, for example CR-SJ (San José, Costa Rica) or US-CA (California, USA). Post code. Country in ISO Alpha-2 code, for example CR (Costa Rica), US (USA) or GT (Guatemala). Telephone. 1 to force the customer to save the card in Tilopay, 0 not to. Name of the platform where the transaction is generated. Value that Tilopay returns in the final response. Sending a base 64 string is recommended. Only used when implementing hash verification. Possible values [V1, V2]; V1 by default. Send as value "v2". ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "redirect": "https://www.urlToRedirect.com", "key": "", "amount": "1.00", "currency": "USD", "orderNumber": "1212122", "capture": "1", "billToFirstName": "DEMO", "billToLastName": "DEMO", "billToAddress": "San Jose", "billToAddress2": "Catedral", "billToCity": "JS", "billToState": "SJ", "billToZipPostCode": "10061", "billToCountry": "CR", "billToTelephone": "88888888", "billToEmail": "cliente@ejemplo.com", "shipToFirstName": "DEMO", "shipToLastName": "DEMO", "shipToAddress": "San Jose", "shipToAddress2": "Catedral", "shipToCity": "JS", "shipToState": "SJ", "shipToZipPostCode": "10061", "shipToCountry": "CR", "shipToTelephone": "88888888", "subscription": "0", "platform": "api", "returnData": "dXNlcl9pZD0xMg==", "hashVersion": "V2", "token_version": "v2" } ``` ## Response [#respuesta] ```json { "type": "100", "html": "Use url redirect", "url": "https://secure.tilopay.com/htmls/1212122_574572148file.html" } ``` `"type": "100"` means the form URL was generated: redirect the customer to `url` so they can complete the payment. ## Parameters that arrive at the redirect [#parametros-del-redirect] When the customer finishes the payment, Tilopay redirects them to your `redirect` with these parameters in the URL: | Parameter | What it is | |---|---| | `code` | `1` = transaction approved; any other value = declined | | `description` | Result text | | `auth` | Authorization code | | `order` | The `orderNumber` you sent | | `tpt` and `tilopay-transaction` | Transaction id in Tilopay | | `OrderHash` | Unique hash of the transaction | | `returnData` | The value you sent in the request | Before delivering the product or releasing the service, confirm the transaction status with `POST /api/v1/consult` from your server — see [consult](/en/developers/api/procesos-operativos/consult). ## Error responses [#respuestas-de-error] HTTP 300: ```json { "type": "300", "message": "Message error" } ``` HTTP 400: ```json { "type": "400", "message": "Message error" } ``` HTTP 402: ```json { "type": "402", "message": "Message error" } ``` HTTP 403: ```json { "type": "403", "message": "Message error" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Server-to-server > A service exclusive to PCI-certified merchants. The URL is provisioned per merchant: it is not self-serve. - kind: guide - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/server-to-server > **Warning:** Restricted access. This is not a self-serve path. You cannot start this integration on your own > today: it requires merchant PCI certification and a URL provisioned individually by Tilopay. ## What it is [#que-es] The path where card data passes through your server, which then sends it to the Tilopay API. ## Who it is for [#para-quien] - It is a **service exclusive to merchants holding PCI certification**. - The **URL is customized per merchant**, with its own key. **It is not a public URL.** That is why the examples on this page carry no real URL: the `Host` and the proxy key are delivered to each approved merchant. ## Requirements [#requisitos] 1. Valid PCI certification for your company. 2. An active Tilopay account. 3. An approved request, with the URL and key provisioned by Tilopay for your merchant. ## How to request it [#solicitud] Write to `soporte@tilopay.com` with your business name and the status of your PCI certification. The URL and key are delivered directly to the approved merchant. ## Request reference [#referencia] > **Warning:** The `Host` and the `tx-proxy-key` header are delivered by Tilopay to each approved merchant. There is > no public URL: the examples use placeholders. ```http POST / HTTP/1.1 Host: url provided by tilopay ``` ### Headers [#encabezados] `bearer {API token}` — see [authentication](/en/developers/api/autenticacion). Transaction proxy key, provided by Tilopay. ### Parameters [#parametros] Key tied to the merchant (required). Card number (required). Security code (required). Expiration date in monthYear format, for example `1023` (required). Cardholder first name (required). Cardholder last name (required). Customer phone (required). Customer email (required). Customer address (required). Customer city (required). State in ISO format, for example `CR-SJ` (San José, Costa Rica) or `US-CA` (California, USA) (required). Postal code (required). Country as ISO Alpha-2 code, for example `CR`, `US`, `GT` (required). Purchase amount (required). Currency in ISO format, for example `USD`, `CRC`, `GTQ` (required). Order number (required). `1` captures and authorizes, `0` only authorizes (required). Response URL (required). Optional shipping data: `shipToFirstName`, `shipToLastName`, `shipToAddress`, `shipToAddress2`, `shipToCity`, `shipToState`, `shipToZipPostCode`, `shipToCountry`, `shipToTelephone`. ### Request [#peticion] ```json { "key": "key", "card": "card PAN", "cvv": "CVV Number", "expire": "1023", "name": "firstname", "lastname": "lastname", "phone": "88888888", "email": "email@user.com", "address": "user address", "city": "city", "state": "state", "zipcode": "zipcode", "country": "country", "shipToFirstName": "Nombre", "shipToLastName": "Apellido", "shipToAddress": "San Jose", "shipToAddress2": "Escazu", "shipToCity": "San Jose", "shipToState": "SJ", "shipToZipPostCode": "10101", "shipToCountry": "CR", "shipToTelephone": "88778877", "amount": "amount", "currency": "currency code", "orderNumber": "Order Number", "capture": 1, "redirect": "url to redirect" } ``` The collection includes two examples, **with 3DS** and **without 3DS**, sharing the same request body. ### Response [#respuesta] Without 3DS: ```json { "code": "1", "description": "Transaccion aprobada", "auth": "123456", "orderNumber": "12345", "urlRedirect": "", "error": "" } ``` With 3DS: ```json { "code": "", "description": "", "auth": "", "orderNumber": "12457", "urlRedirect": "https://secure.tilopay.com/htmls/1026345966.html", "error": "" } ``` When the transaction requires 3DS authentication, the response carries the URL in `urlRedirect`; that is where the cardholder is sent. To interpret `code`, `description` and `error`, see [response conventions](/en/developers/api/convenciones) and [how to read an error response](/en/developers/api/procesos-operativos/errors). ## Alternatives without PCI certification [#alternativas] If you do not hold PCI certification, these paths avoid that scope entirely: - [Hosted payment page](/en/developers/hosted-payment-page) — Tilopay hosts the form. - [JavaScript SDK](/en/developers/sdk) — the form lives on your page, but the data travels from the browser straight to Tilopay. - [No code](/en/developers/sin-codigo) — a plugin or an already-integrated platform. --- # API · Payment links > Payment links operations of the Tilopay API, with parameters and examples. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/links-de-pago ## Operations [#operaciones] - [Create payment link](/en/developers/api/links-de-pago/create-link-payment) - [Payment link detail](/en/developers/api/links-de-pago/detail-link-payment) - [Delete a payment link](/en/developers/api/links-de-pago/delete-link-payment) - [List payment links](/en/developers/api/links-de-pago/payment-item-list) - [Get a payment link by id](/en/developers/api/links-de-pago/payment-by-id) Every operation lives on `https://app.tilopay.com` and, except for the login operations, requires the API token — see [authentication](/en/developers/api/autenticacion). --- # API · Create payment link > Payment link creation through the API, with response callback and webhook. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/links-de-pago/create-link-payment ```http POST /api/v1/createLinkPayment ``` ## What it does [#que-hace] Creates a payment link with amount, currency and usage behaviour. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Amount of the payment link. Currency code in ISO 4217 format. Reference of the payment link. Link type: 0 unlimited, 1 single use. Payment description. Customer name. It only applies when `type` is 1. Optional field. URL, over **GET**, the customer is redirected to with the response once the payment is completed. Example of the data sent: `code=Val&description=Val&auth=Val&tilopayLinkId=Val&orderNumber=Val&tilopayOrderId=Val&creditCardToken=Val&creditCardBrand=Val&last4CreditCardNumber=Val&orderHash=Val` Optional field. URL to send the response webhook to once the payment is completed. It must accept the **POST** method to receive the data. Example of the data sent: ```json { "code": "1", "codeDescription": "Aprobada", "auth": "123456", "tilopayLinkId": 54421, "orderNumber": "TPT123", "tilopayOrderId": 12233, "creditCardToken": "41111******11111", "creditCardBrand": "Visa", "last4CreditCardNumber": "1111", "linkDescription": "Descripcion del link de pago", "orderHash": "" } ``` ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "amount": "10", "currency": "USD", "reference": "123456", "type": 0, "description": "Description", "client": "Client name", "callback_url": "", "webhook_url": "" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Success", "url": "https://app.tilopay.com/link/MjMwMjQ=", "id": 1 } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Payment link detail > Detail of a payment link and its payments, queried by id. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/links-de-pago/detail-link-payment ```http GET /api/v1/getDetailLinkPayment/{link_payment_id}/{api_key} ``` ## What it does [#que-hace] Returns the detail of a payment link and its associated payments. It takes no body: the data travels in the path. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Path parameters, in the order they appear in the URL: Id of the payment link to query. Merchant api key. ## Response [#respuesta] ```json { "type": "200", "message": "Success", "detail": { "id": 6201, "amount": "45.00", "currency": "USD", "reference": "Producto de prueba 1 TP-Shop", "client": "NA", "type": "Unlimited", "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "callback_url": null, "create": "2022-10-24 08:45:14" }, "payments": [] } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Delete a payment link > Deletion of a payment link by id. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/links-de-pago/delete-link-payment ```http POST /api/v1/deleteLinkPayment ``` ## What it does [#que-hace] Deletes an existing payment link. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Payment link id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "35" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · List payment links > Paginated listing of the merchant's payment links. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/links-de-pago/payment-item-list ```http GET /api/v1/getLinkPaymentList/{api_key}/{limit} ``` ## What it does [#que-hace] Returns the paginated list of the merchant's payment links. It takes no body: the data travels in the path. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Path parameters, in the order they appear in the URL: Merchant api key. Number of records per page. ## Response [#respuesta] ```json { "type": "200", "message": "Success", "data": { "current_page": 1, "first_page_url": "http://app.tilopay.com/api/v1/getLinkPaymentList//10?page=1", "from": 1, "last_page": 2, "last_page_url": "http://app.tilopay.com/api/v1/getLinkPaymentList//10?page=2", "next_page_url": "http://app.tilopay.com/api/v1/getLinkPaymentList//10?page=2", "path": "http://app.tilopay.com/api/v1/getLinkPaymentList//10", "per_page": 10, "prev_page_url": null, "to": 10, "total": 16, "payment_link": [ { "id": 1, "amount": "45.00", "currency": "USD", "reference": "Producto de prueba 1 TP-Shop", "url": "https://admin.tilopay.com/link/3dNjE2MA3sdd==", "callback_url": null, "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/51087219_.png", "create": "2022-10-24 08:45:14" }, { "id": 2, "amount": "35.00", "currency": "USD", "reference": "Producto de prueba 2", "url": "https://admin.tilopay.com/link/dwNjE2see3MA==", "callback_url": null, "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/64156766_.png", "create": "2022-10-24 08:45:14" }, { "id": 3, "amount": "45.00", "currency": "USD", "reference": "Producto de prueba 3", "url": "https://admin.tilopay.com/link/f4NjE2MwsdA==", "callback_url": null, "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/88435879_.png", "create": "2022-10-24 08:45:14" }, { "id": 4, "amount": "45.00", "currency": "USD", "reference": "test", "url": "https://admin.tilopay.com/link/ceNjE2sxw2MA==", "callback_url": null, "description": "ddd", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/37716065_.png", "create": "2022-10-24 08:45:14" }, { "id": 5, "amount": "45.00", "currency": "USD", "reference": "test", "url": "https://admin.tilopay.com/link/cweNjE2Mfd5A==", "callback_url": null, "description": "ddd", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/37716065_.png", "create": "2022-10-24 08:45:14" }, { "id": 6, "amount": "45.00", "currency": "USD", "reference": "test", "url": "https://admin.tilopay.com/link/ce3NjE2fg5eMA==", "callback_url": null, "description": "ddd", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/37716065_.png", "create": "2022-10-24 08:45:14" }, { "id": 7, "amount": "45.00", "currency": "USD", "reference": "test", "url": "https://admin.tilopay.com/link/dsNjE2MAd32==", "callback_url": null, "description": "ddd", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/37716065_.png", "create": "2022-10-24 08:45:14" }, { "id": 8, "amount": "45.00", "currency": "USD", "reference": "test", "url": "https://admin.tilopay.com/link/NjE2sd32MA==", "callback_url": null, "description": "ddd", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/37716065_.png", "create": "2022-10-24 08:45:14" }, { "id": 9, "amount": "45.00", "currency": "USD", "reference": "test", "url": "https://admin.tilopay.com/link/sd3NjE2MA==", "callback_url": null, "description": "ddd", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/37716065_.png", "create": "2022-10-24 08:45:14" }, { "id": 10, "amount": "45.00", "currency": "USD", "reference": "test", "url": "https://admin.tilopay.com/link/d3dNjE2MA==", "callback_url": null, "description": "ddd", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/37716065_.png", "create": "2022-10-24 08:45:14" } ] } } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Get a payment link by id > Query of a single payment link by its id. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/links-de-pago/payment-by-id ```http GET /api/v1/getLinkPaymentById/{link_payment_id}/{api_key} ``` ## What it does [#que-hace] Returns a single payment link. It takes no body: the data travels in the path. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Path parameters, in the order they appear in the URL: Id of the payment link to query. Merchant api key. ## Response [#respuesta] ```json { "type": "200", "message": "Success", "data": { "id": 1, "amount": "45.00", "currency": "USD", "reference": "Producto de prueba 1 TP-Shop", "url": "https://admin.tilopay.com/link/loNj8E2MA==", "callback_url": null, "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "times_used": null, "picture": "https://storage.googleapis.com/tilo-uploads/items/51087219_.png", "create": "2022-10-24 08:45:14", "payments": [] } } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Recurring charges > Recurring charges operations of the Tilopay API, with parameters and examples. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes ## Operations [#operaciones] 16 operations in three subgroups: - [Plans](/en/developers/api/recurrentes/planes) — 5 - [Subscribers](/en/developers/api/recurrentes/suscriptores) — 7 - [Coupons](/en/developers/api/recurrentes/cupones) — 4 Every operation lives on `https://app.tilopay.com` and, except for the login operations, requires the API token — see [authentication](/en/developers/api/autenticacion). --- # API · Recurring · Plans > Recurring · Plans operations of the Tilopay API, with parameters and examples. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/planes ## Operations [#operaciones] - [Create recurring plan](/en/developers/api/recurrentes/planes/create-plan) - [Edit recurring plan](/en/developers/api/recurrentes/planes/edit-plan) - [Get a recurring plan](/en/developers/api/recurrentes/planes/get-plan) - [List recurring plans](/en/developers/api/recurrentes/planes/get-plans) - [Delete recurring plan](/en/developers/api/recurrentes/planes/delete-plan) Every operation lives on `https://app.tilopay.com` and, except for the login operations, requires the API token — see [authentication](/en/developers/api/autenticacion). --- # API · Create recurring plan > Recurring plan creation: frequencies, free trial, modalities and webhooks. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api/recurrentes/planes/create-plan ```http POST /api/v1/createPlanRepeat ``` ## What it does [#que-hace] Creates a subscription plan: charge frequency, currency, trial period, modalities and webhooks. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan title. Plan description. Charge frequency. | Code | Value | | --- | --- | | 1 | Daily | | 2 | Weekly | | 3 | Monthly | | 4 | Yearly | | 5 | Fortnightly | | 6 | Every two months | | 7 | Quarterly | | 8 | Every four months | | 9 | Every six months | Currency code in ISO 4217 format. Initial payment amount. Enables the free trial period: 0 no, 1 yes. Days of the free trial period. Number of retries for failed charges. Array of plan modalities. Optional field. The merchant's own thank-you URL; it must support the **GET** method. Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a customer successfully takes out a subscription. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'modality' : 'ModalityName', 'amount' : 25, 'frequency' : '', 'coupon' : '5HT5W8YT', 'free_trial' : 1, 'next_payment_date' : '2023-02-25'}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when the customer is charged successfully. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'amount' : 25, 'auth' : '123456', 'orderNumber' : 'PRE123456'}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a payment fails. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'amount' : 25}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a customer cancels the subscription to one of their plans. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'expire' : '2023-02-25'}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a customer reactivates the subscription to one of their plans. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'next_payment_date' : '2023-02-25'}` End date of the recurring plan, `d-m-Y` format (for example 25-09-2022). When the plan has no end date, send it empty. Set to 1 it adds the `notify_detail` and `notify_note` text to the notification email. Set to 0 it adds neither. Detail text in Spanish. Optional when `notify` is 0. Detail text in English. Optional when `notify` is 0. Notes text in Spanish. Optional when `notify` is 0. Notes text in English. Optional when `notify` is 0. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "title": "Plan title", "description": "Plan description", "frecuency": 1, "currency": "USD", "first_amount": 0, "trial": 0, "trial_days": 0, "attempts": 1, "modality": [ { "title": "Basic", "amount": 10 }, { "title": "Premium", "amount": 30 } ], "thanks_url": "", "webhook_subscribe": "", "webhook_payment": "", "webhook_rejected": "", "webhook_unsubscribe": "", "webhook_reactive": "", "end_at": "25-10-2023", "notify": 0, "notify_detail_es": "", "notify_detail_en": "", "notify_note_es": "", "notify_note_en": "" } ``` ## Response [#respuesta] Plan URL on `https://tp.cr` or `https://app.tilopay.com`. ```json { "type": "200", "status": 1, "message": "Success", "id": 624, "url": "https://app.tilopay.com/link/TmpJMHwx" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Edit recurring plan > Editing a recurring plan and its status. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api/recurrentes/planes/edit-plan ```http POST /api/v1/editPlanRepeat ``` ## What it does [#que-hace] Edits an existing plan, including its status. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. Recurring plan title. Plan description. Charge frequency. | Code | Value | | --- | --- | | 1 | Daily | | 2 | Weekly | | 3 | Monthly | | 4 | Yearly | | 5 | Fortnightly | | 6 | Every two months | | 7 | Quarterly | | 8 | Every four months | | 9 | Every six months | Currency code in ISO 4217 format. Initial payment amount. Enables the free trial period: 0 no, 1 yes. Days of the free trial period. Number of retries for failed charges. Status of the recurring plan. | Code | Value | | --- | --- | | 0 | Inactive | | 1 | Active | | 2 | Active but with no new sign-ups | Optional field. The merchant's own thank-you URL; it must support the **GET** method. Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a customer successfully takes out a subscription. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'modality' : 'ModalityName', 'amount' : 25, 'frequency' : '', 'coupon' : '5HT5W8YT', 'free_trial' : 1, 'next_payment_date' : '2023-02-25'}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when the customer is charged successfully. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'amount' : 25, 'auth' : '123456', 'orderNumber' : 'PRE123456'}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a payment fails. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'amount' : 25}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a customer cancels the subscription to one of their plans. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'expire' : '2023-02-25'}` Optional field. Webhook URL, over **POST**, that receives the callback in the request body when a customer reactivates the subscription to one of their plans. Example of the data sent: `{'id_plan' : 1, 'email' : 'email@email.com', 'next_payment_date' : '2023-02-25'}` End date of the recurring plan, `d-m-Y` format (for example 25-09-2022). When the plan has no end date, send it empty. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": 3, "title": "Plan title", "description": "Plan description", "frecuency": 2, "currency": "USD", "first_amount": 0, "trial": 0, "trial_days": 0, "attempts": 5, "status": 1, "thanks_url": "", "webhook_subscribe": "", "webhook_payment": "", "webhook_rejected": "", "webhook_unsubscribe": "", "webhook_reactive": "", "end_at": "25-10-2023" } ``` ## Response [#respuesta] Plan URL on `https://tp.cr` or `https://app.tilopay.com`. ```json { "type": "200", "status": 1, "message": "Success", "url": "https://app.tilopay.com/link/" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Get a recurring plan > Query of a recurring plan by id. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/planes/get-plan ```http POST /api/v1/getPlanRepeat ``` ## What it does [#que-hace] Returns a single recurring plan. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "3" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Success", "plan": { "id": 232, "title": "Prueba repeat", "description": "Plan repeat de prueba mensual", "frequency": 3, "currency": "USD", "first_amount": "5.00", "trial": 0, "trial_days": 0, "attempts": 5, "thanks_url": null, "webhook_subscribe": null, "webhook_payment": null, "webhook_rejected": null, "webhook_unsubscribe": null, "webhook_reactive": null, "modality": [ { "id": 292, "title": "Pago mensual prueba repeat modalidad", "amount": "10.00" } ], "end_at": null } } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · List recurring plans > Listing of the integration's recurring plans. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/recurrentes/planes/get-plans ```http POST /api/v1/getPlansRepeat ``` ## What it does [#que-hace] Returns the merchant's recurring plans. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Success", "data": [ { "id": 276, "title": "Plan test 1", "description": "Plan", "frequency": 1, "currency": "USD", "first_amount": "0.00", "trial": 0, "trial_days": 0, "attempts": 1, "status": 1, "thanks_url": "", "return_url": null, "webhook_subscribe": "", "webhook_payment": "", "webhook_rejected": "", "webhook_unsubscribe": "", "webhook_reactive": "", "modality": [ { "id": 131, "title": "Basic", "amount": "10.00" } ], "end_at": "2022-10-25" } ] } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Delete recurring plan > Deletion of a recurring plan by id. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/planes/delete-plan ```http POST /api/v1/deletePlanRepeat ``` ## What it does [#que-hace] Deletes a recurring plan. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "625" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Recurring · Subscribers > Recurring · Subscribers operations of the Tilopay API, with parameters and examples. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores ## Operations [#operaciones] - [Subscribers of a plan](/en/developers/api/recurrentes/suscriptores/get-suscriptor-plan) - [Edit subscriber](/en/developers/api/recurrentes/suscriptores/edit-suscriptor-plan) - [Pause subscriber](/en/developers/api/recurrentes/suscriptores/pause-suscriptor-plan) - [Reactivate subscriber](/en/developers/api/recurrentes/suscriptores/reactive-suscriptor-plan) - [Delete subscriber](/en/developers/api/recurrentes/suscriptores/delete-suscriptor-plan) - [Registration and renewal URL](/en/developers/api/recurrentes/suscriptores/consult-url) - [Subscriber payments](/en/developers/api/recurrentes/suscriptores/suscriptor-payments) Every operation lives on `https://app.tilopay.com` and, except for the login operations, requires the API token — see [authentication](/en/developers/api/autenticacion). --- # API · Subscribers of a plan > Query of the subscribers of a recurring plan. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores/get-suscriptor-plan ```http POST /api/v1/getSuscriptorRepeat ``` ## What it does [#que-hace] Returns the subscribers associated with a recurring plan. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "2" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Success", "suscriptor": [ { "id": 360, "name": "Jhon 1", "lastname": "D", "email": "customer1@example.com", "modality": "Pago mensual prueba repeat modalidad", "amount": "10.00", "expire": "2023-03-26", "coupon": "", "status": "Delete", "create": "2022-10-19 12:48:59" }, { "id": 33364, "name": "Jhon 2", "lastname": "D", "email": "customer2@example.com", "modality": "Pago mensual prueba repeat modalidad", "amount": "10.00", "expire": "2023-08-05", "coupon": "", "status": "Active", "create": "2023-06-05 14:24:18" } ] } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Edit subscriber > Editing the status and expiration of a subscriber. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores/edit-suscriptor-plan ```http POST /api/v1/editSuscriptorRepeat ``` ## What it does [#que-hace] Changes the status or the expiration date of a subscriber. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Subscriber id. Status of the subscriber. | Code | Value | | --- | --- | | 1 | Active | | 3 | Paused | | 4 | Deleted | Plan expiration date, `Y-m-d` format (for example 2023-10-25). ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "2", "status": "3", "expire": "2023-10-25" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Pause subscriber > Pausing a subscriber's subscription. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores/pause-suscriptor-plan ```http POST /api/v1/pauseSuscriptorRepeat ``` ## What it does [#que-hace] Pauses a subscriber's subscription. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Subscriber id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id_suscriptor": "1" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Reactivate subscriber > Reactivation of a subscriber's subscription. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores/reactive-suscriptor-plan ```http POST /api/v1/reactiveSuscriptorRepeat ``` ## What it does [#que-hace] Reactivates a paused subscriber's subscription. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Subscriber id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id_suscriptor": "1" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Delete subscriber > Deletion of a subscriber from a recurring plan. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores/delete-suscriptor-plan ```http POST /api/v1/deleteSuscriptorRepeat ``` ## What it does [#que-hace] Deletes a subscriber from a recurring plan. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Subscriber id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id_suscriptor": "1" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Registration and renewal URL > Registration or renewal URL of a recurring plan for a given email. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores/consult-url ```http POST /api/v1/recurrentUrl ``` ## What it does [#que-hace] Returns the registration URL of a recurring plan for a new user, or the renewal URL when the email is already registered. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. Email of the user the registration link is created for. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "1", "email": "email@user.com" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "", "url_register": "https://app.tilopay.com/admin/recurrent/register/MjMy?email=email@user.com", "url_renew": "" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Subscriber payments > Payment history of the subscribers of a recurring plan. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/recurrentes/suscriptores/suscriptor-payments ```http POST /api/v1/getSuscriptorPayments ``` ## What it does [#que-hace] Returns the payments of the subscribers of a recurring plan. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "1" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Success", "data": [ { "id": 150, "commerce_id": 37, "suscriptor_id": 86, "reference": "0", "currency": "USD", "amount": "0.00", "status": 0, "data": "{\"first\":1,\"expire\":\"2024-12-28T19:05:29.572648Z\",\"amount\":0,\"activeAmount\":0,\"originalAmount\":0,\"key\":\"XXXX-XXXX-XXXX-XXXX-XXXX\"}", "create": "2024-12-13 13:05:06" } ] } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Recurring · Coupons > Recurring · Coupons operations of the Tilopay API, with parameters and examples. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/recurrentes/cupones ## Operations [#operaciones] - [Create coupon](/en/developers/api/recurrentes/cupones/create-coupon) - [List coupons of a plan](/en/developers/api/recurrentes/cupones/get-coupons) - [Get a coupon](/en/developers/api/recurrentes/cupones/get-coupon) - [Delete coupon](/en/developers/api/recurrentes/cupones/delete-coupon) Every operation lives on `https://app.tilopay.com` and, except for the login operations, requires the API token — see [authentication](/en/developers/api/autenticacion). --- # API · Create coupon > Creation of discount coupons for recurring plans. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/recurrentes/cupones/create-coupon ```http POST /api/v1/createCoupon ``` ## What it does [#que-hace] Creates a discount coupon for a recurring plan: discount type, expiration, user and email permissions, and usage limits. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. User permission. | Code | Value | | --- | --- | | 0 | Allow new users only | | 1 | Allow already registered users | Discount type. | Code | Value | | --- | --- | | 1 | Percentage | | 2 | Fixed amount | Numeric value of the discount. Coupon expiration date, `Y-m-d` format (for example 2025-10-20). Email permission. | Code | Value | | --- | --- | | 1 | Specific emails | | 2 | Any email | Comma-separated list of emails. Required when `email_group` is sent as 1. Total number of uses. Total number of valid renewals. Total uses by the same user. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "planId": 10, "active_users": 1, "type_discount": 1, "discount": 40, "expire": "2025-10-20", "email_group": 1, "email": "cliente@ejemplo.com, cliente@ejemplo.com", "usage": 1, "renews": 2, "renews_by_user": 2 } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success", "id": 10788, "code": "E6RMS339" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · List coupons of a plan > Listing of the coupons of a recurring plan. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/recurrentes/cupones/get-coupons ```http POST /api/v1/getRepeatCoupons ``` ## What it does [#que-hace] Returns the coupons associated with a recurring plan. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Recurring plan id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "1" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Success", "data": [ { "id": 10787, "code": "TK7DPCHY", "active_users": 1, "type_discount": 1, "discount": "30.00", "expire": "2026-06-12", "email_group": 2, "email": null, "usage": 1, "renews": 1, "renews_by_user": 1, "usage_counter": 0, "create": "2026-06-12 15:17:34" } ] } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Get a coupon > Query of a recurring coupon by id. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/recurrentes/cupones/get-coupon ```http POST /api/v1/getCoupon ``` ## What it does [#que-hace] Returns a single coupon. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Coupon id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "1" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Success", "data": { "id": 10788, "recurrent_id": 10, "code": "E6RMS339", "active_users": 1, "type_discount": 1, "discount": "40.00", "expire": "2026-10-20", "email_group": 1, "email": "cliente@ejemplo.com, otro@ejemplo.com", "usage": 1, "renews": 2, "renews_by_user": 2, "usage_counter": 0, "created_at": "2026-09-02T20:12:01.000000Z", "updated_at": "2026-09-02T20:12:01.000000Z", "deleted_at": null } } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Delete coupon > Deletion of a recurring coupon by id. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/recurrentes/cupones/delete-coupon ```http POST /api/v1/deleteCoupon ``` ## What it does [#que-hace] Deletes a coupon. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Coupon id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "id": "1" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Credential on File (CoF) > Credential on File (CoF) operations of the Tilopay API, with parameters and examples. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/cof ## Operations [#operaciones] 3 operations: - [Tokenize card](/en/developers/api/cof/process-tokenize) — 1 - [Remove card](/en/developers/api/cof/remove-card) — 1 - [Charge](/en/developers/api/cof/realizar-cobro) — 1 Every operation lives on `https://app.tilopay.com` and, except for the login operations, requires the API token — see [authentication](/en/developers/api/autenticacion). --- # API · processTokenize > Card tokenization with processTokenize: parameters, request and response. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api/cof/process-tokenize ```http POST /api/v1/processTokenize ``` ## What it does [#que-hace] Tokenizes a card: returns the URL of the tokenization form. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Site where you wait for the transaction response. Key associated with the merchant. Cardholder email. Language [es, en]. Cardholder first name. Cardholder last name. Send as value "v2". ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "redirect": "https://urlToRedirect.com", "key": "", "email": "email@user.com", "language": "es", "firstname": "name", "lastname": "lastname", "token_version": "v2" } ``` ## Response [#respuesta] ```json { "type": "100", "url": "https://secure.tilopay.com/htmls/TOK-5063-740506121440_1648145457file.html" } ``` ## Error responses [#respuestas-de-error] ## Redirect parameters [#parametros-del-redirect] `token`, `code`, `description`, `auth`, `crd`, `order`, `tpt`, `brand`, `last-digits`, `tilopay-transaction`, `OrderHash`, `returnData`. The card token travels in `token` and `crd`; store it with the customer email. Use `brand` and `last-digits` to display the saved card. `crd` only has a value when the card was saved with `code = 1`. HTTP 300: ```json { "type": "300", "result": "Message error" } ``` HTTP 402: ```json { "type": "402", "result": "Message error" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Remove card > Removal of a saved card token from the merchant's private vault. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cof/remove-card ```http POST /api/v1/user/card-remove ``` ## What it does [#que-hace] Removes a saved card token. **Requirement:** the merchant must have a private token vault. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Key associated with the merchant. Customer email associated with the token. Token to remove. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "1234-1234-1234-1234-1234", "email": "email@card.com", "token": "411111.......1111" } ``` ## Response [#respuesta] ```json { "type": "200", "status": 1, "message": "Success" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · CoF · Charge > Server-to-server charge with a v2 card token through Credential on File. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cof/realizar-cobro ```http POST /api/v1/processRecurrentPayment ``` ## What it does [#que-hace] Processes a charge with an already tokenized card, without a form. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Key associated with the customer. Obtained in Admin panel · Platform integrations. Purchase amount. Purchase currency in ISO format, for example USD, CRC, GTQ. Order number, it can be alphanumeric. Capture and authorize: 1 yes, 0 no. Cardholder email. Card token. It must be the v2 token. Only if you implement hash verification. Values V1 or V2; defaults to V1. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "amount": "10.00", "currency": "USD", "orderNumber": "1213", "capture": "1", "email": "myemail@exapmle.com", "card": "511111_00GOB1111" } ``` ## Response [#respuesta] ```json { "type": "200", "response": "1", "description": "Transacción aprobada", "auth": "123456", "tpt": 734329, "order_id": "12135", "tilopayTransaction": 734329, "orderHash": "f3ad85046d249bc7a781eed5285a5ded92045a2e11108063504deec72ec9b338" } ``` ## Error responses [#respuestas-de-error] HTTP 300: ```json { "type": "300", "result": "Message error" } ``` HTTP 400: ```json { "type": "400", "result": "Message error" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Operational processes > Operational processes operations of the Tilopay API, with parameters and examples. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/procesos-operativos ## Operations [#operaciones] - [processModification](/en/developers/api/procesos-operativos/process-modification) - [consult](/en/developers/api/procesos-operativos/consult) - [consultTransactions](/en/developers/api/procesos-operativos/consult-transactions) - [Split settlement](/en/developers/api/procesos-operativos/split) These operations are cross-cutting: they work for any of the four [integration paths](/en/developers), no matter how the charge was originated. To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). Every operation lives on `https://app.tilopay.com` and, except for the login operations, requires the API token — see [authentication](/en/developers/api/autenticacion). --- # API · processModification > Capture, refund and reversal of a transaction with processModification. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api/procesos-operativos/process-modification ```http POST /api/v1/processModification ``` ## What it does [#que-hace] Modifies an already processed transaction: capture, refund or reversal. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Number of the order sent in the original charge. Type of modification. | Code | Value | | --- | --- | | 1 | Capture | | 2 | Refund | | 3 | Reversal | Amount to modify. Key associated with the customer. Obtained in Admin panel · Platform integrations. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "orderNumber": "1214352", "type": "2", "amount": "1.00", "key": "" } ``` ## Response [#respuesta-modificacion] `type: "100"` means the modification was processed; its outcome is in `ReasonCode` and `ReasonCodeDescription`. `1101` = approved. Approved: ```json {"type":"100","transactionId":954183,"ReasonCode":"1101","ReasonCodeDescription":"Transaccion aprobada","OrderHash":""} ``` Rejected: ```json {"type":"100","transactionId":954183,"ReasonCode":"3","ReasonCodeDescription":"Invalid Retrieval reference number","OrderHash":""} ``` Error (`type` 300 or 400): ```json {"type":"400","message":"Order number: TYP974_172 does not exist"} ``` `"100"` indicates that the modification was processed. Transaction identifier. Result code of the modification. Result text. Order hash. See the note about `orderHash` in [webhooks](/en/developers/api/webhooks#verificacion). To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · consult > Query the status and processed amount of a single transaction. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/procesos-operativos/consult ```http POST /api/v1/consult ``` ## What it does [#que-hace] Queries a specific transaction: returns the processed amount and the status of the transaction. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Order number being queried. Merchant id. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "orderNumber": "1212122", "merchantId": "" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "", "response": [ { "id_tilopay": 734312, "orderNumber": "1212122", "amount": "5.00", "currency": "USD", "merchantId": "88803956", "code": "1", "response": "Transacción aprobada", "auth": "123456", "commission": 0.18, "iva_commission": 0.02, "retention_iva": 0.27, "retention_rent": 0.09, "cost": 0.35, "cost_iva": 0.05, "net_to_liquidate": "4.04", "capture": "Capture", "card": "4021", "last": "5221", "environment": "Test", "type": "Payment", "date": "2024-07-31 16:59:20" } ] } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · consultTransactions > Bulk transaction query by date range, currency, order or email. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-08-29 - url: https://tilopay.com/en/developers/api/procesos-operativos/consult-transactions ```http POST /api/v1/consultTransactions ``` ## What it does [#que-hace] Queries transactions by date range, with optional filters. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Tilopay integration key. Start date, `Y-m-d H:i:s` format — for example "2022-01-15 00:00:00". End date, `Y-m-d H:i:s` format — for example "2022-08-01 23:59:59". Whether to return only approved transactions. | Code | Value | | --- | --- | | 0 | Any | | 1 | Approved only | Environment of the transactions to return. | Code | Value | | --- | --- | | 0 | Production | | 1 | Test | Array of currencies to return, for example ["USD", "CRC"]. Merchant id. Order number. Customer email. Authorization number. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "key": "", "startDate": "2023-06-01 00:00:00", "endDate": "2023-06-30 23:59:59", "onlyAproved": 1, "environment": 1, "currency": [ "USD", "CRC" ], "merchantId": "", "orderNumber": "12135", "email": "customer@example.com", "auth": "123456" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "", "response": [ { "id": 734329, "orderNumber": "12135", "amount": "5.00", "taxes": "0.00", "discount": "0.00", "discount_name": "", "currency": "USD", "merchantId": "", "code": "1", "response": "Transacción aprobada", "auth": "123456", "card": "4021", "last": "5221", "email": "customer@example.com", "commission": 0.18, "iva_commission": 0.02, "retention_iva": 0.27, "retention_rent": 0.09, "cost": 0.35, "cost_iva": 0.05, "net_to_liquidate": 4.04, "capture": "Capture", "type": "Payment", "environment": "Production", "date": "2024-07-31 16:59:20" }, { "id": 734330, "orderNumber": "12136", "amount": "5.00", "taxes": "0.00", "discount": "0.00", "discount_name": "", "currency": "USD", "merchantId": "", "code": "1", "response": "Transacción aprobada", "auth": "123456", "card": "4021", "last": "5221", "email": "customer@example.com", "commission": 0.18, "iva_commission": 0.02, "retention_iva": 0.27, "retention_rent": 0.09, "cost": 0.35, "cost_iva": 0.05, "net_to_liquidate": 4.04, "capture": "Capture", "type": "Payment", "environment": "Production", "date": "2024-07-31 17:25:35" } ] } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # API · Split settlement > Splitting the settlement of an order among several facilitated merchants. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/procesos-operativos/split ```http POST /api/v1/orders/liquidation/split ``` ## What it does [#que-hace] Splits the settlement of an order among several merchants. It only applies to merchants that use Tilopay as a payment facilitator, and the resulting orders are identified by the `SL|` prefix. Rules: - The merchants must be approved and be from the same country. - If the split is lower than the total amount of the order, the remainder is assigned to the merchant that made the original transaction. - It only applies to fully approved orders. ## Authentication [#autenticacion] Requires the API bearer token in the `Authorization` header — see [authentication](/en/developers/api/autenticacion). ## Parameters [#parametros] Id of the order approved in Tilopay. Associative array with the `email` and `amount` keys of every merchant the settlement is divided with. Including the owner merchant is optional: when its amount is not specified the remainder of the split is assigned to it, and when it is included and there is a remainder, the remainder is added to it. Request language. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "order_id": "1", "commerces": [ { "email": "commerce-1@example.com", "amount": "7.5" }, { "email": "commerce-2@example.com", "amount": "5.5" } ], "lang": "en" } ``` ## Response [#respuesta] ```json { "type": "200", "message": "Great", "description": "Order splitted successfully", "response": { "total_order_splitted": 3, "order_id": 1, "order_number": "PFC000069-TYP785237_313", "order_key": "", "order_currency": "USD", "order_amount": "33.00", "splitted_orders": [ { "id": 1, "amount": 20, "commerce_name": "Commerce owner name", "commerce_email": "owner@example.com" }, { "id": 2, "amount": 7.5, "commerce_name": "Commerce name", "commerce_email": "commerce-1@example.com" }, { "id": 3, "amount": 5.5, "commerce_name": "Commerce name", "commerce_email": "commerce-2@example.com" } ] } } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # Saved card payments > Saved card payments: enrollment, groups and immediate or scheduled payment batches; 10 POST operations. - kind: api-index - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas ## Flow [#flujo] 1. Create a group (optional). 2. Create an individual or group enrollment URL and share it. 3. The customer registers their card at that URL and becomes an affiliate, with daily and monthly maximum amounts. 4. Create immediate or date-scheduled charges to affiliates and/or groups. 5. Query the batches and their detail. The acquiring API is not idempotent: repeating `set/payments` creates another charge. [Safe retries](/en/developers/reintentos-seguros). ## Authentication [#autenticacion] All operations are POST on `https://app.tilopay.com` and require the API bearer token and the integration key (`key`) in the body. ## Operations [#operaciones] - [List groups](/en/developers/api/cobros-tarjetas-guardadas/get-groups) — `POST /api/v1/collect/get/groups` - [List affiliates](/en/developers/api/cobros-tarjetas-guardadas/get-affiliates) — `POST /api/v1/collect/get/affiliates` - [List payment batches](/en/developers/api/cobros-tarjetas-guardadas/get-massive) — `POST /api/v1/collect/get/massive` - [Get batch detail](/en/developers/api/cobros-tarjetas-guardadas/get-massive-detail) — `POST /api/v1/collect/get/massive/detail` - [Create group](/en/developers/api/cobros-tarjetas-guardadas/set-group) — `POST /api/v1/collect/set/group` - [Create individual enrollment URL](/en/developers/api/cobros-tarjetas-guardadas/set-url-affiliate) — `POST /api/v1/collect/set/urlaffiliate` - [Create group enrollment URL](/en/developers/api/cobros-tarjetas-guardadas/set-url-group) — `POST /api/v1/collect/set/urlgroup` - [Edit affiliate](/en/developers/api/cobros-tarjetas-guardadas/edit-affiliate) — `POST /api/v1/collect/edit/affiliate` - [Edit group](/en/developers/api/cobros-tarjetas-guardadas/edit-group) — `POST /api/v1/collect/edit/group` - [Create payments](/en/developers/api/cobros-tarjetas-guardadas/set-payments) — `POST /api/v1/collect/set/payments` --- # API · List groups > List groups: POST /api/v1/collect/get/groups. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-groups ```http POST /api/v1/collect/get/groups ``` ## What it does [#que-hace] List groups. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "" } ``` ## Response [#respuesta] groups: amount is the group default amount, as text. `status`. `errors`. `groups`. ```json { "status": 200, "errors": "", "groups": [ { "id": 1, "name": "Grupo 1", "currency": "USD", "amount": "1000.00", "created_at": "2024-05-03T23:09:03.000000Z" }, { "id": 5, "name": "Colones", "currency": "CRC", "amount": "10.00", "created_at": "2024-05-08T23:52:41.000000Z" } ] } ``` ```schema { "type": "object", "properties": { "status": { "type": "integer" }, "errors": { "type": "string" }, "groups": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "currency": { "type": "string" }, "amount": { "type": "string" }, "created_at": { "type": "string" } } } } } } ``` --- # API · List affiliates > List affiliates: POST /api/v1/collect/get/affiliates. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-affiliates ```http POST /api/v1/collect/get/affiliates ``` ## What it does [#que-hace] List affiliates. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. Group ID; 0 = all. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "group": 0 } ``` ## Response [#respuesta] affiliates contains affiliate data and daily/monthly limits. `status`. `errors`. `affiliates`. ```json { "status": 200, "errors": "", "affiliates": [ { "id": 2, "name": "Juan", "lastname": "Lopez", "email": "cliente@ejemplo.com", "company": "JuanSA", "phone": "88778878", "address": "San José, 100 m este", "city": "San Jose", "state": "CR-SJO", "zipcode": "10101", "country": "CR", "currency": "USD", "amount_max_daily": "1000.00", "amount_max_month": "1250.00", "base_amount": "10.00", "status": 1, "created_at": "2024-01-16T22:45:00.000000Z" }, { "id": 14, "name": "Ramon", "lastname": "Perez", "email": "otro@ejemplo.com", "company": null, "phone": null, "address": "San José", "city": "Alajuela", "state": "CR-A", "zipcode": "10101", "country": "CR", "currency": "USD", "amount_max_daily": "100.00", "amount_max_month": "1000.00", "base_amount": "0.00", "status": 1, "created_at": "2024-02-27T18:19:31.000000Z" } ] } ``` ```schema { "type": "object", "properties": { "status": { "type": "integer" }, "errors": { "type": "string" }, "affiliates": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "lastname": { "type": "string" }, "email": { "type": "string" }, "company": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "address": { "type": "string" }, "city": { "type": "string" }, "state": { "type": "string" }, "zipcode": { "type": "string" }, "country": { "type": "string" }, "currency": { "type": "string" }, "amount_max_daily": { "type": "string" }, "amount_max_month": { "type": "string" }, "base_amount": { "type": "string" }, "status": { "type": "integer" }, "created_at": { "type": "string" } } } } } } ``` --- # API · List payment batches > List payment batches: POST /api/v1/collect/get/massive. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-massive ```http POST /api/v1/collect/get/massive ``` ## What it does [#que-hace] List payment batches. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "" } ``` ## Response [#respuesta] Use code to query a batch detail. `status`. `errors`. `massives`. ```json { "status": 200, "errors": "", "massives": [ { "id": 1, "code": "R3VhKzhMNU1uaGg1ZjZRVGw4UGh0dz09", "created_at": "2024-05-15T04:36:58.000000Z" }, { "id": 3, "code": "UnBSdXN0bnZSc0owVXdvU1haVisxZz09", "created_at": "2024-05-15T04:56:59.000000Z" } ] } ``` ```schema { "type": "object", "properties": { "status": { "type": "integer" }, "errors": { "type": "string" }, "massives": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "code": { "type": "string" }, "created_at": { "type": "string" } } } } } } ``` --- # API · Get batch detail > Get batch detail: POST /api/v1/collect/get/massive/detail. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-massive-detail ```http POST /api/v1/collect/get/massive/detail ``` ## What it does [#que-hace] Get batch detail. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. The code returned in the batch list. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "code": "UnBSdXN0bnZSc0owVXdvU1haVisxZz09" } ``` ## Response [#respuesta] `responseText` contains the rejection reason: `Daily amount exceeded` means the affiliate exceeded their daily maximum amount. The `Programmed` status indicates a scheduled charge that has not been processed yet. `status`. `errors`. `payments`. ```json { "status": 200, "errors": "", "payments": [ { "id": 12, "collect_id": 2, "currency": "CRC", "amount": "100.00", "reference": "0", "auth": "", "date_programmed": "", "reason": "Cobro mensualidad", "responseText": "Daily amount exceeded", "status": "processed", "approved_payment": "0" }, { "id": 20, "collect_id": 2, "currency": "CRC", "amount": "100.00", "reference": "0", "auth": "123456", "date_programmed": "", "reason": "Cobro mensualidad", "responseText": "Approved", "status": "processed", "approved_payment": "0" }, { "id": 9, "collect_id": 2, "currency": "CRC", "amount": "100.00", "reference": "", "auth": "", "date_programmed": "2024-05-19", "reason": "Cobro mensualidad", "responseText": "", "status": "Programmed", "approved_payment": "0" } ] } ``` ```schema { "type": "object", "properties": { "status": { "type": "integer" }, "errors": { "type": "string" }, "payments": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "collect_id": { "type": "integer" }, "currency": { "type": "string" }, "amount": { "type": "string" }, "reference": { "type": "string" }, "auth": { "type": "string" }, "date_programmed": { "type": "string" }, "reason": { "type": "string" }, "responseText": { "type": "string", "description": "Rejection reason; Daily amount exceeded means the affiliate daily maximum was exceeded." }, "status": { "type": "string", "enum": [ "processed", "Programmed" ] }, "approved_payment": { "type": "string" } } } } } } ``` --- # API · Create group > Create group: POST /api/v1/collect/set/group. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/set-group ```http POST /api/v1/collect/set/group ``` ## What it does [#que-hace] Create group. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. Group name. ISO 4217 currency. Default amount, text with 2 decimal places. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "currency": "USD", "amount": "100.00", "name": "Grupo Name" } ``` ## Response [#respuesta] type is "200". id identifies the created group. `type`. `errors`. `id`. ```json { "type": "200", "errors": "", "id": 10 } ``` ```schema { "type": "object", "properties": { "type": { "type": "string", "const": "200" }, "errors": { "type": "string" }, "id": { "type": "integer" } } } ``` --- # API · Create individual enrollment URL > Create individual enrollment URL: POST /api/v1/collect/set/urlaffiliate. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/set-url-affiliate ```http POST /api/v1/collect/set/urlaffiliate ``` ## What it does [#que-hace] Create individual enrollment URL. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. Link name. ISO 4217 currency. Base amount. Daily maximum amount. Monthly maximum amount. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "name": "Name link", "currency": "USD", "base_amount": "5.00", "amount_max_daily": "10.00", "amount_max_month": "100.00" } ``` ## Response [#respuesta] link is the URL where the customer registers their card. `type`. `errors`. `link`. ```json { "type": "200", "errors": "", "link": "https://app.tilopay.com/link/TWc9PXw0" } ``` ```schema { "type": "object", "properties": { "type": { "type": "string" }, "errors": { "type": "string" }, "link": { "type": "string" } } } ``` --- # API · Create group enrollment URL > Create group enrollment URL: POST /api/v1/collect/set/urlgroup. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/set-url-group ```http POST /api/v1/collect/set/urlgroup ``` ## What it does [#que-hace] Create group enrollment URL. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. Link name. ISO 4217 currency. Base amount. Daily maximum amount. Monthly maximum amount. Group ID. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "name": "Name link", "group": "1", "currency": "USD", "base_amount": "5.00", "amount_max_daily": "10.00", "amount_max_month": "100.00" } ``` ## Response [#respuesta] Share link with customers enrolling in the group. `type`. `errors`. `link`. ```json { "type": "200", "errors": "", "link": "https://app.tilopay.com/link/TXc9PXw0" } ``` ```schema { "type": "object", "properties": { "type": { "type": "string" }, "errors": { "type": "string" }, "link": { "type": "string" } } } ``` --- # API · Edit affiliate > Edit affiliate: POST /api/v1/collect/edit/affiliate. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/edit-affiliate ```http POST /api/v1/collect/edit/affiliate ``` ## What it does [#que-hace] Edit affiliate. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. Affiliate ID. name lastname company phone address city state zipcode country base_amount ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "affiliateid": 2, "name": "Juan", "lastname": "Lopez", "company": "Tilo", "phone": "88998877", "address": "Escazu", "city": "San Jose", "state": "SJO", "zipcode": "10101", "country": "CR", "base_amount": "10.00" } ``` ## Response [#respuesta] update returns success. `type`. `errors`. `update`. ```json { "type": "200", "errors": "", "update": "success" } ``` ```schema { "type": "object", "properties": { "type": { "type": "string" }, "errors": { "type": "string" }, "update": { "type": "string", "const": "success" } } } ``` --- # API · Edit group > Edit group: POST /api/v1/collect/edit/group. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/edit-group ```http POST /api/v1/collect/edit/group ``` ## What it does [#que-hace] Edit group. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. Group ID. Group default amount. Group name. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "groupid": 1, "amount": "800.00", "name": "Grupo Name" } ``` ## Response [#respuesta] update returns success. `type`. `errors`. `update`. ```json { "type": "200", "errors": "", "update": "success" } ``` ```schema { "type": "object", "properties": { "type": { "type": "string" }, "errors": { "type": "string" }, "update": { "type": "string", "const": "success" } } } ``` --- # API · Create payments > Create payments: POST /api/v1/collect/set/payments. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/set-payments ```http POST /api/v1/collect/set/payments ``` ## What it does [#que-hace] Create payments. ## Authentication [#autenticacion] Host: `https://app.tilopay.com`. Requires the API bearer token in `Authorization: Bearer ` and the integration key in the body (`key`). [Get the API token](/en/developers/api/autenticacion/token-api). ## Parameters [#parametros] Integration key. Also applies to charges scheduled by date. 1 captures; 0 only authorizes. Payment reason. Affiliates: array of `{id, amount, currency, date}`. Groups: array of `{id, amount, currency, date}`; amount is per affiliate. The `date` field is empty for immediate charges or `YYYY-MM-DD` for scheduled charges. In `groups`, `amount` is per affiliate. ## Request example [#ejemplo-request] `` is the placeholder for your integration key. ```json { "key": "", "capture": 1, "reason": "Cobro mensualidad", "users": [ { "id": "5", "amount": "500.00", "currency": "CRC", "date": "" }, { "id": "32", "amount": "1000.00", "currency": "CRC", "date": "2024-06-20" }, { "id": "25", "amount": "10.00", "currency": "USD", "date": "2024-06-20" } ], "groups": [ { "id": "10", "amount": "25.00", "currency": "USD", "date": "" } ] } ``` ## Response [#respuesta] batch is the batch code for get/massive/detail. Empty date = immediate; YYYY-MM-DD = scheduled. Repeating this operation creates another charge. `status`. `errors`. `users_errors`. `groups_errors`. `programeds`. `batch`. ```json { "status": 200, "errors": "", "users_errors": "", "groups_errors": "", "programeds": { "users": [ "5", "32", "25" ], "groups": [ "10" ] }, "batch": "SGZqbHlHalJiWmRNSEszd3ZEZHo4dz09" } ``` ```schema { "type": "object", "properties": { "status": { "type": "integer" }, "errors": { "type": "string" }, "users_errors": { "type": [ "string", "array" ], "items": {} }, "groups_errors": { "type": [ "string", "array" ], "items": {} }, "programeds": { "type": "object", "properties": { "users": { "type": "array", "items": { "type": "string" } }, "groups": { "type": "array", "items": { "type": "string" } } } }, "batch": { "type": "string" } } } ``` ## Error response [#respuestas-de-error] HTTP 400: ```json { "status": "400", "errors": "Please verify the data sent", "users_errors": [ { "id": "32", "amount": "1000.00", "currency": "CRC", "date": "invalidDate" } ], "groups_errors": [ { "id": "10", "amount": "25.00", "currency": null, "date": null } ] } ``` [Safe retries](/en/developers/reintentos-seguros). --- # API · Webhooks > Which events exist, how many retries there are, in what format they arrive and how to confirm the state of a charge. - kind: concept - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/webhooks ## Recurrence [#recurrencia] Six fields configured when **creating or editing a plan**. All optional. | Field | What it is | |---|---| | `webhook_subscribe` | Subscription webhook | | `webhook_payment` | Charge webhook | | `webhook_rejected` | Rejected charge webhook | | `webhook_unsubscribe` | Unsubscribe webhook | | `webhook_reactive` | Reactivation webhook | | `thanks_url` | **Not a webhook:** it is a GET callback | ## Payment links [#links-de-pago] `webhook_url` is configured when creating the link. ## Retries [#reintentos] | Source | Attempts | When it retries | |---|---|---| | Recurrence | 5 | When your response is 400, 404, 502, 504 or 403 | | Purchases | 1 | — | Delivery is considered successful on an **HTTP 200**. Respond 200 as soon as you receive the event and process it separately: anything else counts as a failure, and purchases have no second attempt. ## Format [#formato] JSON. ## The card token [#token] | Source | Token | |---|---| | Recurrence | **Not sent** | | Purchases | Sent and **usable** | ## Origin verification [#verificacion] > **Warning:** The only origin verification available is the `orderHash`, and its algorithm is not public: > it is requested by writing to `soporte@tilopay.com`. > > Until you have the algorithm, do not take irreversible business decisions from webhook content > alone: confirm the state against the API before shipping or releasing a service. ## Main flow webhook [#processpayment] > **Note:** `processPayment` also has a webhook. Its technical specification (the fields it sends) we deliver it when you request it, so your team can implement it: > write to `soporte@tilopay.com` to receive it. --- # JavaScript SDK > The integration path where the card form lives on your page and the SDK sends the data straight to Tilopay. - kind: guide - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/sdk ## What it is [#que-es] The card fields live on your page. The V2 SDK takes over those inputs and sends the data **from the browser straight to Tilopay**: it never passes through your server. ## Who it is for [#para-quien] For teams that want to control the checkout design without card data touching their backend. You need a JavaScript frontend and a minimal backend to obtain the SDK token. ## How to build it, in order [#orden] 1. **Credentials.** There are no shared sandbox credentials: get yours through [developer registration](/en/developers/registro). 2. **SDK token.** Your backend requests it with `POST /api/v1/loginSdk`. See [authentication](/en/developers/api/autenticacion). 3. **Load the script.** See [installation](/en/developers/sdk/instalacion). 4. **Add the inputs with the contract ids** and the `responseTilopay` container. See [form fields](/en/developers/sdk/campos-del-formulario). 5. **Start the purchase** with [`Tilopay.Init()`](/en/developers/sdk/reference/init) and show the customer the payment methods it returns. 6. **Close the purchase** with [`Tilopay.startPayment()`](/en/developers/sdk/reference/startpayment). The SDK handles 3DS and renders the result on your `redirect` URL. 7. **Confirm the result on your server.** The redirect belongs to the customer; before delivering the product, confirm the status with the API. See [confirm the result](#confirmar). ## Method flow [#flujo] ## SINPE Móvil: confirmation and timing [#sinpe-confirmacion] When the transfer arrives, the SDK redirects to your `redirect`; Tilopay also notifies `webhook` if provided. The SDK does not set a deadline, but Tilopay matches transfers with SINPE Móvil orders created within the last **25 minutes**: customers must transfer within that window. The session depends on the SDK token lifetime. ```text Required: Init() ──────────────────────────────► startPayment() Optional: getCardType() (show the brand icon) updateOptions() (if data changes) ``` You can also accept [Apple Pay](/en/developers/sdk/apple-pay): the SDK draws the button inside the form. ## Confirm the result on your server [#confirmar] When it finishes, the customer's browser lands on your `redirect` URL with the result parameters: `code`, `description`, `auth`, `order`, `tpt`, `tilopay-transaction`, `OrderHash` and `returnData`. See the full table in [parameters that arrive at the redirect](/en/developers/api/hosted-payment-page/process-payment#parametros-del-redirect). - `code = 1` means the transaction is approved; any other value is a decline. - The redirect is received by the customer's browser, so it is not enough proof: **before delivering the product, confirm the status with `POST /api/v1/consult` from your server**. - `OrderHash` is a unique string per transaction: its algorithm is not public and is requested by writing to `soporte@tilopay.com`. ## Before assuming you are in test mode [#entornos] Testing and production share one host: the mode is verified with the `environment` field returned by `Init()` (`"TEST"` or `"PROD"`). See [environments](/en/developers/entornos). ## Full example [#ejemplo-completo] Form inside `.payFormTilopay`, the five fields, Pay and Update buttons, `responseTilopay` outside the form, the v2 script, `Init()` with every field, loading `methods` and `cards` into their selects, and `startPayment()`. ```html
``` ## Compliance [#cumplimiento] Card data does not pass through your server, but your page captures it: check the applicable scope with your compliance team. --- # API · Get SDK token > SDK login: returns the token that Tilopay.Init() uses in the browser. - kind: api-operation - status: stable - api_version: v1 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api/autenticacion/token-sdk ```http POST /api/v1/loginSdk ``` ## What it does [#que-hace] Returns the security token needed to consume the SDK. All three credentials come from [Admin panel · Platform integrations](https://admin.tilopay.com/platform-integrations). ## Authentication [#autenticacion] It does not require a token: this is the operation that issues it. See [authentication](/en/developers/api/autenticacion). > **Warning:** This login runs on your server, never in the browser. `apiuser`, `password` and `key` > are merchant secrets: they are never exposed in the frontend. Only the token returned by > this operation reaches the browser. ## Parameters [#parametros] Merchant API user. Merchant API password. Merchant api key. ## Request example [#request] The `<...>` values are placeholders: replace them with your own credentials and data. ```json { "apiuser": "", "password": "", "key": "" } ``` ## Response [#respuesta] ```json { "access_token": "351d477adaaa51cdbb33ae4b4b8c843aa759f131c1c38415180b5035543d59d2f23f4e049706f5fa71c5f0d9ff763960a3cd035b4c1cefa428c90bedb672f780", "token_type": "bearer", "expires_in": "2023-06-05 13:32:06" } ``` ## Error responses [#respuestas-de-error] Invalid credentials → HTTP 401: ```json { "error": "Unauthorized" } ``` To interpret a response that is not a success, see [how to read an error response](/en/developers/api/procesos-operativos/errors). --- # SDK installation > The V2 SDK script tag, the version history, the domains for your Content-Security-Policy and which versioning guarantees do not exist. - kind: guide - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/sdk/instalacion ## The script [#script] ```html ``` Load it before your own checkout code. The SDK exposes the global `Tilopay` object. The current version is **2.1.1**. ## jQuery is not required [#jquery] > **Note:** jQuery is NOT required. Since version 2.0.0 the SDK is written in JavaScript: you can > install it without jQuery on the page. ## Version history [#versiones] | Version | Date | Change | |---|---|---| | 1.0.0 | 29/07/2022 | Tilopay payment form | | 1.1.0 | 05/10/2022 | Saved cards and SINPE Móvil | | 2.0.0 | 01/06/2023 | Migration from jQuery to JavaScript | | 2.1.0 | 30/08/2023 | [`InitTokenize()`](/en/developers/sdk/reference/init-tokenize) to save cards | | 2.1.1 | — | [Apple Pay](/en/developers/sdk/apple-pay) | ## Domains for Content-Security-Policy [#csp] If your site uses a Content-Security-Policy, allow the origins the SDK loads: | Origin | When | |---|---| | `https://app.tilopay.com` | Always | | `https://api.tokenex.com` | Always | | `https://h.online-metrix.net` | Always | | `https://storage.googleapis.com` | Always | | `https://cdn.jsdelivr.net` | Always | | `https://applepay.cdn-apple.com` | Only if you use [Apple Pay](/en/developers/sdk/apple-pay) | ## What does not exist [#garantias] > **Warning:** There is no immutable versioning, no SRI hash and no changelog. The > `/sdk/v2/sdk_tpay.min.js` URL is not pinned to a specific version: its contents can change > without prior notice and without public release notes. > > Practical consequences: > > - You cannot set `integrity="sha384-..."` on the `script` tag, because the file changes. > - You cannot rely on a build number to reproduce a bug. > - Keep smoke tests for your checkout running periodically, not only on deploy. ## Single-page applications [#spa] Works in React and Vue. Call `Init()` after the fields mount in the DOM: it uses them immediately and fails if they do not exist. Call `Init()` again if the form remounts. React Native WebView is not tested by Tilopay and may disable Apple Pay; test it in your environment before production. ## What is next [#que-sigue] - [Form fields](/en/developers/sdk/campos-del-formulario) — the ids the SDK looks for. - [`Tilopay.Init()`](/en/developers/sdk/reference/init) — start the purchase. --- # Form fields > The tlpy_* ids the V2 SDK looks for inside the payFormTilopay div, and the responseTilopay container it needs for 3DS. - kind: concept - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-21 - url: https://tilopay.com/en/developers/sdk/campos-del-formulario The V2 SDK does not render the form: it takes over the inputs that already exist on your page, finding them by `id`. Those ids are a contract — change one and the SDK stops finding the field. > **Warning:** The `payFormTilopay` container is required. All `tlpy_*` fields (and the > [Apple Pay](/en/developers/sdk/apple-pay) div) must be inside a `div` with the class > `payFormTilopay`: the SDK looks for that container to listen to the fields. The > `responseTilopay` container goes outside that `div`. ## Fields [#campos] Holds the payment method id obtained from Tilopay. It may be visible or hidden, at your discretion. Holds the saved card id obtained from Tilopay. It must exist in the DOM; it may be hidden if the payment method does not use it. Card number. It must exist in the DOM; it may be hidden if the payment method does not use it. Expiration date in month/year format, for example `12/30`. It must exist in the DOM; it may be hidden if the payment method does not use it. Security code. It must exist in the DOM; it may be hidden if the payment method does not use it. When the customer uses a saved card, this field must be enabled so they can type the CVV. For [Apple Pay](/en/developers/sdk/apple-pay): the SDK draws the Apple Pay button there. It goes inside `payFormTilopay`. Optional, for Apple Pay. Its value changes to `"cancelled"` and fires `change` when the buyer closes the Apple Pay sheet. Required container, **outside `payFormTilopay`**. This is where the SDK mounts the 3DS flow. ## The Yappy phone does not travel through the DOM [#yappy] The Yappy phone number is **not** read from a form field: it is sent in the `phoneYappy` parameter of [`Init()`](/en/developers/sdk/reference/init) or [`updateOptions()`](/en/developers/sdk/reference/update-options). ## Example structure [#estructura] The `div` with the `payFormTilopay` class, the input `id`s and `responseTilopay` are required; the other `div` elements are free-form. ```html
``` --- # Apple Pay > How to accept Apple Pay with the V2 SDK (2.1.1) — requirements, steps, example, payment sheet data and common problems. - kind: guide - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-21 - url: https://tilopay.com/en/developers/sdk/apple-pay ## What it is [#que-es] The SDK draws the Apple Pay button inside your form, opens Apple's payment sheet and processes the payment with Apple's encrypted token. The buyer does not type the card. ## Requirements [#requisitos] | Requirement | Detail | |---|---| | Production account | In testing mode the button does not appear and `applePaySupported` is `false`: it cannot be tested in testing mode. | | Payment method with Apple Pay | In `init.methods` the method has `apple_pay = 1`. If your method does not, the button is not shown. | | Certified domain | The domain must be certified for Apple Pay in your Tilopay account. The match is exact: `www.comercio.com` and `comercio.com` are different domains. To certify it, write to soporte@tilopay.com with the exact domain. | | HTTPS | The page is served over HTTPS. | | Browser | Safari on an Apple device with Apple Pay set up. | | Cards | Visa, Mastercard or American Express. | ## Steps [#pasos] 1. Load the [v2 SDK](/en/developers/sdk/instalacion). 2. Inside `.payFormTilopay` add `
` where the button goes and, optionally, `` to know when the buyer closes the sheet (its value changes to `"cancelled"` and fires `change`). 3. Call [`Init()`](/en/developers/sdk/reference/init) as usual (token, currency, amount, order, billing, `capture` and `redirect`). `applePaySupported: true` means the requirements are met for that domain and browser. 4. Fill `#tlpy_payment_method` with `init.methods`. 5. The button appears when the select fires the `change` event with a method that has Apple Pay. If you select the method from code, fire `select.dispatchEvent(new Event('change'))`. 6. When the button is tapped, the SDK opens Apple Pay and, once the buyer authorizes, processes the payment: if approved it redirects to `redirect`; if the bank asks for 3DS it shows it in `#responseTilopay`. > **Note:** The card fields are still required. `tlpy_cc_number`, `tlpy_cc_expiration_date` and > `tlpy_cvv` must be in the HTML even if the buyer pays with Apple Pay, because the SDK > initializes them in `Init()`. ## Example [#ejemplo] ```html
``` ## Apple Pay sheet data [#hoja] | Field | Value | |---|---| | Country | `CR` | | Currency | The one from `Init()`, uppercase | | Networks | `visa`, `mastercard` and `amex` | | Capability | `supports3DS` | | Label | `Tilopay: ` | | Amount | The one from `Init()` | ## Behavior to keep in mind [#comportamiento] - The Apple Pay payment result **is not returned to your code** (the button calls the SDK directly): it arrives on the `redirect` URL like any payment. Confirm it with `POST /api/v1/consult` from your server. See [confirm the result](/en/developers/sdk#confirmar). - You can only listen for cancellation, with `tlpy_apple_pay_cancel`. - If the payment fails, the sheet stays open until the buyer closes it or it expires, and the detail is logged in the browser console. ## Common problems [#problemas] | Symptom | Cause and fix | |---|---| | `applePaySupported` is `false` | Account in testing mode, no method with `apple_pay = 1`, browser without Apple Pay or uncertified domain. When it is the domain, the console shows `Apple Pay not allowed for this domain`. | | `applePaySupported` is `true` but the button does not appear | The div is not inside `.payFormTilopay`, the selected method has no Apple Pay or the select did not fire `change`. | | It works on one domain but not with `www` | Certify the other form too, or always use the certified one. | | The button opens the sheet and it closes right away | Merchant validation with Apple failed: check that the domain certification is current and write to soporte@tilopay.com with the domain and the time. | | The buyer authorizes and the sheet keeps loading | The payment failed: check the console and look up the transaction by order number. | --- # Tilopay.Init() > Starts a purchase: authenticates the checkout and returns the environment, the available payment methods and the customer's saved cards. - kind: sdk-method - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/sdk/reference/init Starts a purchase. It authenticates the checkout with the SDK token and returns the payment methods available to you. To save a card without charging it, use [`Tilopay.InitTokenize()`](/en/developers/sdk/reference/init-tokenize). ## Signature [#firma] ```js await Tilopay.Init({ /* parameters */ }) ``` ## Parameters [#parametros] URL where Tilopay notifies the outcome of SINPE Móvil and Yappy payments. SDK token, obtained by your backend with `POST /api/v1/loginSdk`. See [SDK token](/en/developers/api/autenticacion/token-sdk). Purchase currency, ISO 4217. Checkout language: `es` or `en`. Purchase amount. Customer email. Needed for the response to include saved cards. Order number. **It cannot be repeated.** See [safe retries](/en/developers/reintentos-seguros). Customer identification type. Required for SINPE Móvil. See the [type table](/en/developers/sdk/reference/update-options#tipos-de-identificacion). Customer identification number. Required for SINPE Móvil. Customer first name. Customer last name. Customer address line 1. Customer address line 2. City. State or province. Postal code. Country, ISO 3166-1 alpha-2. Customer phone. Shipping recipient first name. Shipping recipient last name. Shipping address line 1. Shipping address line 2. Shipping city. Shipping state or province. Shipping postal code. Shipping country, ISO 3166-1 alpha-2. Shipping phone. `0` authorizes; `1` authorizes and captures. URL where the SDK renders the final purchase response. `1` saves the customer card in Tilopay; `0` does not save it. Values `"V1"` or `"V2"`. If omitted, the final response hash is built with **V2**. It is sent in this first call. Returned as-is in the final payment response and recovered on the transaction response URL. Supports up to **65,535 characters**, although a very long value can affect the response URL. You can send a base64-serialized array so it satisfies the string format. Yappy phone. Required when paying with Yappy. It is not read from the DOM. ## Call [#llamada] ```js const init = await Tilopay.Init({ token: "", currency: "CRC", language: "es", amount: 100.0, billToEmail: "cliente@ejemplo.com", orderNumber: "ORD-2026-000123", billToFirstName: "Ana", billToLastName: "Rojas", billToCountry: "CR", capture: 1, redirect: "https://ejemplo.com/respuesta", subscription: 0, }); ``` ## Response [#respuesta] ```json { "message": "Success", "environment": "PROD", "sinpemovil": { "code": "TB095", "amount": "100.00" }, "methods": [ { "id": "", "name": "Tarjeta", "type": "card", "apple_pay": 0 }, { "id": "", "name": "SINPE Móvil", "type": "sinpemovil", "apple_pay": 0 } ], "cards": [ { "id": "411112555451111:Ref", "name": "4111********1111", "brand": "visa" } ], "applePaySupported": false } ``` `Success`, or the error description. `"PROD"` production, `"TEST"` testing. See [environments](/en/developers/entornos). Object with `code` and `amount`, present when you have SINPE Móvil. The full data, including the destination phone, is obtained with [`getSinpeMovil()`](/en/developers/sdk/reference/get-sinpe-movil). Array of `{id, name, type, apple_pay}` with available methods. `type` is `card`, `sinpemovil` or `yappy`; every method includes `apple_pay` (`0` or `1`). Array of `{id, name, brand}` with the customer's saved cards. `true` when the [Apple Pay](/en/developers/sdk/apple-pay) requirements are met for that domain and browser. ### On error [#respuesta-de-error] On error: ```json { "message": "", "methods": [], "cards": [] } ``` ## Payment method id format [#formato-id-metodo] Do not interpret the id: store it as-is as the select value and use each method's `type` field (`card`, `sinpemovil` or `yappy`) to decide which fields to show. ## Yappy [#yappy] The method has `type: "yappy"`; its id has the form `:18::…`. The form needs the required `Init()` fields plus `phoneYappy`. Calling `startPayment()` redirects to a Tilopay page that opens Yappy; the customer approves in their app. When finished, Tilopay notifies `webhook` (if provided) and redirects to your `redirect` with `code`, `auth`, `order` and `OrderHash`. Cancellation rejects the order; without a response within **7 minutes**, it becomes “Transaction expired”. ## Saved cards [#tarjetas-guardadas] - For the response to include `cards`, **`billToEmail` is required**. - Once the cards are obtained, **the email can no longer be changed** with [`updateOptions()`](/en/developers/sdk/reference/update-options). - The customer can pick a saved card or type a new one. - When a saved card is picked, **enable `tlpy_cvv`** so the customer can type the code. --- # Tilopay.InitTokenize() > Starts saving a card without a purchase: the card is validated with a USD 0.10 charge that is reversed immediately. - kind: sdk-method - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/sdk/reference/init-tokenize Starts saving a card **without a purchase**. It uses the same [form fields](/en/developers/sdk/campos-del-formulario) as `Init()` and is closed with [`Tilopay.startPayment()`](/en/developers/sdk/reference/startpayment). > **Note:** Validation charge. To validate the card, Tilopay makes a USD 0.10 charge (or its equivalent > in the currency) that is reversed immediately; the customer sees it refunded within minutes. Store the token you receive and associate it with the customer's email to use it later. ## Signature [#firma] Do not send `orderNumber`: the SDK uses its own session identifier. Tilopay sets the validation amount. ```js await Tilopay.InitTokenize({ /* parameters */ }) ``` ## Redirect parameters [#parametros-del-redirect] Redirect query parameters are `code`, `crd`, `order`, `brand`, `last-digits` and `OrderHash`. `crd` is the card token and only has a value when `code = 1`; store it with the customer email. Tilopay generates `order`, not the merchant. ## Parameters [#parametros] SDK token, obtained with `POST /api/v1/loginSdk`. Currency, ISO 4217. Language: `es` or `en`. Customer email. Customer first name. Customer last name. Customer address line 1. Customer address line 2. City. State or province. Postal code. Country, ISO 3166-1 alpha-2. Customer phone. URL where the SDK renders the final response. ## Call [#llamada] ```js const init = await Tilopay.InitTokenize({ token: "", currency: "USD", language: "es", billToEmail: "cliente@ejemplo.com", billToFirstName: "Ana", billToLastName: "Rojas", redirect: "https://ejemplo.com/respuesta", }); ``` ## Response [#respuesta] ```json { "message": "Success", "environment": "PROD", "methods": [ { "id": "", "name": "Tarjeta", "type": "card" } ] } ``` On error: ```json { "message": "", "methods": [] } ``` --- # Tilopay.startPayment() > Closes the purchase. It takes no parameters; it redirects to the redirect URL or shows 3DS in responseTilopay. - kind: sdk-method - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/sdk/reference/startpayment Closes the purchase started with [`Tilopay.Init()`](/en/developers/sdk/reference/init) (or the card save started with [`InitTokenize()`](/en/developers/sdk/reference/init-tokenize)). ## Signature [#firma] ```js await Tilopay.startPayment() ``` It takes no parameters. **It requires the data to be complete**: payment method, card data and whatever the method needs, sent in `Init()` or in [`updateOptions()`](/en/developers/sdk/reference/update-options). ## 3DS: the SDK handles it [#tres-ds] **The SDK handles the whole 3D Secure flow.** If the bank asks for it, the SDK shows it in the `responseTilopay` container: ```html
``` See [form fields](/en/developers/sdk/campos-del-formulario#campos). ## Where the result arrives [#resultado] On successful payment, the SDK redirects and the promise resolves with `{ "message": "" }`, without payment data. On error it returns `{ "message": "" }`. If everything goes well, the SDK redirects to the URL given in `redirect`. That redirect is received by the customer's browser, so before delivering the product [confirm the result on your server](/en/developers/sdk#confirmar). If there is an error, the method returns the description: ```json { "message": "" } ``` With SINPE Móvil, until the transfer arrives, `startPayment()` returns the pending-SINPE error. See [`getSinpeMovil()`](/en/developers/sdk/reference/get-sinpe-movil). --- # Tilopay.getCardType() > Returns the brand of the card the customer typed or picked, in lowercase, so you can show its icon. - kind: sdk-method - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-21 - url: https://tilopay.com/en/developers/sdk/reference/get-card-type Returns the card brand from the number the customer already typed into `tlpy_cc_number` (or from the saved card they picked). The typical use is showing the brand icon. ## Signature [#firma] ```js await Tilopay.getCardType() ``` It takes no parameters. **Precondition:** the customer must have already typed the card number or picked a saved card. ## Call [#llamada] ```js const type = await Tilopay.getCardType(); ``` ## Response [#respuesta] ```json { "message": "visa" } ``` The card brand, in lowercase. If the brand is not recognized, it is empty. ## Brand values [#marcas-soportadas] | Status | Values | |---|---| | Supported | `visa`, `mastercard`, `amex` | | Not supported | `elo`, `visaelectron`, `maestro`, `forbrugsforeningen`, `dankort`, `hipercard`, `dinersclub`, `discover`, `unionpay`, `jcb`, `laser` | Use the result for presentation only: brand validation happens when Tilopay processes the charge, not in this method. --- # Tilopay.getSinpeMovil() > Returns the data to show the customer to pay via SINPE Móvil: destination phone, exact amount and code. - kind: sdk-method - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/sdk/reference/get-sinpe-movil SINPE Móvil is not charged from the form: the customer makes the transfer from their own bank. This method builds the data of the selected SINPE method that you must show them — destination phone, exact amount and description code. ## When to use it [#cuando] When you have a SINPE Móvil method available and the customer selects it from the list returned by [`Init()`](/en/developers/sdk/reference/init). ## Signature [#firma] ```js await Tilopay.getSinpeMovil() ``` It takes no parameters. **Preconditions:** - The customer must have selected SINPE Móvil as the payment method. - `typeDni` and `dni` must have been sent earlier, via `Init()` or [`updateOptions()`](/en/developers/sdk/reference/update-options). ## Call [#llamada] ```js const params = await Tilopay.getSinpeMovil(); ``` ## Response [#respuesta] ```json { "message": "Success", "code": "863", "amount": 10, "number": "70599200" } ``` `Success` when the operation succeeded; the error description otherwise. The code the customer must write in the transfer description. It is what identifies the payment. The exact amount they must transfer. The destination phone number for the transfer. ### On error [#respuesta-de-error] It returns an object with the error description and the other fields empty: ```json { "message": "", "code": "", "amount": "", "number": "" } ``` ## SINPE Móvil: confirmation and timing [#sinpe-confirmacion] When the transfer arrives, the SDK redirects to your `redirect`; Tilopay also notifies `webhook` if provided. The SDK does not set a deadline, but Tilopay matches transfers with SINPE Móvil orders created within the last **25 minutes**: customers must transfer within that window. The session depends on the SDK token lifetime. ## What to do with this data [#que-hacer] - When SINPE Móvil is selected, hide the card fields and show a message such as: *"To pay with SINPE Móvil, transfer the exact amount ₡100.00 to phone 8888 8888 and write TB095 in the description"*. - Optionally, add an "I already made the SINPE Móvil transfer" button that, when tapped, shows that the deposit is being awaited. Before enabling it, make sure you have the email, `typeDni` and `dni`. - Until the transfer arrives, [`startPayment()`](/en/developers/sdk/reference/startpayment) returns the pending-SINPE error. --- # Tilopay.updateOptions() > Partially updates the parameters sent to Init() when they change before closing the payment. - kind: sdk-method - status: stable - sdk_version: 2.1.1 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/sdk/reference/update-options Updates values already sent in [`Tilopay.Init()`](/en/developers/sdk/reference/init) that changed during checkout — typically when the customer edits their data, or when they select SINPE Móvil or Yappy and information that did not apply before must be added. > **Note:** It is a partial update. Send only the fields you want to update; there is no need to resend > everything. ## No field is required in the call [#obligatorio] No parameter is required in `updateOptions()`. What matters is that, before closing the payment, the data the payment method needs (for example `typeDni` and `dni` for SINPE Móvil, or `phoneYappy` for Yappy) was sent in `Init()` or in `updateOptions()`. ## Signature [#firma] ```js await Tilopay.updateOptions({ /* only the fields that change */ }) ``` ## Call [#llamada] ```js const update = await Tilopay.updateOptions({ typeDni: 1, dni: "0707770777", billToFirstName: "Ana", billToLastName: "Rojas", }); ``` ## Parameters [#parametros] URL where Tilopay notifies the outcome of SINPE Móvil and Yappy payments. You can update `typeDni`, `dni`, the `billTo*` fields (except `billToEmail`), the `shipTo*` fields, `capture`, `redirect`, `subscription` and `phoneYappy`. Customer identification type. Needed for SINPE Móvil. See [identification types](#tipos-de-identificacion). Customer identification number. Needed for SINPE Móvil. **Accepted with and without hyphens.** Customer first name. Customer last name. Customer address line 1. Customer address line 2. City. State or province. Postal code. Country, ISO 3166-1 alpha-2. Customer phone. Shipping recipient first name. Shipping recipient last name. Shipping address line 1. Shipping address line 2. Shipping city. Shipping state or province. Shipping postal code. Shipping country, ISO 3166-1 alpha-2. Shipping phone. `0` authorizes; `1` authorizes and captures. URL where the final purchase response is expected. `1` if the customer wants to save their card in Tilopay; `0` if not. Yappy phone. Needed when the customer pays with Yappy. > **Warning:** `billToEmail` cannot be updated. Once `Init()` returned the customer's cards, the email > cannot be changed through this method. ## Identification types [#tipos-de-identificacion] Applies to the `typeDni` parameter, both here and in `Tilopay.Init()`. In the masks, `#` is a **numeric** character and `&` is **alphanumeric**. Lengths do not count hyphens. | Code | Type | Format | Length | |---|---|---|---| | 1 | Cédula de identidad | `0#-####-####` | 10 | | 2 | Cédula jurídica | `3-###-######` | 10 | | 3 | Gobierno central | `2-###-######` | 10 | | 4 | Institución autónoma | `4-###-######` | 10 | | 5 | Extranjero no residente | `9&&&&&&&&&&&&&&&&&&&` | 20 | | 6 | DIMEX | `1###########` | 12 | | 7 | DIDI | `5###########` | 12 | ## Response [#respuesta] ```json { "message": "Success" } ``` `Success` when the update succeeded; the error description otherwise. --- # Tilopay Banking API > Reference for Tilopay's Banking API (BaaS), generated from its OpenAPI 3.0.3 spec, version 1.0.0. - kind: api-index - status: stable - api_version: 1.0.0 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api-bancario With our Banking API you can initiate payments (PIN and SINPE Móvil), check their status, manage your accounts and receive payment event webhooks from your own systems, always limited to the accounts your access token authorizes. **It is a separate product from the payment gateway.** It runs on its own infrastructure, with its own host, credentials and authentication. If you already integrate our acquiring API, access to this one is enabled separately. Here you will find the official endpoints, models and flows for version **1.0.0** of the Banking API. ## What it can do [#que-permite] **Payments.** Create a payment, fetch it by identifier, list it by account and search it by `payment_id`, `public_id` or `client_reference`; the available methods are `PIN` and `SINPE_MOVIL`. Creation only accepts direction `OUT`; `IN` payments remain available in listings and webhooks. [See payment operations](/en/developers/api-bancario/pagos). **Accounts.** Validate a destination account before transferring, list your accounts with or without balances, check an account balance and request statements asynchronously. [See account operations](/en/developers/api-bancario/cuentas). **Webhooks.** Register your encrypted HTTPS destination, see the events you can subscribe to per payment method and the data structure we send in each one. [See webhook operations](/en/developers/api-bancario/webhooks). **Catalogs.** Customer identification person types per country. [See catalogs](/en/developers/api-bancario/catalogos). We also provide `GET /api/public/v1/healthz` for health checks, plus `GET /openapi.yaml` and `GET /docs` with the OpenAPI definition and interactive documentation right on the API host. ## Environments [#entornos] There are two environments, with the same routes and contracts. The sandbox one: ```text https://api-baas-sandbox.tilopay.com ``` Production is the same host without the `-sandbox` suffix: ```text https://api-baas.tilopay.com ``` Tilopay issues sandbox credentials on request; coordinate access with your merchant contact at Tilopay. When you are ready, we provide production credentials as we enable your access. ## Machine-readable artifacts [#artefactos] We publish the full Banking API OpenAPI definition, with open CORS, in two formats: - [openapi.json](/developers/api-bancario/openapi.json) - [openapi.yaml](/developers/api-bancario/openapi.yaml) It is independent from our acquiring API definition ([/developers/openapi.json](/developers/openapi.json)): they are two separate products. ## How to get access [#acceso] Access is restricted and granted on request, for operations in Costa Rica. To learn about the product and request it, visit [baas.tilopay.com](https://baas.tilopay.com), stating estimated monthly volume, use case and systems to integrate. ## Where to go next [#siguiente] 1. [Authentication](/en/developers/api-bancario/autenticacion) — login, token exchange and Bearer. 2. [Conventions](/en/developers/api-bancario/convenciones) — envelope, correlation, safe retries, pagination and dates. 3. [Response codes](/en/developers/api-bancario/errores) — full `response_code` catalog. --- # Banking API authentication > Login, token exchange and Bearer JWT usage in Tilopay's Banking API. - kind: concept - status: stable - api_version: 1.0.0 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api-bancario/autenticacion The Banking API uses a two-step flow: first you authenticate the customer with email and password, then you exchange the resulting token for a JWT access token that signs every other call. The three endpoints in the `Auth` group are the only ones that carry **no** `Authorization` header. Every other endpoint requires `Authorization: Bearer `. ## Step 1 — Login [#login] ```http POST /api/public/v1/auth/login ``` Required body: `email` (email format) and `password`. ```json { "email": "operations@merchant.com", "password": "••••••••" } ``` The `200` response returns in `data`: | Field | Type | Description | |---|---|---| | `gidp_id_token` | string | Identity token for step 2, valid for 1 hour and only usable for the exchange. | | `assignments` | array | Available customer contexts. | Each `assignments` item carries `assignment_id`, `owner_type`, `owner_id`, `tenant_id`, `tenant_code`, `country_code`, `role`, `roles` and `status`. Pick the `assignment_id` of the context you will operate with. ## Step 2 — Token exchange [#exchange] ```http POST /api/public/v1/auth/token/exchange ``` Required body: `gidp_id_token` (from step 1) and `assignment_id` (the chosen context). ```json { "gidp_id_token": "eyJhbGciOi...", "assignment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ``` The `200` response returns in `data`: | Field | Type | Description | |---|---|---| | `token` | string | JWT access token. | | `expires_in` | integer | Access-token lifetime in seconds; read `expires_in`, do not assume a fixed value. | | `claims` | object | Resolved token context. | `claims` includes `sub`, `subject_kind`, `user_type`, `tenant_id`, `owner_type`, `owner_id`, `roles`, `scopes`, `country_code`, `locale`, `channel`, `idp`, `idp_sub` and `idempotency_key`. `claims.idempotency_key` is reserved and always empty: ignore it. Idempotency uses the `Idempotency-Key` header. ## Step 3 — Use the Bearer token [#bearer] The declared security scheme is `BearerAuth`: HTTP bearer with JWT format. ```http Authorization: Bearer ``` Context travels **inside** the token: business operations do not take `tenant_id`, `owner_id` or `owner_type` as parameters. The token alone scopes which accounts and payments you can reach. When the token is absent, expired or invalid, the response is `401` with `response_code: UNAUTHORIZED`. If the token is valid but the context does not authorize the resource, it is `403` with `FORBIDDEN`. There is no refresh token: repeat login and exchange (steps 1 and 2) before expiration, recommended at **80% of `expires_in`**, or after receiving a `401`. ## Passwords [#password] ```http POST /api/public/v1/auth/request-set-password ``` Starts the set/reset password process for the given email. It requires no prior authentication and always responds the same way whether or not the account exists, so it never reveals if an email is registered. ```json { "email": "operations@merchant.com" } ``` ## Rate limits [#limites-de-tasa] They currently apply to authentication and account validation. Exceeding them returns `429` (`TOO_MANY_REQUESTS`). Retry with exponential backoff. ## Sandbox access [#sandbox] Tilopay issues sandbox credentials on request; coordinate access with your merchant contact at Tilopay. --- # Banking API conventions > Response envelope, correlation, safe retries, pagination and date format in the Banking API. - kind: concept - status: stable - api_version: 1.0.0 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api-bancario/convenciones Every Banking API operation shares the same response shape, the same pagination rules and the same date format. Here we explain those shared rules. ## Response envelope [#envelope] Every response, successful or not, travels in the same envelope: ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": {} } ``` | Field | Type | Description | |---|---|---| | `success` | boolean | `true` on successful responses; `false` on errors. | | `http_status_code` | integer | Repeats the HTTP status inside the body. | | `response_code` | string | Code from the [`response_code` catalog](/en/developers/api-bancario/errores). | | `message` | string | Human-readable response message. | | `correlation_id` | string | Call identifier. | | `data` | object | Operation payload. | Error responses also carry `errors`, a detail object that may include arbitrary fields such as `code`, `message` or `detail`. Branch your logic on `response_code`, never on the `message` text. ## Correlation [#correlacion] Send `X-Correlation-Id` on the request to propagate your own identifier; the value comes back as `correlation_id` in the envelope. Keep it in your logs: it is what support uses to trace a specific call. ## Safe retries [#reintentos] Two operations accept the `Idempotency-Key` header: ```http POST /api/public/v1/transactions/payments POST /api/public/v1/accounts/statements ``` Resending the same key with the same body returns the original result instead of creating a second record, so a network timeout can be retried without duplicating. If you reuse a key with a different body, the response is `409` with `response_code: IDEMPOTENCY_CONFLICT`. Generate one key per business intent (for example a UUID per order), not one per retry. Every other operation is a read and can be repeated with no additional effects. ## Pagination [#paginacion] The API uses two schemes depending on the resource. **Cursor, on the payment list.** `GET /api/public/v1/transactions/payments` accepts `limit` (default 20, maximum 100) and `cursor`. The response carries `pagination.next_cursor`: pass it as `cursor` on the next request. When it is empty or omitted, there are no more pages. **Offset, on accounts.** `GET /api/public/v1/accounts` and `GET /api/public/v1/accounts/balances` accept `limit` (1 to 100) and `offset` (from 0), and return `pagination` with `limit`, `offset` and `total`. ## Dates and amounts [#formatos] Date parameters and fields use RFC 3339, for example `2026-09-02T15:04:05Z`. The `date_from` and `date_to` filters on the payment list follow that same format. ## Host health and documentation [#host] The host itself exposes three service routes: ```http GET /api/public/v1/healthz GET /openapi.yaml GET /docs ``` `healthz` is for availability checks, `openapi.yaml` returns the OpenAPI definition and `/docs` the interactive documentation. We also publish it in this portal at [openapi.json](/developers/api-bancario/openapi.json) and [openapi.yaml](/developers/api-bancario/openapi.yaml). ## Public identifier [#public-id] `public_id` is a number in webhooks and text in REST; it is the same identifier. **Always treat it as text.** ## Rate limits [#limites-de-tasa] They currently apply to authentication and account validation. Exceeding them returns `429` (`TOO_MANY_REQUESTS`); retry with exponential backoff. --- # Banking API payments > Banking API payment operations: create, fetch, list and search PIN and SINPE Móvil payments. - kind: api-operation - status: stable - api_version: 1.0.0 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api-bancario/pagos 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. ```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 ``` ## Resource rules [#reglas] **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](/en/developers/api-bancario/cuentas#operaciones). **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](/en/developers/api-bancario/convenciones#reintentos). ## States [#estados] 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`](/en/developers/api-bancario/webhooks) event. Always check those two fields before treating a payment as final. ## Operations [#operaciones] ### List customer payments [#get-transactions-payments] ```http GET /api/public/v1/transactions/payments ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. 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** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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 | yes | Account **identifier scheme** (e.g. `IBAN`). This is **not** the internal account classification (e.g. operational). Values: IBAN | | `value` | query | string | yes | Account identifier value (IBAN). Spaces are ignored. | | `status` | query | string | no | 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) | no | Inclusive lower bound for `created_at`. RFC 3339 (UTC `Z` or offset; optional fractional seconds). | | `date_to` | query | string (date-time) | no | Inclusive upper bound for `created_at`. RFC 3339 (UTC `Z` or offset; optional fractional seconds). | | `client_reference` | query | string | no | Filter by partner client reference (exact match against the value sent at payment creation). | | `currency` | query | string | no | Filter by payment currency (ISO 4217, e.g. CRC). | | `payment_method_code` | query | string | no | Filter by public payment method code. Values: PIN, SINPE_MOVIL | | `direction` | query | string | no | 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 | no | Page size (default 20, max 100). | | `cursor` | query | string | no | Opaque cursor from a previous response `pagination.next_cursor`. | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `schema_version` | string | yes | Same value as webhook envelope `schema_version` (`1.0.0`). | | `items` | array | yes | — | | `items[].schema_version` | string | yes | Payment-read contract version. Same value as webhook envelope `schema_version` (`1.0.0`) for `payment.succeeded` / `payment.failed`. | | `items[].payment_id` | string (uuid) | yes | Payment UUID returned by create. Partner-facing payment key for `GET /transactions/payments/{payment_id}` and search. Not an internal account id. | | `items[].public_id` | string | no | Numeric 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[].type` | string | yes | Payment type derived from direction (`PAYIN` / `PAYOUT`). | | `items[].payment_method_code` | string | yes | Public payment method catalog (`PIN`, `SINPE_MOVIL`). Same values as webhooks. Values: PIN, SINPE_MOVIL | | `items[].status` | string | yes | Public 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_detail` | string | no | Operational (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[].result` | string | yes | Consolidated 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_at` | string (date-time) | no | Provider 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_reversal` | boolean | yes | True when a reversal was applied. Independent of `status`. Correlates with webhook `payment.reversed`. | | `items[].reversed_at` | string (date-time) | no | When the reversal was applied. Null when `has_reversal` is false. Can be null. | | `items[].created_at` | string (date-time) | no | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `items[].updated_at` | string (date-time) | no | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `items[].amount` | object | no | Same monetary shape as payment create (`PaymentAmount`). | | `items[].amount.amount` | string | yes | — | | `items[].amount.currency` | string | yes | — | | `items[].source` | object | no | Payment 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.account` | object | yes | Public account identifier (`type` + `value`). `null` when the IBAN or phone cannot be resolved. Never an internal account UUID. Can be null. | | `items[].source.name` | string | yes | Party name from processing metadata when available; otherwise `null`. Can be null. | | `items[].source.identification` | object | yes | Party 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[].destination` | object | no | Payment 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.account` | object | yes | Public account identifier (`type` + `value`). `null` when the IBAN or phone cannot be resolved. Never an internal account UUID. Can be null. | | `items[].destination.name` | string | yes | Party name from processing metadata when available; otherwise `null`. Can be null. | | `items[].destination.identification` | object | yes | Party 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_reference` | string | no | Partner reference sent at payment creation (stable after processing). Same field as webhook `payment.client_reference`. | | `items[].external_reference` | string | no | Rail/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[].provider` | object | no | Shared 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_id` | string | yes | Provider 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_at` | string (date-time) | yes | RFC 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_code` | string | yes | Raw 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_desc` | string | yes | Provider Motivo text (prefers Detalle). Same meaning as webhook `rejection.message`. `null` when there is no provider rejection. Can be null. | | `items[].provider.provider_status_semantic` | string | yes | Normalized platform semantic (e.g. `ACCOUNT_BLOCKED`). Same meaning as webhook `rejection.code`. `null` when there is no provider rejection. Can be null. | | `items[].error` | object | no | Always 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.domain` | string | yes | Same catalog as webhook `payment.error.domain`. Values: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal | | `items[].error.platform` | object | yes | — | | `pagination` | object | no | Cursor pagination. Pass next_cursor as cursor on the next request. | | `pagination.next_cursor` | string | no | Opaque cursor for the next page; omitted or empty when there is no next page | | `pagination.limit` | integer | no | Page size applied to this response | **Response example** ```json { "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** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 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` — requires `Authorization: Bearer `. 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** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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 | no | 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. | **Request body** | Field | Type | Required | Description | |---|---|---|---| | `payment_method_code` | string | yes | Public payment method catalog (`PIN`, `SINPE_MOVIL`). Values: PIN, SINPE_MOVIL | | `direction` | string | yes | Creation only accepts OUT (outbound). IN payments remain available in listings and webhooks. Values: OUT | | `country_code` | string | no | Operational country when multi-country applies | | `amount` | object | yes | — | | `amount.amount` | string | yes | Amount as a decimal string with exactly 2 fraction digits (e.g. "1000.00"). | | `amount.currency` | string | yes | ISO 4217 currency of the payment. Values: CRC, USD | | `source` | object | yes | — | | `source.account` | object | yes | — | | `source.account.type` | string | yes | Account **identifier scheme**. Only `IBAN` is supported here. This is not the internal account classification (e.g. operational). Values: IBAN | | `source.account.value` | string | yes | IBAN (spaces allowed; normalized server-side) | | `source.name` | string | yes | Account holder name | | `source.identification` | object | yes | 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 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.type` | string | no | 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). Values: cedula, juridica, dimex, gobierno, institucion_autonoma, didi, pasaporte | | `source.identification.number` | string | no | 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 SINPE display form. Supported types include `cedula`, `juridica`, `dimex`, `gobierno`, `institucion_autonoma`, `didi`, and `pasaporte`. | | `destination` | object | yes | — | | `destination.account` | object | yes | — | | `destination.account.type` | string | yes | Counterparty account **identifier scheme** (`IBAN` for PIN, `PHONE` for SINPE_MOVIL). Not the internal account classification. Values: IBAN, PHONE | | `destination.account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). Pattern for `PHONE`: `^\d{8}$`. | | `destination.name` | string | yes | Counterparty name | | `destination.identification` | object | yes | 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 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.type` | string | no | 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). Values: cedula, juridica, dimex, gobierno, institucion_autonoma, didi, pasaporte | | `destination.identification.number` | string | no | 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 SINPE display form. Supported types include `cedula`, `juridica`, `dimex`, `gobierno`, `institucion_autonoma`, `didi`, and `pasaporte`. | | `detail_reference` | string | yes | Operational / transactional description stored on the payment | | `client_reference` | string | no | 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 | no | — | | `originator` | object | no | Optional 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.company` | object | no | Can be null. | | `originator.company.legal_name` | string | no | — | | `originator.company.id_type` | string | no | — | | `originator.company.id_number` | string | no | — | | `originator.customer` | object | no | Can be null. | | `originator.customer.name` | string | no | — | | `originator.customer.id_type` | string | no | — | | `originator.customer.id_number` | string | no | — | **Request example** ```json { "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" } ``` **201** — The resource was created successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `payment_id` | string (uuid) | no | Internal payment UUID | | `public_id` | string | no | Public numeric payment identifier The same identifier is a number in webhooks and text in REST. Always treat it as text. | | `status` | string | no | Public 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_detail` | string | no | Operational status detail (e.g. PROCESSING) | | `payment_method_code` | string | no | Public payment method catalog (`PIN`, `SINPE_MOVIL`). Values: PIN, SINPE_MOVIL | | `created_at` | string (date-time) | no | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | **Response example** ```json { "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** | HTTP | response_code | Description | |---|---|---| | 400 | `PAYMENT_INVALID_PAYLOAD` | Invalid payment request payload. | | 400 | `INVALID_PAYMENT_METHOD` | Invalid or unsupported payment method. | | 400 | `INVALID_ACCOUNT_TYPE` | Unsupported or invalid account type. | | 400 | `ACCOUNT_INVALID` | The account is invalid or not enabled for the service. | | 400 | `ACCOUNT_HOLDER_MISMATCH` | Account holder does not match the provided identification or name. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 404 | `ACCOUNT_NOT_FOUND` | Account not found. | | 409 | `IDEMPOTENCY_CONFLICT` | Idempotency conflict: the same Idempotency-Key was reused with a different request body, or a previous request is still in progress. | | 409 | `PAYMENT_DUPLICATE` | An in-flight or completed payment with this client_reference already exists. | | 409 | `CONFLICT` | The request conflicts with the current state. | | 422 | `INSUFFICIENT_FUNDS` | Insufficient funds to complete the payment. | | 422 | `LIMIT_EXCEEDED` | A transaction or accumulation limit was exceeded. | | 422 | `UNPROCESSABLE_ENTITY` | The request is well-formed but cannot be processed due to business rules. | | 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` — requires `Authorization: Bearer `. 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** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | **Request body** | Field | Type | Required | Description | |---|---|---|---| | `account` | object | yes | — | | `account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `account.currency` | string | no | Optional account currency in response payloads. | | `payment_id` | string (uuid) | no | Internal payment UUID. | | `public_id` | string | no | Numeric public payment identifier. The same identifier is a number in webhooks and text in REST. Always treat it as text. | | `client_reference` | string | no | Partner reference sent at payment creation. | **Request example** — byPaymentId ```json { "account": { "type": "IBAN", "value": "CR08036900101010933608" }, "payment_id": "11111111-1111-1111-1111-111111111111" } ``` **Request example** — byPublicId ```json { "account": { "type": "IBAN", "value": "CR08036900101010933608" }, "public_id": "134729" } ``` **Request example** — byClientReference ```json { "account": { "type": "IBAN", "value": "CR08036900101010933608" }, "client_reference": "INV-01082" } ``` **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `schema_version` | string | yes | Payment-read contract version. Same value as webhook envelope `schema_version` (`1.0.0`) for `payment.succeeded` / `payment.failed`. | | `payment_id` | string (uuid) | yes | Payment UUID returned by create. Partner-facing payment key for `GET /transactions/payments/{payment_id}` and search. Not an internal account id. | | `public_id` | string | no | Numeric 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. | | `type` | string | yes | Payment type derived from direction (`PAYIN` / `PAYOUT`). | | `payment_method_code` | string | yes | Public payment method catalog (`PIN`, `SINPE_MOVIL`). Same values as webhooks. Values: PIN, SINPE_MOVIL | | `status` | string | yes | Public 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_detail` | string | no | Operational (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). | | `result` | string | yes | Consolidated 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_at` | string (date-time) | no | Provider 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_reversal` | boolean | yes | True when a reversal was applied. Independent of `status`. Correlates with webhook `payment.reversed`. | | `reversed_at` | string (date-time) | no | When the reversal was applied. Null when `has_reversal` is false. Can be null. | | `created_at` | string (date-time) | no | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `updated_at` | string (date-time) | no | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `amount` | object | no | Same monetary shape as payment create (`PaymentAmount`). | | `amount.amount` | string | yes | — | | `amount.currency` | string | yes | — | | `source` | object | no | Payment 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.account` | object | yes | Public account identifier (`type` + `value`). `null` when the IBAN or phone cannot be resolved. Never an internal account UUID. Can be null. | | `source.account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `source.account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `source.account.currency` | string | no | Optional account currency in response payloads. | | `source.name` | string | yes | Party name from processing metadata when available; otherwise `null`. Can be null. | | `source.identification` | object | yes | Party 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.type` | string | no | Identification catalog key (e.g. `cedula`, `juridica`). `null` when unknown. Can be null. | | `source.identification.number` | string | no | Public 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. | | `destination` | object | no | Payment 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.account` | object | yes | Public account identifier (`type` + `value`). `null` when the IBAN or phone cannot be resolved. Never an internal account UUID. Can be null. | | `destination.account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `destination.account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `destination.account.currency` | string | no | Optional account currency in response payloads. | | `destination.name` | string | yes | Party name from processing metadata when available; otherwise `null`. Can be null. | | `destination.identification` | object | yes | Party 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.type` | string | no | Identification catalog key (e.g. `cedula`, `juridica`). `null` when unknown. Can be null. | | `destination.identification.number` | string | no | Public 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_reference` | string | no | Partner reference sent at payment creation (stable after processing). Same field as webhook `payment.client_reference`. | | `external_reference` | string | no | Rail/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. | | `provider` | object | no | Shared 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_id` | string | yes | Provider 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_at` | string (date-time) | yes | RFC 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_code` | string | yes | Raw 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_desc` | string | yes | Provider Motivo text (prefers Detalle). Same meaning as webhook `rejection.message`. `null` when there is no provider rejection. Can be null. | | `provider.provider_status_semantic` | string | yes | Normalized platform semantic (e.g. `ACCOUNT_BLOCKED`). Same meaning as webhook `rejection.code`. `null` when there is no provider rejection. Can be null. | | `error` | object | no | Always 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.domain` | string | yes | Same catalog as webhook `payment.error.domain`. Values: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal | | `error.platform` | object | yes | — | | `error.platform.code` | string | yes | Stable platform public code (same as webhook `payment.error.platform.code`). | | `error.platform.message` | string | yes | Actionable message (prefers provider Detalle). Same as webhook `payment.error.platform.message`. | **Response example** ```json { "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** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 404 | `PAYMENT_NOT_FOUND` | Payment not found. | | 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` — requires `Authorization: Bearer `. 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** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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 | yes | Internal payment UUID or numeric public_id. | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `schema_version` | string | yes | Payment-read contract version. Same value as webhook envelope `schema_version` (`1.0.0`) for `payment.succeeded` / `payment.failed`. | | `payment_id` | string (uuid) | yes | Payment UUID returned by create. Partner-facing payment key for `GET /transactions/payments/{payment_id}` and search. Not an internal account id. | | `public_id` | string | no | Numeric 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. | | `type` | string | yes | Payment type derived from direction (`PAYIN` / `PAYOUT`). | | `payment_method_code` | string | yes | Public payment method catalog (`PIN`, `SINPE_MOVIL`). Same values as webhooks. Values: PIN, SINPE_MOVIL | | `status` | string | yes | Public 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_detail` | string | no | Operational (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). | | `result` | string | yes | Consolidated 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_at` | string (date-time) | no | Provider 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_reversal` | boolean | yes | True when a reversal was applied. Independent of `status`. Correlates with webhook `payment.reversed`. | | `reversed_at` | string (date-time) | no | When the reversal was applied. Null when `has_reversal` is false. Can be null. | | `created_at` | string (date-time) | no | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `updated_at` | string (date-time) | no | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `amount` | object | no | Same monetary shape as payment create (`PaymentAmount`). | | `amount.amount` | string | yes | — | | `amount.currency` | string | yes | — | | `source` | object | no | Payment 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.account` | object | yes | Public account identifier (`type` + `value`). `null` when the IBAN or phone cannot be resolved. Never an internal account UUID. Can be null. | | `source.account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `source.account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `source.account.currency` | string | no | Optional account currency in response payloads. | | `source.name` | string | yes | Party name from processing metadata when available; otherwise `null`. Can be null. | | `source.identification` | object | yes | Party 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.type` | string | no | Identification catalog key (e.g. `cedula`, `juridica`). `null` when unknown. Can be null. | | `source.identification.number` | string | no | Public 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. | | `destination` | object | no | Payment 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.account` | object | yes | Public account identifier (`type` + `value`). `null` when the IBAN or phone cannot be resolved. Never an internal account UUID. Can be null. | | `destination.account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `destination.account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `destination.account.currency` | string | no | Optional account currency in response payloads. | | `destination.name` | string | yes | Party name from processing metadata when available; otherwise `null`. Can be null. | | `destination.identification` | object | yes | Party 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.type` | string | no | Identification catalog key (e.g. `cedula`, `juridica`). `null` when unknown. Can be null. | | `destination.identification.number` | string | no | Public 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_reference` | string | no | Partner reference sent at payment creation (stable after processing). Same field as webhook `payment.client_reference`. | | `external_reference` | string | no | Rail/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. | | `provider` | object | no | Shared 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_id` | string | yes | Provider 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_at` | string (date-time) | yes | RFC 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_code` | string | yes | Raw 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_desc` | string | yes | Provider Motivo text (prefers Detalle). Same meaning as webhook `rejection.message`. `null` when there is no provider rejection. Can be null. | | `provider.provider_status_semantic` | string | yes | Normalized platform semantic (e.g. `ACCOUNT_BLOCKED`). Same meaning as webhook `rejection.code`. `null` when there is no provider rejection. Can be null. | | `error` | object | no | Always 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.domain` | string | yes | Same catalog as webhook `payment.error.domain`. Values: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal | | `error.platform` | object | yes | — | | `error.platform.code` | string | yes | Stable platform public code (same as webhook `payment.error.platform.code`). | | `error.platform.message` | string | yes | Actionable message (prefers provider Detalle). Same as webhook `payment.error.platform.message`. | **Response example** ```json { "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** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 404 | `PAYMENT_NOT_FOUND` | Payment not found. | | 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. | ## Processing and limits [#procesamiento] 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](/en/developers/agentes/mcp/api-bancario) transfer-confirmation flow is separate from this direct API creation. --- # Banking API accounts > Banking API account operations: validation, listing, balances and statements. - kind: api-operation - status: stable - api_version: 1.0.0 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api-bancario/cuentas The `Accounts` group covers the account as a resource: validate a destination account before transferring, list accessible accounts, check balances and request statements. ```http POST /api/public/v1/accounts/validate GET /api/public/v1/accounts POST /api/public/v1/accounts/balances GET /api/public/v1/accounts/balance POST /api/public/v1/accounts/statements GET /api/public/v1/accounts/statements/{request_id} ``` ## Resource rules [#reglas] **Scope.** The listing returns only the accounts the token authorizes; `tenant_id` and `owner_id` are never sent as parameters. **Validate first.** Validate the destination account before creating a payment: the operation confirms the account exists and is eligible, and returns the holder data the service exposes. **Asynchronous statements.** `POST /api/public/v1/accounts/statements` accepts `Idempotency-Key` and always returns **HTTP 202 (`ACCEPTED`)** with `request_id` and `status: PENDING`; generation is asynchronous. Poll `GET /api/public/v1/accounts/statements/{request_id}`: it returns **HTTP 200** with `status` `PENDING`, `PROCESSING`, `DONE` or `ERROR`. `DONE` includes `download_url`, **valid for 48 hours**. See [safe retries](/en/developers/api-bancario/convenciones#reintentos). **Pagination.** `GET /api/public/v1/accounts` and `GET /api/public/v1/accounts/balances` use `limit` and `offset`, and return `pagination` with `limit`, `offset` and `total`. ## Operations [#operaciones] ### Validate external account for a payment method [#post-accounts-validate] ```http POST /api/public/v1/accounts/validate ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Validates a counterparty account (IBAN or phone) via the SINPE provider for the given payment method. Does not validate internal platform 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 SINPE Móvil wallet query 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 (account query in SINPE) 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 value table below). **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`. **Account / profile / limits** | 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 | **Settlement with destination institution** | 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 | Operación no autorizada entre las entidades participantes | | 209 | Pendientes de Cumplimiento | | 210 | Conciliación de la transacción no fue posible | **Identification** | 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 | **Format validation / Core Banking** | Code | Message | |---|---| | 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 | **Channel / identification type** | 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 (wallet)** | 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 | **`rejection.code` values** | Value | Meaning | |---|---| | `ACCOUNT_CLOSED` | Account closed | | `ACCOUNT_NOT_FOUND` | Account not found | | `ACCOUNT_BLOCKED` | Account blocked | | `INVALID_ACCOUNT` | Invalid account | | `IDENT_MISMATCH` | Identification does not match | | `IDENT_TYPE_INVALID` | Invalid identification type | | `INVALID_ID_FORMAT` | Invalid identification format | | `VALIDATION_ERROR` | Validation error | | `CURRENCY_MISMATCH` | Currency mismatch | | `NOT_AUTHORIZED_BY_RECIPIENT` | Not authorized by the recipient | | `PROFILE_NOT_ALLOWED` | Profile not allowed | | `CHANNEL_NOT_RECOGNIZED` | Channel not recognized | | `COMMUNICATION_ERROR` | Communication error | | `RETRY_LATER` | Retry later | | `WALLET_PHONE_ORIGIN_INVALID` | Origin phone invalid (SINPE Móvil) | | `WALLET_PHONE_ORIGIN_NOT_ENABLED` | Origin phone without wallet service | | `WALLET_PHONE_DEST_INVALID` | Destination phone invalid (SINPE Móvil) | | `WALLET_PHONE_NOT_REGISTERED_BCCR` | Phone not registered in the BCCR mobile registry | | `WALLET_NOT_FOUND` | Wallet not found | | `WALLET_PHONE_ALREADY_ACTIVE` | Phone already has an active wallet | | `PHONE_NOT_REGISTERED` | Phone not registered | | `SINPE_REJECTED` | Rejected by SINPE (generic fallback) | Rate limits currently apply to authentication and account validation. Exceeding them returns HTTP 429 (TOO_MANY_REQUESTS). Retry with exponential backoff. SINPE code 1001 means the account exists and is active; it is a valid result, not a rejection reason. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | **Request body** | Field | Type | Required | Description | |---|---|---|---| | `payment_method_code` | string | yes | Payment method used to route validation (PIN for external IBAN, SINPE_MOVIL for phone wallet). Values: PIN, SINPE_MOVIL | | `account` | object | yes | — | | `account.type` | string | yes | Values: IBAN, PHONE | | `account.value` | string | yes | — | **Request example** — pin ```json { "payment_method_code": "PIN", "account": { "type": "IBAN", "value": "CR16010400000000000031" } } ``` **Request example** — sinpe_movil ```json { "payment_method_code": "SINPE_MOVIL", "account": { "type": "PHONE", "value": "88887777" } } ``` **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `is_valid` | boolean | no | — | | `account` | object | no | — | | `account.type` | string | no | — | | `account.value` | string | no | — | | `account.currency` | string | no | — | | `account.product_name` | string | no | — | | `holder` | object | no | Account holder when validation succeeds. `id_type` matches `PaymentIdentification.type` (same catalog as POST /payments). For SINPE_MOVIL, `id_type` may be `unknown` because the wallet operation does not return an identification type. That is expected and stable; do not treat it as a bug. Can be null. | | `holder.name` | string | no | — | | `holder.id_type` | string | no | Identification catalog key (lowercase), e.g. `cedula`, `juridica`, `dimex`. Same vocabulary as `identification.type` on payment create. PIN: mapped from the provider account query (confirmed by the provider). SINPE_MOVIL: the wallet query does not return an identification type, so this field **may be `unknown`**. That is expected, stable behavior due to available provider data — not a mapping bug or a transient error. Do not assume a confirmed type is always present. Values: cedula, dimex, gobierno, juridica, institucion_autonoma, didi, pasaporte, unknown | | `holder.id_number` | string | no | — | | `financial_institution` | object | no | Counterparty institution when validation succeeds. PIN fills `entity_name` from the SINPE account query. SINPE_MOVIL only receives an entity **code** from the wallet query; it does not return the institution name, so `entity_name` **may be `""`**. That is expected, stable behavior — not a transient error. Do not assume `entity_name` is always populated. Can be null. | | `financial_institution.country_code` | string | no | — | | `financial_institution.entity_code` | string | no | — | | `financial_institution.entity_name` | string | no | Human-readable institution name. PIN: populated from the provider account-info operation. SINPE_MOVIL: may be `""` because the wallet query does not expose the name. Expected limitation, not an error. | | `rejection` | object | no | Present when `is_valid` is `false` and the provider rejected the account. `null` when `is_valid` is `true`. Can be null. | | `rejection.code` | string | yes | Normalized platform semantic derived from `reason_code`. Only a subset of `reason_code` values (see the catalog in the description of `POST /accounts/validate` and of the `payment.failed` webhook contract endpoint) has a distinct entry here — for any other numeric `reason_code`, `code` is `SINPE_REJECTED` even when `message` is specific (e.g. `reason_code = 21` "fondos insuficientes" still returns `code = SINPE_REJECTED`, it has no dedicated semantic). `PHONE_NOT_REGISTERED` is the fallback when `reason_code` is non-numeric (SINPE_MOVIL wallet flow). Values: ACCOUNT_CLOSED, ACCOUNT_NOT_FOUND, ACCOUNT_BLOCKED, INVALID_ACCOUNT, IDENT_MISMATCH, IDENT_TYPE_INVALID, INVALID_ID_FORMAT, VALIDATION_ERROR, CURRENCY_MISMATCH, NOT_AUTHORIZED_BY_RECIPIENT, PROFILE_NOT_ALLOWED, CHANNEL_NOT_RECOGNIZED, COMMUNICATION_ERROR, RETRY_LATER, WALLET_PHONE_ORIGIN_INVALID, WALLET_PHONE_ORIGIN_NOT_ENABLED, WALLET_PHONE_DEST_INVALID, WALLET_PHONE_NOT_REGISTERED_BCCR, WALLET_NOT_FOUND, WALLET_PHONE_ALREADY_ACTIVE, PHONE_NOT_REGISTERED, SINPE_REJECTED | | `rejection.reason_code` | string | yes | Raw Motivo/code returned by SINPE. See the endpoint description of `POST /accounts/validate` for the full code catalog (shared by this object wherever it appears, including the `payment.failed` webhook). A code not listed there still produces a response — `message` falls back to a generic text and `code` falls back to `SINPE_REJECTED`. | | `rejection.message` | string | yes | Descriptive Motivo text; prefers provider `Detalle` when present. | | `provider` | object | no | — | | `provider.operation` | string | no | — | | `provider.correlation_id` | string | no | Can be null. | | `provider.occurred_at` | string (date-time) | no | Can be null. | **Response example** — valid ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "is_valid": true, "account": { "type": "IBAN", "value": "CR16010400000000000031", "currency": "CRC", "product_name": "Cuenta corriente" }, "holder": { "name": "Juan Perez", "id_type": "cedula" }, "financial_institution": { "entity_code": "104", "entity_name": "Banco Ejemplo" }, "provider": { "reason_code": "0", "message": "Cuenta válida", "code": null }, "rejection": null } } ``` **Response example** — rejected ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "is_valid": false, "account": { "type": "IBAN", "value": "CR16010400000000000099", "currency": "CRC", "product_name": "" }, "holder": null, "financial_institution": { "entity_code": "104", "entity_name": "Banco Ejemplo" }, "provider": { "reason_code": "28", "message": "Cuenta cliente no existe", "code": "ACCOUNT_NOT_FOUND" }, "rejection": { "code": "ACCOUNT_NOT_FOUND", "reason_code": "28", "message": "Cuenta cliente no existe" } } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 400 | `INVALID_ACCOUNT_TYPE` | Unsupported or invalid account type. | | 400 | `INVALID_ACCOUNT_FORMAT` | Invalid account format. | | 400 | `INVALID_PAYMENT_METHOD` | Invalid or unsupported payment method. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 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. | ### List balances for accessible accounts [#get-accounts-balances] ```http GET /api/public/v1/accounts/balances ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Returns paginated account balances (`account`, `amounts`, `as_of`). This endpoint checks the current balance in real time and may respond more slowly than `GET /accounts`. For account attributes without amounts, use `GET /accounts`. Optional filters use the public account identification standard: `type` + `value` (identifier scheme + value). `type` is not the internal account classification. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | | `limit` | query | integer | no | — | | `offset` | query | integer | no | — | | `type` | query | string | no | Optional filter by account **identifier scheme** (e.g. `IBAN`). Not the internal account classification. | | `value` | query | string | no | Optional filter by account identifier value (partial match supported for list endpoints). | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `items` | array | no | — | | `items[].account` | object | yes | — | | `items[].account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `items[].account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `items[].account.currency` | string | no | Optional account currency in response payloads. | | `items[].amounts` | object | yes | Ledger balances as decimal strings with 2 fraction digits (same display scale as public payment amounts). Prefer string over JSON number for financial amounts. | | `items[].amounts.available` | string | no | — | | `items[].amounts.posted` | string | no | — | | `items[].amounts.inbound_in_transit` | string | no | — | | `items[].amounts.outbound_in_transit` | string | no | — | | `items[].as_of` | string (date-time) | yes | Ledger timestamp when known (RFC 3339 UTC, second precision, `Z`); `null` when not available. Can be null. | | `pagination` | object | no | — | | `pagination.limit` | integer | no | — | | `pagination.offset` | integer | no | — | | `pagination.total` | integer | no | — | **Response example** ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "items": [ { "account": { "type": "IBAN", "value": "CR26036900101010588061", "currency": "CRC" }, "amounts": { "available": "991900.00", "posted": "991900.00", "inbound_in_transit": "0.00", "outbound_in_transit": "0.00" }, "as_of": "2026-01-15T12:00:00Z" } ], "pagination": { "limit": 20, "offset": 0, "total": 1 } } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 404 | `NOT_FOUND` | The requested resource was not found. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 502 | — | Dependency error | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | ### List accessible accounts without balances [#get-accounts] ```http GET /api/public/v1/accounts ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Returns paginated accessible accounts without querying current balances. Each item is account attributes only (`account.type`, `account.value`, `account.currency`). `amounts` and `as_of` are not included; use `GET /accounts/balances` or `POST /accounts/balance`. Optional filters use the public account identification standard: `type` + `value` (identifier scheme + value). `type` is not the internal account classification. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | | `limit` | query | integer | no | — | | `offset` | query | integer | no | — | | `type` | query | string | no | Optional filter by account **identifier scheme** (e.g. `IBAN`). Not the internal account classification. | | `value` | query | string | no | Optional filter by account identifier value (partial match supported for list endpoints). | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `items` | array | no | — | | `items[].account` | object | yes | — | | `items[].account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `items[].account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `items[].account.currency` | string | no | Optional account currency in response payloads. | | `pagination` | object | no | — | | `pagination.limit` | integer | no | — | | `pagination.offset` | integer | no | — | | `pagination.total` | integer | no | — | **Response example** ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "items": [ { "account": { "type": "IBAN", "value": "CR26036900101010588061", "currency": "CRC" } } ], "pagination": { "limit": 20, "offset": 0, "total": 1 } } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 404 | `NOT_FOUND` | The requested resource was not found. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 502 | — | Dependency error | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | ### Get account balance [#post-accounts-balance] ```http POST /api/public/v1/accounts/balance ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Returns a single account snapshot with `amounts` and `as_of`. For account attributes without balances, use `GET /accounts`. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | **Request body** | Field | Type | Required | Description | |---|---|---|---| | `account` | object | yes | — | | `account.type` | string | yes | Account **identifier scheme**. Only `IBAN` is supported here. This is not the internal account classification (e.g. operational). Values: IBAN | | `account.value` | string | yes | IBAN (spaces allowed; normalized server-side) | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `account` | object | yes | — | | `account.type` | string | yes | Account **identifier scheme** (e.g. `IBAN`, `PHONE`). Not the internal account classification (e.g. operational). Values: IBAN, PHONE | | `account.value` | string | yes | When `type` is `PHONE` (SINPE Móvil): 8 local digits, no country prefix (e.g. "88887777"). | | `account.currency` | string | no | Optional account currency in response payloads. | | `amounts` | object | yes | Ledger balances as decimal strings with 2 fraction digits (same display scale as public payment amounts). Prefer string over JSON number for financial amounts. | | `amounts.available` | string | no | — | | `amounts.posted` | string | no | — | | `amounts.inbound_in_transit` | string | no | — | | `amounts.outbound_in_transit` | string | no | — | | `as_of` | string (date-time) | yes | Ledger timestamp when known (RFC 3339 UTC, second precision, `Z`); `null` when not available. Can be null. | **Response example** ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "account": { "type": "IBAN", "value": "CR26036900101010588061", "currency": "CRC" }, "amounts": { "available": "991900.00", "posted": "991900.00", "inbound_in_transit": "0.00", "outbound_in_transit": "0.00" }, "as_of": "2026-01-15T12:00:00Z" } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 404 | `ACCOUNT_NOT_FOUND` | Account not found. | | 422 | `ACCOUNT_NOT_CONFIGURED` | This account is not mapped in the system. Please contact support to configure it correctly. | | 422 | `UNPROCESSABLE_ENTITY` | The request is well-formed but cannot be processed due to business rules. | | 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. | ### Request account statement generation [#post-accounts-statements] ```http POST /api/public/v1/accounts/statements ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Creates an asynchronous account statement request for a date range (max 60 days). Identify the account with `account.type` + `account.value` (identifier scheme + value, e.g. IBAN). This follows the public account identification standard. `account.type` is not the internal account classification. Returns `{request_id, status: PENDING}` immediately. Poll `GET /accounts/statements/{request_id}` for status; when `DONE`, the response includes a signed `download_url`. Always returns HTTP 202 (ACCEPTED) with request_id and status PENDING. Generation is asynchronous. Poll GET /api/public/v1/accounts/statements/{request_id}: HTTP 200 with PENDING, PROCESSING, DONE or ERROR. DONE includes download_url, valid for 48 hours. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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 | no | — | **Request body** | Field | Type | Required | Description | |---|---|---|---| | `account` | object | yes | — | | `account.type` | string | yes | Account **identifier scheme**. Only `IBAN` is supported here. This is not the internal account classification (e.g. operational). Values: IBAN | | `account.value` | string | yes | IBAN (spaces allowed; normalized server-side) | | `date_from` | string (date-time) | yes | RFC 3339 timestamp accepted on requests. UTC `Z` or a numeric offset is allowed. Optional fractional seconds are accepted and truncated to seconds. | | `date_to` | string (date-time) | yes | RFC 3339 (max 60 days after date_from) | | `notify_email` | boolean | no | — | **202** — Statement request accepted for asynchronous generation. `response_code`: `ACCEPTED` Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `request_id` | string (uuid) | yes | — | | `status` | string | yes | Values: PENDING | **Response example** ```json { "success": true, "http_status_code": 202, "response_code": "ACCEPTED", "message": "Statement request accepted.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "request_id": "5f3b2c1d-9e8a-4b7c-8d6e-5f4a3b2c1d0e", "status": "PENDING" } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 403 | `ACCOUNT_ACCESS_DENIED` | The actor does not have access to the requested account. | | 404 | `NOT_FOUND` | The requested resource was not found. | | 409 | `IDEMPOTENCY_CONFLICT` | Idempotency conflict: the same Idempotency-Key was reused with a different request body, or a previous request is still in progress. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 502 | — | Dependency error | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | ### Get account statement request status [#get-accounts-statements-request-id] ```http GET /api/public/v1/accounts/statements/{request_id} ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Returns request status. When `DONE`, includes a signed, time-limited `download_url` (48h from generation) and `expires_at`. When `ERROR`, includes a generic `message` without internal details. Unknown or unauthorized `request_id` returns 404. Returns HTTP 200 with status PENDING, PROCESSING, DONE or ERROR. DONE includes download_url, valid for 48 hours. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | | `request_id` | path | string (uuid) | yes | — | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `request_id` | string (uuid) | yes | — | | `status` | string | yes | Values: PENDING, PROCESSING, DONE, ERROR | | `download_url` | string (uri) | no | Signed download URL, present when status is DONE; valid for 48 hours from generation. | | `expires_at` | string (date-time) | no | Expiration of download_url (48h from generation) | | `message` | string | no | Generic error message when status is ERROR | **Response example** ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "request_id": "5f3b2c1d-9e8a-4b7c-8d6e-5f4a3b2c1d0e", "status": "DONE", "download_url": "https://storage.example.com/statements/5f3b2c1d.pdf?signature=example", "expires_at": "2026-01-17T12:00:00Z", "message": null } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 404 | `NOT_FOUND` | The requested resource was not found. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | ## Validation and SINPE codes [#validacion-sinpe] Code `1001` means the account exists and is active: it is not a rejection. `208` means “Operation not authorized between the participating institutions” and `209`, “Pending compliance”. Account validation is rate-limited: exceeding the limit returns `429` (`TOO_MANY_REQUESTS`). Retry with exponential backoff. --- # Banking API webhooks > Destination registration, subscribable features and payload contracts for Banking API webhooks. - kind: api-operation - status: stable - api_version: 1.0.0 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api-bancario/webhooks The `Webhooks` group lets you register the HTTPS destination where you receive events, see which features you can subscribe to per payment method and fetch each event's payload contract before writing the receiver. ```http POST /api/public/v1/webhook/destination GET /api/public/v1/webhook/features GET /api/public/v1/webhook/event-contracts/{event_type} ``` ## Events [#eventos] Our API notifies you of four event types: | Event | When it arrives | |---|---| | `payin.posted` | An inbound movement was credited. | | `payment.succeeded` | The payment finished successfully. | | `payment.failed` | The payment finished with a failure. | | `payment.reversed` | The payment was reversed. | A reversed payment keeps the `posted` state and is told apart by `result: reversed` and `has_reversal: true`; see [payment states](/en/developers/api-bancario/pagos#estados). ## Receiver best practices [#receptor] Delivery is attempted up to **3 times per event** and **any HTTP `2xx`** counts as success. Respond `2xx` as soon as you receive the event and process it separately: a slow receiver causes redeliveries. Treat events as potentially repeated and deduplicate by the envelope's `event.event_id` (retries redeliver the same event with the same `event_id`). And do not trust the final state to the webhook alone: confirm it with [the payment lookup](/en/developers/api-bancario/pagos#operaciones). ## Operations [#operaciones] ### Create webhook destination [#post-webhook-destination] ```http POST /api/public/v1/webhook/destination ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Valid fields depend on `destination_kind` and `scope_kind`. Send only the fields that apply to your combination; other scope fields must be omitted (not null placeholders). **Event payloads** Inspect outbound contracts with `GET /api/public/v1/webhook/event-contracts/{event_type}` (`payin.posted`, `payment.succeeded`, `payment.failed`, `payment.reversed`). **destination_kind** - `EXTERNAL_WEBHOOK` (only kind available on the public API): requires `webhook_url` (must start with `https://`) and `secret_ref`. Optional delivery tuning (`timeout_ms`, `max_attempts`). Payloads are always encrypted with `AES_GCM` using `secret_ref`; the HTTP body is `{"data":""}`. - `INTERNAL` is reserved for platform administration and cannot be created through this endpoint. **scope_kind** - `TENANT`: omit `scope_account_id`, `scope_owner_type`, and `scope_owner_id`. - `ACCOUNT`: require `scope_account_id`; omit owner fields. - `OWNER`: require `scope_owner_type` and `scope_owner_id`; omit `scope_account_id`. `scope_owner_type` is one of `tenant`, `partner`, `user`, `platform`, `customer` (lowercase in payload). ## Webhook encryption (EXTERNAL_WEBHOOK) Deliveries always send `Content-Type: application/json` and `Accept: application/json`. The HTTP body is always `{"data": ""}`. The base64-decoded bytes are: ``` nonce (12 bytes, random) || AES-256-GCM ciphertext || 16-byte auth tag ``` ``` key = SHA256(secret_ref) # 32 bytes → AES-256 wire = base64_decode(data) nonce, sealed = wire[:12], wire[12:] # sealed = ciphertext || tag plaintext = AES-GCM-Open(key, nonce, sealed, additional_data = none) ``` No additional authenticated data (AAD) is used. `plaintext` is the same JSON envelope documented per event type in `GET /webhook/event-contracts/{event_type}` — decrypt first, then apply that contract. Authenticity is provided by the GCM auth tag (decryption fails if the body was tampered with). Deduplicate retries using `event.event_id` in the payload envelope — delivery retries can redeliver the same event with the same `event_id`. Delivery is attempted up to 3 times per event. Any HTTP 2xx response counts as successful delivery. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | **Request body** | Field | Type | Required | Description | |---|---|---|---| | `feature_id` | string (uuid) | yes | Feature to subscribe (from `GET /api/public/v1/webhook/features`). Must exist or create fails. | | `is_enabled` | boolean | no | Defaults to true if omitted. | | `destination_kind` | string | yes | Public API accepts only `EXTERNAL_WEBHOOK` (HTTPS callback with shared secret). `INTERNAL` destinations are managed administratively. Values: EXTERNAL_WEBHOOK | | `destination_ref` | string | no | Not used on the public API. Reserved for internal destinations. Can be null. | | `webhook_url` | string (uri) | no | Required when `destination_kind = EXTERNAL_WEBHOOK` (must use `https://`). Can be null. | | `secret_ref` | string | no | Required when `destination_kind = EXTERNAL_WEBHOOK`: shared secret used to decrypt AES_GCM payloads (`key = SHA256(secret_ref)`). See "Webhook encryption" in this operation's description. Can be null. | | `signature_algo` | string | no | Fixed to `AES_GCM` for `EXTERNAL_WEBHOOK`. Omitted on create; stored as `AES_GCM`. `HMAC_SHA256` is no longer supported. Can be null. Values: AES_GCM | | `replay_window_sec` | integer | no | Reserved for legacy destinations. Not used for `EXTERNAL_WEBHOOK` AES_GCM deliveries. Can be null. | | `timeout_ms` | integer | no | HTTP timeout for delivery attempts (`EXTERNAL_WEBHOOK`). Can be null. | | `max_attempts` | integer | no | Delivery attempts per event, up to 3. Can be null. | | `scope_kind` | string | yes | - `TENANT`: no scope id fields. - `ACCOUNT`: set `scope_account_id` only. - `OWNER`: set `scope_owner_type` and `scope_owner_id` only. Values: TENANT, ACCOUNT, OWNER | | `scope_account_id` | string (uuid) | no | Required when `scope_kind` is `ACCOUNT`. Must be omitted for `TENANT` and `OWNER`. Can be null. | | `scope_owner_type` | string | no | Required when `scope_kind` is `OWNER`. Allowed values (lowercase) `tenant`, `partner`, `user`, `platform`, `customer`. Omit for `TENANT` and `ACCOUNT`. Can be null. | | `scope_owner_id` | string (uuid) | no | Required when `scope_kind` is `OWNER`. Omit for `TENANT` and `ACCOUNT`. Can be null. | **Request example** — external_webhook_tenant ```json { "feature_id": "11111111-1111-1111-1111-111111111111", "destination_kind": "EXTERNAL_WEBHOOK", "scope_kind": "TENANT", "webhook_url": "https://example.com/hooks/tilopay", "secret_ref": "whsec_example_shared_secret" } ``` **201** — The resource was created successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | no | — | | `feature_id` | string (uuid) | no | — | | `is_enabled` | boolean | no | — | | `destination_kind` | string | no | — | | `destination_ref` | string | no | — | | `webhook_url` | string | no | — | | `secret_ref` | string | no | — | | `signature_algo` | string | no | — | | `replay_window_sec` | integer | no | — | | `timeout_ms` | integer | no | — | | `max_attempts` | integer | no | Delivery attempts per event, up to 3. | | `scope_kind` | string | no | — | | `scope_account_id` | string (uuid) | no | — | | `scope_owner_type` | string | no | — | | `scope_owner_id` | string (uuid) | no | — | | `created_at` | string | no | — | | `updated_at` | string | no | — | **Response example** ```json { "success": true, "http_status_code": 201, "response_code": "CREATED", "message": "The resource was created successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "feature_id": "11111111-1111-1111-1111-111111111111", "is_enabled": true, "destination_kind": "EXTERNAL_WEBHOOK", "destination_ref": null, "webhook_url": "https://example.com/hooks/tilopay", "secret_ref": "whsec_example_shared_secret", "signature_algo": "AES_GCM", "replay_window_sec": null, "timeout_ms": 10000, "max_attempts": 8, "scope_kind": "TENANT", "scope_account_id": null, "scope_owner_type": null, "scope_owner_id": null, "created_at": "2026-01-15T12:00:00Z", "updated_at": "2026-01-15T12:00:00Z" } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 400 | `INVALID_REQUEST` | Invalid request. Check the required fields and try again. | | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | ### List webhook features by payment method [#get-webhook-features] ```http GET /api/public/v1/webhook/features ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Returns feature IDs and the `event_types` each feature can emit. Tenant scope is taken from the access token; clients must not send `tenant_id`. **Naming:** `event_types` use `payment.*` for resource-level outcomes (`payment.succeeded`, `payment.failed`, `payment.reversed`) and `payin.posted` for the inbound direction only (funds received). There is no `payout.posted` and no `transaction.*` events. The delivered JSON object is always `payment`. Inspect payload shapes with `GET /api/public/v1/webhook/event-contracts/{event_type}` (`payin.posted`, `payment.succeeded`, `payment.failed`, `payment.reversed`). **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | | `country_code` | query | string | no | — | | `payment_method_code` | query | string | no | — | | `webhook_enabled` | query | boolean | no | — | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `items` | array | no | — | | `items[].id` | string (uuid) | yes | — | | `items[].event_types` | array | yes | Public event types this feature can emit. `payment.*` = resource-level outcomes (any direction). `payin.posted` = inbound direction only (funds received). There is no `payout.posted` and no `transaction.*`. Inspect payload shapes with `GET /api/public/v1/webhook/event-contracts/{event_type}`. | **Response example** ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "items": [ { "id": "11111111-1111-1111-1111-111111111111", "event_types": [ "payin.posted", "payment.succeeded", "payment.failed", "payment.reversed" ] } ] } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | ### Webhook event contract [#get-webhook-event-contracts-event-type] ```http GET /api/public/v1/webhook/event-contracts/{event_type} ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Returns the outbound webhook payload contract for one public event type (`schema_version` 1.0.0). The envelope object is always `payment` (never `payin`, `payout`, or `transaction`). **When to use each name** - **payment** — resource and envelope. Outcome events: `payment.succeeded`, `payment.failed`, `payment.reversed` (both PAYIN and PAYOUT). Correlate with REST `/transactions/payments`. - **payin / payout** — direction (`PAYIN` inbound, `PAYOUT` outbound). `payin.posted` is inbound-only (ledger posted / funds received). There is no public `payout.posted`. - **transaction** — REST path prefix only. Not used in webhook `event_type` or payload objects. Supported `event_type` values: - `payin.posted` — inbound (PAYIN) payment reached public status `posted`. Envelope field is `payment`. `payment.local_payment` is a JSON boolean (`true`/`false`), not a string. - `payment.succeeded` — the provider confirmed success for a payment (PAYIN or PAYOUT; PIN or SINPE Móvil). Resource-level outcome, not direction-specific. - `payment.failed` — processing ended in a definitive failure (public `status=failed`), with structured `payment.error` (`domain` / `platform`). When the provider rejected the payment, the envelope also includes `rejection`. Not used for later reversals. - `payment.reversed` — subsequent reversal of a payment that already progressed (typically `status=posted` or `confirmed`). REST `result=reversed` and `has_reversal=true`. Processing `status` is unchanged. Not a `payment.failed`. Unknown `event_type` values (including internal-only `payment.status_changed`) return `404`. ## SINPE rejection codes (`rejection`, only on `payment.failed`) When `event_type = payment.failed` and the provider rejected the payment, the envelope includes `rejection` (same object and catalog as `data.rejection` on `POST /accounts/validate`). `rejection.code` is one of a fixed set of normalized semantics (see the value table below). **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`). 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`. **Account / profile / limits** | 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 | **Settlement with destination institution** | 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 | Operación no autorizada entre las entidades participantes | | 209 | Pendientes de Cumplimiento | | 210 | Conciliación de la transacción no fue posible | **Identification** | 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 | **Format validation / Core Banking** | Code | Message | |---|---| | 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 | **Channel / identification type** | 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 (wallet)** | 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 | Delivery body is JSON. `EXTERNAL_WEBHOOK` HTTP body is always `{"data":""}` (AES-256-GCM, key = SHA256(secret_ref)). Decrypt to obtain the event envelope documented per `GET /webhook/event-contracts/{event_type}`; the decrypted plaintext matches that contract. **`rejection.code` values** | Value | Meaning | |---|---| | `ACCOUNT_CLOSED` | Account closed | | `ACCOUNT_NOT_FOUND` | Account not found | | `ACCOUNT_BLOCKED` | Account blocked | | `INVALID_ACCOUNT` | Invalid account | | `IDENT_MISMATCH` | Identification does not match | | `IDENT_TYPE_INVALID` | Invalid identification type | | `INVALID_ID_FORMAT` | Invalid identification format | | `VALIDATION_ERROR` | Validation error | | `CURRENCY_MISMATCH` | Currency mismatch | | `NOT_AUTHORIZED_BY_RECIPIENT` | Not authorized by the recipient | | `PROFILE_NOT_ALLOWED` | Profile not allowed | | `CHANNEL_NOT_RECOGNIZED` | Channel not recognized | | `COMMUNICATION_ERROR` | Communication error | | `RETRY_LATER` | Retry later | | `WALLET_PHONE_ORIGIN_INVALID` | Origin phone invalid (SINPE Móvil) | | `WALLET_PHONE_ORIGIN_NOT_ENABLED` | Origin phone without wallet service | | `WALLET_PHONE_DEST_INVALID` | Destination phone invalid (SINPE Móvil) | | `WALLET_PHONE_NOT_REGISTERED_BCCR` | Phone not registered in the BCCR mobile registry | | `WALLET_NOT_FOUND` | Wallet not found | | `WALLET_PHONE_ALREADY_ACTIVE` | Phone already has an active wallet | | `PHONE_NOT_REGISTERED` | Phone not registered | | `SINPE_REJECTED` | Rejected by SINPE (generic fallback) | Delivery is attempted up to 3 times per event. Any HTTP 2xx response counts as successful delivery. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | | `event_type` | path | string | yes | Values: payin.posted, payment.succeeded, payment.failed, payment.reversed | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `event_type` | string | yes | Values: payin.posted, payment.succeeded, payment.failed, payment.reversed | | `schema_version` | string | yes | — | | `description` | string | yes | — | | `example` | object | yes | — | **Response example** ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "event_type": "payment.succeeded", "schema_version": "1.0.0", "description": "Envelope delivered for `payment.succeeded` (decrypt AES_GCM `data` field first).", "example": { "event_id": "evt_01JEXAMPLE", "event_type": "payment.succeeded", "schema_version": "1.0.0" }, "x-description-es": "Sobre entregado para `payment.succeeded` (descifrá primero el campo `data` AES_GCM)." } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 404 | `NOT_FOUND` | The requested resource was not found. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | ## Payload contracts [#contratos] Each event has its own data structure. This is what we send you in each one: ### payin.posted [#contrato-payin-posted] Data we send for `payin.posted`. | Field | Type | Required | Description | |---|---|---|---| | `schema_version` | string | yes | — | | `event` | object | yes | — | | `event.event_id` | string (uuid) | yes | — | | `event.event_type` | string | yes | Values: payin.posted | | `event.occurred_at` | string (date-time) | yes | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `event.correlation_id` | string | yes | — | | `context` | object | yes | — | | `context.country_code` | string | yes | — | | `context.payment_method_code` | string | yes | Public payment method catalog. Same values as REST (`PIN`, `SINPE_MOVIL`). Internal codes are never exposed. Values: PIN, SINPE_MOVIL | | `payment` | object | yes | — | | `payment.payment_id` | string (uuid) | yes | — | | `payment.public_id` | integer (int64) | yes | The same identifier is a number in webhooks and text in REST. Always treat it as text. | | `payment.status` | string | yes | Public 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 | | `payment.amount` | object | yes | — | | `payment.amount.amount` | string | yes | Amount as a decimal string with exactly 2 fraction digits (e.g. "1000.00"). | | `payment.amount.currency` | string | yes | ISO 4217 currency of the payment. Values: CRC, USD | | `payment.posted_at` | string (date-time) | yes | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `payment.account` | object | yes | — | | `payment.account.type` | string | yes | — | | `payment.account.value` | string | yes | — | | `payment.reference` | object | yes | — | | `payment.reference.client_reference` | string | yes | Partner reference sent at payment creation (`client_reference`), not `detail_reference`. | | `payment.reference.external_reference` | string | yes | — | | `payment.destination_phone_number` | string | no | — | | `payment.local_payment` | boolean | no | JSON boolean (`true`/`false`), not the strings `"true"`/`"false"`. | | `payment.origin_client_name` | string | no | — | | `provider` | object | yes | Shared 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_id` | string | yes | Provider 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_at` | string (date-time) | yes | RFC 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_code` | string | yes | Raw 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_desc` | string | yes | Provider Motivo text (prefers Detalle). Same meaning as webhook `rejection.message`. `null` when there is no provider rejection. Can be null. | | `provider.provider_status_semantic` | string | yes | Normalized platform semantic (e.g. `ACCOUNT_BLOCKED`). Same meaning as webhook `rejection.code`. `null` when there is no provider rejection. Can be null. | ### payment.succeeded [#contrato-payment-succeeded] Data we send for `payment.succeeded`. | Field | Type | Required | Description | |---|---|---|---| | `schema_version` | string | yes | — | | `event` | object | yes | — | | `event.event_id` | string (uuid) | yes | — | | `event.event_type` | string | yes | Values: payment.succeeded | | `event.occurred_at` | string (date-time) | yes | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `event.correlation_id` | string | yes | — | | `context` | object | yes | — | | `context.country_code` | string | yes | — | | `context.payment_method_code` | string | yes | Public payment method catalog. Same values as REST (`PIN`, `SINPE_MOVIL`). Internal codes are never exposed. Values: PIN, SINPE_MOVIL | | `payment` | object | yes | — | | `payment.payment_id` | string (uuid) | yes | — | | `payment.public_id` | integer (int64) | yes | The same identifier is a number in webhooks and text in REST. Always treat it as text. | | `payment.status` | string | yes | Public 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 | | `payment.amount` | object | yes | — | | `payment.amount.amount` | string | yes | Amount as a decimal string with exactly 2 fraction digits (e.g. "1000.00"). | | `payment.amount.currency` | string | yes | ISO 4217 currency of the payment. Values: CRC, USD | | `payment.client_reference` | string | yes | Partner reference sent at payment creation (`client_reference`), not `detail_reference`. | | `payment.external_reference` | string | yes | — | | `payment.fee_total` | string | yes | Fee total as decimal string (same currency as amount.currency) | | `payment.tax_total` | string | yes | Tax total as decimal string (same currency as amount.currency) | | `payment.net_amount` | string | yes | Net cash impact on the merchant account. PAYIN: amount - fee_total - tax_total (what the merchant receives). PAYOUT: amount + fee_total + tax_total (total debit; fee is charged separately and is not deducted from the transferred amount). | | `payment.succeeded_at` | string (date-time) | yes | Timestamp when the provider confirmed the payment succeeded. | | `provider` | object | yes | Shared 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_id` | string | yes | Provider 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_at` | string (date-time) | yes | RFC 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_code` | string | yes | Raw 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_desc` | string | yes | Provider Motivo text (prefers Detalle). Same meaning as webhook `rejection.message`. `null` when there is no provider rejection. Can be null. | | `provider.provider_status_semantic` | string | yes | Normalized platform semantic (e.g. `ACCOUNT_BLOCKED`). Same meaning as webhook `rejection.code`. `null` when there is no provider rejection. Can be null. | ### payment.failed [#contrato-payment-failed] Data we send for `payment.failed`. | Field | Type | Required | Description | |---|---|---|---| | `schema_version` | string | yes | — | | `event` | object | yes | — | | `event.event_id` | string (uuid) | yes | — | | `event.event_type` | string | yes | Values: payment.failed | | `event.occurred_at` | string (date-time) | yes | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `event.correlation_id` | string | yes | — | | `context` | object | yes | — | | `context.country_code` | string | yes | — | | `context.payment_method_code` | string | yes | Public payment method catalog. Same values as REST (`PIN`, `SINPE_MOVIL`). Internal codes are never exposed. Values: PIN, SINPE_MOVIL | | `payment` | object | yes | — | | `payment.payment_id` | string (uuid) | yes | — | | `payment.public_id` | integer (int64) | yes | The same identifier is a number in webhooks and text in REST. Always treat it as text. | | `payment.status` | string | yes | Public 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 | | `payment.amount` | object | yes | — | | `payment.amount.amount` | string | yes | Amount as a decimal string with exactly 2 fraction digits (e.g. "1000.00"). | | `payment.amount.currency` | string | yes | ISO 4217 currency of the payment. Values: CRC, USD | | `payment.client_reference` | string | no | Partner reference sent at payment creation (`client_reference`), not `detail_reference`. | | `payment.external_reference` | string | no | — | | `payment.error` | object | yes | — | | `payment.error.domain` | string | yes | Values: provider_rejection, platform_validation, platform_posting, platform_infra, platform_reversal, sinpe_rejection | | `payment.error.platform` | object | yes | — | | `provider` | object | yes | Shared 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_id` | string | yes | Provider 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_at` | string (date-time) | yes | RFC 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_code` | string | yes | Raw 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_desc` | string | yes | Provider Motivo text (prefers Detalle). Same meaning as webhook `rejection.message`. `null` when there is no provider rejection. Can be null. | | `provider.provider_status_semantic` | string | yes | Normalized platform semantic (e.g. `ACCOUNT_BLOCKED`). Same meaning as webhook `rejection.code`. `null` when there is no provider rejection. Can be null. | | `rejection` | object | no | Rail-agnostic provider rejection. Shared by `POST /accounts/validate` (`data.rejection`) and the `payment.failed` webhook (`rejection`). Object only when the provider rejected the operation; omitted/`null` otherwise. | | `rejection.code` | string | yes | Normalized platform semantic derived from `reason_code`. Only a subset of `reason_code` values (see the catalog in the description of `POST /accounts/validate` and of the `payment.failed` webhook contract endpoint) has a distinct entry here — for any other numeric `reason_code`, `code` is `SINPE_REJECTED` even when `message` is specific (e.g. `reason_code = 21` "fondos insuficientes" still returns `code = SINPE_REJECTED`, it has no dedicated semantic). `PHONE_NOT_REGISTERED` is the fallback when `reason_code` is non-numeric (SINPE_MOVIL wallet flow). Values: ACCOUNT_CLOSED, ACCOUNT_NOT_FOUND, ACCOUNT_BLOCKED, INVALID_ACCOUNT, IDENT_MISMATCH, IDENT_TYPE_INVALID, INVALID_ID_FORMAT, VALIDATION_ERROR, CURRENCY_MISMATCH, NOT_AUTHORIZED_BY_RECIPIENT, PROFILE_NOT_ALLOWED, CHANNEL_NOT_RECOGNIZED, COMMUNICATION_ERROR, RETRY_LATER, WALLET_PHONE_ORIGIN_INVALID, WALLET_PHONE_ORIGIN_NOT_ENABLED, WALLET_PHONE_DEST_INVALID, WALLET_PHONE_NOT_REGISTERED_BCCR, WALLET_NOT_FOUND, WALLET_PHONE_ALREADY_ACTIVE, PHONE_NOT_REGISTERED, SINPE_REJECTED | | `rejection.reason_code` | string | yes | Raw Motivo/code returned by SINPE. See the endpoint description of `POST /accounts/validate` for the full code catalog (shared by this object wherever it appears, including the `payment.failed` webhook). A code not listed there still produces a response — `message` falls back to a generic text and `code` falls back to `SINPE_REJECTED`. | | `rejection.message` | string | yes | Descriptive Motivo text; prefers provider `Detalle` when present. | ### payment.reversed [#contrato-payment-reversed] Data we send for `payment.reversed`. | Field | Type | Required | Description | |---|---|---|---| | `schema_version` | string | yes | — | | `event` | object | yes | — | | `event.event_id` | string (uuid) | yes | — | | `event.event_type` | string | yes | Values: payment.reversed | | `event.occurred_at` | string (date-time) | yes | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `event.correlation_id` | string | yes | — | | `context` | object | yes | — | | `context.country_code` | string | yes | — | | `context.payment_method_code` | string | yes | Public payment method catalog. Same values as REST (`PIN`, `SINPE_MOVIL`). Internal codes are never exposed. Values: PIN, SINPE_MOVIL | | `payment` | object | yes | — | | `payment.payment_id` | string (uuid) | yes | — | | `payment.public_id` | integer (int64) | yes | The same identifier is a number in webhooks and text in REST. Always treat it as text. | | `payment.status` | string | yes | Public 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 | | `payment.result` | string | yes | Values: reversed | | `payment.has_reversal` | boolean | yes | Values: true | | `payment.amount` | object | yes | — | | `payment.amount.amount` | string | yes | Amount as a decimal string with exactly 2 fraction digits (e.g. "1000.00"). | | `payment.amount.currency` | string | yes | ISO 4217 currency of the payment. Values: CRC, USD | | `payment.client_reference` | string | no | — | | `payment.external_reference` | string | no | — | | `payment.reversed_at` | string (date-time) | yes | RFC 3339 timestamp in UTC with second precision and a `Z` suffix. Fractional seconds are never emitted. Example: `2026-01-15T12:00:00Z`. | | `payment.reason` | object | no | — | | `payment.reason.code` | string | no | — | | `payment.reason.message` | string | no | — | | `provider` | object | yes | Shared 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_id` | string | yes | Provider 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_at` | string (date-time) | yes | RFC 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_code` | string | yes | Raw 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_desc` | string | yes | Provider Motivo text (prefers Detalle). Same meaning as webhook `rejection.message`. `null` when there is no provider rejection. Can be null. | | `provider.provider_status_semantic` | string | yes | Normalized platform semantic (e.g. `ACCOUNT_BLOCKED`). Same meaning as webhook `rejection.code`. `null` when there is no provider rejection. Can be null. | ## Public identifier [#public-id] `payment.public_id` is a number in the webhook and text in REST; it is the same identifier. **Always treat it as text.** --- # Banking API catalogs > Customer identification type catalog per country in the Banking API. - kind: api-operation - status: stable - api_version: 1.0.0 - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/api-bancario/catalogos The `Catalogs` group publishes the values your integration must use in customer identification fields, instead of hardcoding them. ```http GET /api/public/v1/catalogs/customer-identification-types ``` Filter by two-letter `country_code` to get the types in force in that country. Fetch the catalog when you start the integration and cache the result; read it again before adding a new country. ## Operations [#operaciones] ### List customer identification person types [#get-catalogs-customer-identification-types] ```http GET /api/public/v1/catalogs/customer-identification-types ``` Host: `https://api-baas-sandbox.tilopay.com` — requires `Authorization: Bearer `. Returns the distinct customer person types (`individual`, `business`, `both`) available for the authenticated tenant, optionally filtered by country. This is a person-type classification, not the payment identification-document catalog (`cedula`, `juridica`, `dimex`, ...) used by `identification.type` on `POST /payments`. **Parameters** | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `X-Correlation-Id` | header | string | no | 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. | | `country_code` | query | string | no | Optional ISO-2 country code used to filter results. A legacy `country` query parameter is also accepted as an alias when `country_code` is omitted; prefer `country_code`. | **200** — The request was processed successfully. Fields in `data`: | Field | Type | Required | Description | |---|---|---|---| | `items` | array | yes | — | | `items[].code` | string | yes | Values: individual, business, both | **Response example** ```json { "success": true, "http_status_code": 200, "response_code": "OK", "message": "The request was processed successfully.", "correlation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "data": { "items": [ { "code": "individual" }, { "code": "business" }, { "code": "both" } ] } } ``` **Error responses** | HTTP | response_code | Description | |---|---|---| | 401 | `UNAUTHORIZED` | Unauthorized. Verify your session or credentials. | | 403 | `FORBIDDEN` | You do not have permission to perform this action. | | 500 | `INTERNAL_ERROR` | An unexpected error occurred. Please try again later. | | 502 | — | Dependency error | | 503 | `SERVICE_UNAVAILABLE` | A required service is temporarily unavailable. Please try again later. | --- # Banking API response codes > Full response_code catalog for Tilopay's Banking API and how to react to each family. - kind: concept - status: stable - api_version: 1.0.0 - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/api-bancario/errores Every Banking API error arrives in the [standard envelope](/en/developers/api-bancario/convenciones#envelope), with `success: false`, the HTTP status in `http_status_code` and a `response_code` from the catalog. Branch on `response_code`. ## How to react per family [#familias] **Request `4xx`.** The body or parameters are invalid, or the resource does not exist. Fix the call before resending it: retrying as-is returns the same error. This covers `INVALID_REQUEST`, `NOT_FOUND` and business codes such as `INSUFFICIENT_FUNDS` or `LIMIT_EXCEEDED`. **`401` and `403`.** The token is absent, expired, or its context does not authorize the resource. Renew the access token by repeating [login and exchange](/en/developers/api-bancario/autenticacion#login); if it persists with a fresh token, that context has no permission over the resource. **`409 IDEMPOTENCY_CONFLICT`.** You reused an `Idempotency-Key` with a different body. Use a new key for the new intent; see [safe retries](/en/developers/api-bancario/convenciones#reintentos). **`429 TOO_MANY_REQUESTS`.** Currently applies to authentication and account validation. Space out requests and retry with exponential backoff. **`5xx`.** Service-side failure. Retry with backoff, and on `POST` calls resend the **same** `Idempotency-Key` so the record is not duplicated. Keep the `correlation_id` before escalating to support. ## Full catalog [#catalogo] These are the `ResponseCode` values our API returns, with their HTTP statuses. `OK`, `CREATED` and `ACCEPTED` identify successful responses; asynchronous statement requests use `202 ACCEPTED`. | response_code | HTTP status returned | |---|---| | `OK` | — | | `CREATED` | — | | `ACCEPTED` | 202 | | `UNAUTHORIZED` | 401 | | `FORBIDDEN` | 403 | | `TOO_MANY_REQUESTS` | 429 | | `SERVICE_UNAVAILABLE` | 503 | | `INTERNAL_ERROR` | 500 | | `INVALID_REQUEST` | 400 | | `ACCOUNT_ACCESS_DENIED` | 403 | | `PROVIDER_ERROR` | 502 | | `INVALID_CREDENTIALS` | 401 | | `NOT_FOUND` | 404 | | `ACCOUNT_NOT_FOUND` | 404 | | `ACCOUNT_NOT_CONFIGURED` | 422 | | `UNPROCESSABLE_ENTITY` | 422 | | `PAYMENT_NOT_FOUND` | 404 | | `INVALID_ACCOUNT_TYPE` | 400 | | `INVALID_ACCOUNT_FORMAT` | 400 | | `INVALID_PAYMENT_METHOD` | 400 | | `PAYMENT_INVALID_PAYLOAD` | 400 | | `ACCOUNT_INVALID` | 400 | | `ACCOUNT_HOLDER_MISMATCH` | 400 | | `IDEMPOTENCY_CONFLICT` | 409 | | `PAYMENT_DUPLICATE` | 409 | | `CONFLICT` | 409 | | `INSUFFICIENT_FUNDS` | 422 | | `LIMIT_EXCEEDED` | 422 | A `response_code` outside this list is not part of the API's 1.0.0 contract: treat it as a generic error based on its HTTP status and report it with the `correlation_id`. --- # AI agents > Index of the AI agents section: MCP server, connecting, permissions, recipes, skills, WhatsApp assistant, debug agent and machine-readable documentation. - kind: guide - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes ## Read [#leer] The whole portal is available in machine-readable form: the [`llms.txt`](https://tilopay.com/en/llms.txt) index, the raw Markdown of any URL on the site by adding `.md`, the API [`openapi.json`](/developers/openapi.json) and the MCP server [`mcp.json`](/developers/mcp.json). ## Automate your business operations [#actuar] The [MCP server](/en/developers/agentes/mcp) lets an assistant operate your Tilopay account: query sales, create payment links, issue refunds. Access is self-service — you sign in with your Tilopay credentials — and your credentials never leave the Tilopay server. ## Integrate into a product [#integrar] When you are building a product that charges, the path is the [JavaScript SDK](/en/developers/sdk) and the [Acquiring API](/en/developers/api/autenticacion), not the MCP. ## In this section [#indice] - [MCP server](/en/developers/agentes/mcp) — what it is, how access works and how to connect. - [Connect your agent](/en/developers/agentes/mcp/conectar) — Claude, ChatGPT, Copilot, Cursor, Gemini and other clients, step by step. - [What an agent can do](/en/developers/agentes/mcp/permisos) — scope, sensitive tools and the confirmations Tilopay requires. - [Recipes](/en/developers/agentes/mcp/recetas) — end-to-end flows ready to copy. - [Skills](/en/developers/agentes/mcp/skills) — those recipes packaged and installable. - [WhatsApp assistant](/en/developers/agentes/asistente-whatsapp) — the Tilopay assistant inside WhatsApp, with nothing to install. - [Machine-readable documentation](/en/developers/agentes/documentacion-legible) — llms.txt, raw Markdown, OpenAPI and mcp.json. The **debug agent** is this portal's side panel: open it with the button in the top bar and it answers with the verified documentation, cites the exact page and points to the right integration path. ## Which one to choose [#cual-elegir] - Operate the account from an assistant → [MCP server](/en/developers/agentes/mcp). - Build a product that charges → [API](/en/developers/api/autenticacion) and [SDK](/en/developers/sdk). - Feed an agent with the documentation → [machine-readable documentation](/en/developers/agentes/documentacion-legible). MCP access is self-service: install the connector and sign in with your Tilopay credentials, as the [MCP server page](/en/developers/agentes/mcp) explains. --- # Tilopay Assistant on WhatsApp > What the Tilopay WhatsApp assistant is, how it is enabled, how to use it and what it can do: sales, transactions, payment links, catalog, micro-store and orders, receipts, subscriptions, saved cards, reminders and alerts. - kind: guide - status: stable - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/asistente-whatsapp ## What it is [#que-es] The Tilopay assistant on WhatsApp is the same Tilopay MCP server wired to a WhatsApp chat. Instead of configuring an MCP client, you write to it in plain language from the WhatsApp they already use, and the assistant runs the tools against their Tilopay account. The capabilities are the ones the MCP exposes: whatever an agent can do through the [acquiring tools](/en/developers/agentes/mcp/adquirencia) and, on request, the [banking tools](/en/developers/agentes/mcp/api-bancario), the assistant can do from the chat. It is not a canned-response chatbot: it reads and operates on real account data, and answers in plain text meant to be read on a phone. ## How it is enabled [#activacion] Linking takes place from the WhatsApp chat itself and takes about a minute. You must be affiliated with Tilopay, and the person needs their Tilopay email and password. 1. Send any message to Tilopay's official number, **+506 7098 2131**, or open the chat directly: [Open WhatsApp chat](https://wa.me/50670982131). If the number is not linked yet, the chat shows a welcome message with a **Vincular mi cuenta** button. You can also write “vincular mi cuenta” to request the button again. 2. Tap **Vincular mi cuenta**. Enter credentials and codes in the secure form that opens, never as messages in the chat. 3. Enter the same email and password you use to sign in to the Tilopay dashboard. 4. If your account has two-step verification, enter the code Tilopay sends to the method shown on the screen. 5. Enter the 6-digit code sent to your Tilopay email. This final code is always required to confirm the link. 6. If your user has more than one merchant, choose the one you want to use. When finished, close the form; you can now ask the assistant to query or operate on that account. Linking is authenticated with the email, the password and the code sent to the email, not with the phone number: you can text from any phone. +506 7098 2131 is also the number Tilopay sends verification codes from; save it as a contact. An unlinked number cannot run queries or operations. The linking welcome is sent at most once per day; even if it does not appear again that day, you can request the button by writing “vincular mi cuenta.” ## Link the account again [#revincular] If the session stops working or the password changes, write “vincular mi cuenta” and complete the same process. Linking the same number again updates the existing connection: it does not create another profile or disconnect MCP clients that are already linked. Each channel keeps its own session, so a problem in WhatsApp does not automatically close sessions in Claude, ChatGPT, Cursor or other MCP clients. ## How to use it [#uso] There are no commands or menus. You write as you would to a teammate: - "how are sales going over the last 30 days?" - "charge Ana 25 thousand colones for the appointment and send it to me on WhatsApp" - "look up order 10432" - "every day at 8pm send me today's sales summary" The assistant remembers the last messages of the thread, so you can keep going without repeating data ("send it by email too", "delete that link"). ## What it can do [#capacidades] | Area | What it solves from the chat | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sales | Summaries and trends by day, week or month, plus analysis with average ticket, approval rate, top customers and decline reasons. | | Transactions | List and look up transactions, and capture, refund or reverse a charge. | | Payment links | Create a link, share it on WhatsApp or by email, see its detail and delete it. | | Catalog | List products or services, see their detail, create, change or delete an item. | | Micro-store | Build a store with a cart from the catalog products, list the active stores and deactivate one. | | Orders | Turn on order management with the store name and logo, see the summary, list orders by status, see the paid but undelivered ones and mark them as delivered. | | Receipts | List the receipts of approved charges and resend a customer the receipt for their payment. | | Contacts | Save, list and delete contacts so you can charge them by name. | | Recurring charges | Create, edit and delete plans, build them step by step from the chat, generate the subscription link, review subscribers and their payments, edit, pause, reactivate or delete a subscriber, see failed charges, generate the link to settle a declined charge and manage coupons. | | Saved cards | Tokenize a customer's card, list or delete their cards, check payment methods and installments, charge a saved card and review groups, affiliates and bulk charges. | | Reminders | Schedule one-off, daily, weekly or monthly reminders that can also run an assistant query when they fire; list them and cancel them. Up to 20 active. | | Alerts | Turn your alerts on and off and review them: approved charges, declined charges, orders and the daily summary at the chosen time. | | Profile and recipes | Save the business profile and get automation recommendations based on what it sells. | | Support | Find the official guide for a topic, run the connection diagnostics and review an integration error. | | Banking (on request) | Accounts, balances, payments, statements and movement analysis; and, with email confirmation, validate a destination account, send a transfer and check its status. Only with banking API credentials. | There are 117 tools across 15 groups today. Parameter detail and the access level of each one live in the [MCP server](/en/developers/agentes/mcp) pages. The assistant also accepts **images** in the chat: a product photo for the catalog, or the logo shown in the micro-store and on receipts. ## Authorizing sensitive operations [#autorizacion] Operations that move money or alter charges — refunds, captures, reversals, saved-card charges, subscriber changes, deleting links, products or plans, and sending a bank transfer — ask for a 6-digit code that Tilopay sends to the user's registered email. Without that code the assistant does not run them. The merchant's keys never travel through the chat, and the assistant never reveals credentials, tokens or internal identifiers. Every query and operation is audited with user, tool, date and result, with per-account usage limits. ## Messages the assistant sends on its own [#avisos] - **Payment received:** when a customer pays a link created from the assistant, a WhatsApp message arrives with amount, customer, description, reference and order number. - **Scheduled reports:** the summary or query you scheduled, with the data pulled at send time. - **Reminders:** the reminder you scheduled, with the query result attached if you asked for an action. Reminders about charges or links always go to your WhatsApp, never to the customer. - **Pending orders:** the daily reminder of paid orders still to be delivered, when order management is on. ## If you prefer your own agent [#mcp] The same tools are available over MCP for Claude, ChatGPT, Cursor or any other client: start with [Connect your agent](/en/developers/agentes/mcp/conectar) and [Permissions](/en/developers/agentes/mcp/permisos). --- # Tilopay MCP server > How to connect an MCP client to the Tilopay server with OAuth and the same Tilopay account credentials. - kind: mcp-index - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp ## How access works [#acceso] Access is self-service: there is no registration form and no manual approval. You installs the connector with the server URL and, in the authorization window, signs in with the **same credentials as their Tilopay account** (email and password) and confirms the verification code Tilopay sends. That automatically creates the assistant user, ties it to you, and registers your API credentials encrypted on the server side. Three things worth knowing: 1. The session is authorized with the Tilopay dashboard email and password, plus the verification code. The assistant never sees or stores that password: the connection runs on OAuth 2.0. 2. Because the user is tied to one merchant, the agent only reaches the data and the operations of that merchant. 3. You never paste your `apiKey`, `apiUser` or `apiPassword` into the MCP client. > **Warning:** Your API credentials live encrypted on the Tilopay server and are decrypted on every call. > They are not pasted into the MCP client, nor into the assistant configuration file, and they are > never handed to the model. That is the security argument behind the whole design. ## Connection [#conexion] The server is `https://mcp.tilopay.com/mcp`. To connect: 1. Add the server in the MCP client with that URL. 2. The client discovers authentication from the 401 with `WWW-Authenticate`, which points to `/.well-known/oauth-protected-resource`. 3. The client registers dynamically and opens the authorization screen. 4. The user signs in with their Tilopay account credentials, confirms the verification code and sees a consent screen with the application requesting access, the return URL and the permissions. 5. On approval, the client is connected. The model is OAuth 2.0 with `authorization_code` and `refresh_token`. The client discovers the identity provider endpoints on its own: the only thing to configure is the server URL. ## From the most used MCP clients [#clientes] - **Claude** (desktop and web): add a remote connector with the server URL and complete sign-in in the window it opens. Connect with Claude - **ChatGPT**: add the server as a remote connector with that same URL; authorization completes in the browser. - **Cursor, VS Code and other editors**: declare a remote HTTP MCP server with the URL, with no token in the configuration file; the editor opens the browser to authorize. - **Your own client**: use an MCP client with Streamable HTTP transport and OAuth 2.0 support with dynamic registration. The sequence is the one above: 401, discovery, registration, authorization, connection. Want to start with ready-made cases? [Install a skill](/en/developers/agentes/mcp/skills). The transport is Streamable HTTP. The available tools are organized in 15 groups: the 14 of the [acquiring API](/en/developers/agentes/mcp/adquirencia) (full list) and the [banking API (BaaS)](/en/developers/agentes/mcp/api-bancario) group, which is enabled on request and only works for users with Tilopay banking API credentials. Before connecting an agent, read [what it can do](/en/developers/agentes/mcp/permisos). ## Dates in UTC [#fechas-utc] For sales queries, send `startDate` and `endDate` in UTC using `YYYY-MM-DD HH:mm:ss`, not local time. Costa Rica is UTC-6: the local day 2026-10-20 runs from `2026-10-20 06:00:00` to `2026-10-21 05:59:59` UTC. When comparing periods, convert the local range to UTC first. Next step → [Connect your agent](/en/developers/agentes/mcp/conectar). --- # Connect your AI agent > How to connect Claude, ChatGPT, GitHub Copilot, Cursor, Gemini or any other MCP client to the Tilopay MCP server. - kind: guide - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/conectar Every agent connects to the same server through the same flow: you add a remote MCP server with the Tilopay URL and authorize the session in the browser. The only thing that changes per agent is where you add it. ```text https://mcp.tilopay.com/mcp ``` The MCP server version in production is **0.3.0**. ## Before connecting [#antes] 1. There is no form and no prior approval: an active Tilopay account is enough. 2. During authorization you sign in with your Tilopay dashboard email and password and confirm the verification code Tilopay sends. The assistant user is created automatically at that moment and tied to your account. 3. Review [what an agent can do](/en/developers/agentes/mcp/permisos) before connecting it, especially the tools marked as sensitive. > **Warning:** Never paste your `apiKey`, `apiUser` or `apiPassword` into the agent or its config file. > Merchant credentials live encrypted on the Tilopay server: the agent only handles its own OAuth > session. ## Claude [#claude] Claude supports remote connectors, both in the desktop app and on the web. 1. Add a remote custom connector with the server URL. 2. Claude opens the Tilopay authorization screen: sign in with your Tilopay credentials and confirm the verification code. 3. Review the consent screen (requesting application, return URL and permissions) and approve. 4. Back in the conversation, the Tilopay tools are available. There is no token to paste: the connector registers itself. ## ChatGPT [#chatgpt] ChatGPT connects as a remote connector using the same URL. 1. Add the server as a remote connector with the URL above. 2. Authorization completes in the browser with your Tilopay credentials and the verification code. 3. Once approved, the connector is active and the model can call the tools. ## GitHub Copilot and VS Code [#copilot] In VS Code (with Copilot in agent mode) you declare a remote HTTP MCP server. 1. Declare the server with the URL, no token or credentials in the config file. 2. On first use, the editor opens the browser to authorize. 3. After approving, the tools show up in the agent's tool list. ## Cursor and other editors [#cursor] Cursor and equivalent editors use the same scheme: a remote MCP server over HTTP. 1. Add the remote server with the URL, no token in the configuration. 2. Authorize in the browser when the editor asks. 3. Enable the tools for the editor's chat or agent. ## Gemini [#gemini] Gemini clients that support remote MCP connect the same way: register the server URL and authorize in the browser. If the client only supports local stdio MCP servers, you need a bridge that speaks Streamable HTTP against the Tilopay URL. ## Other MCP clients [#otros] For a custom client or any other agent, the requirement is support for **Streamable HTTP** transport and **OAuth 2.0 with dynamic client registration**. The sequence is: 1. The client calls the server and gets a `401` with `WWW-Authenticate`, pointing to `/.well-known/oauth-protected-resource`. 2. The client discovers the identity provider endpoints. 3. It registers dynamically and opens the authorization screen. 4. The user signs in and approves the consent screen. 5. The client stays connected with `authorization_code` and `refresh_token`. The only thing you configure is the server URL. ## Verify the connection [#verificar] Ask the agent to list the available tools: the Tilopay ones should appear, organized in the 14 groups of the [acquiring API](/en/developers/agentes/mcp/adquirencia) (full list). The [banking API (BaaS)](/en/developers/agentes/mcp/api-bancario) group is enabled on request and only works for users with Tilopay banking API credentials. A safe first test is a read-only query, for example asking for transactions in a date range. For full flows see the [recipes](/en/developers/agentes/mcp/recetas). ## Dates in UTC [#fechas-utc] For sales queries, send `startDate` and `endDate` in UTC using `YYYY-MM-DD HH:mm:ss`. Convert the local range before querying: Costa Rica is UTC-6, so the local day 2026-10-20 runs from `2026-10-20 06:00:00` to `2026-10-21 05:59:59` UTC. Use the same conversion when comparing periods. ## If something fails [#problemas] - **The client never opens authorization**: check the server is declared as remote over HTTP, not as a local server. - **Authorization never completes**: sign in with your Tilopay dashboard email and password, and confirm the verification code sent to your email. - **The agent sees no tools**: several clients require enabling them for the conversation or for agent mode after connecting. - **A tool returns empty**: it may be the queried environment; several tools default to `production`. --- # What an agent can do > The real scope of an agent connected to the MCP server: which tools read, which tools change state, and which controls exist. - kind: guide - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/permisos ## Scope of the tools [#alcance] Of the 117 tools, 63 only read and 54 change state. 20 of those 54 are marked as sensitive. The ones marked as sensitive, grouped by theme. The list comes from the catalog: it is every tool with the sensitive access level. Sending messages (links, receipts or notices over WhatsApp or email) counts as a write, not as a sensitive operation. **Move money: refunds and captures, stored-card charges, bank transfers, their confirmation and dual control** - [`tilopay_modify_transaction`](https://tilopay.com/en/developers/agentes/mcp/ventas#tilopay-modify-transaction): Capture, refund or reverse - [`tilopay_charge_saved_card`](https://tilopay.com/en/developers/agentes/mcp/tarjetas-guardadas#tilopay-charge-saved-card): Charge a customer's saved card - [`tilopay_saved_cards_create_payments`](https://tilopay.com/en/developers/agentes/mcp/tarjetas-guardadas#tilopay-saved-cards-create-payments): Charge stored cards - [`baas_send_transfer`](https://tilopay.com/en/developers/agentes/mcp/api-bancario/send-transfer#baas-send-transfer): Send BaaS transfer - [`baas_confirm_transfer`](https://tilopay.com/en/developers/agentes/mcp/api-bancario/confirm-transfer#baas-confirm-transfer): Authorize the pending transfer - [`banca_decide_approval`](https://tilopay.com/en/developers/agentes/mcp/api-bancario/otras-operaciones#banca-decide-approval): Approve or reject a pending operation **Delete or deactivate: links, products, plans, coupons, contacts, stored cards, reminders, stores, domains and orders** - [`tilopay_delete_payment_link`](https://tilopay.com/en/developers/agentes/mcp/enlaces-de-pago#tilopay-delete-payment-link): Delete payment link - [`tilopay_catalog_delete_item`](https://tilopay.com/en/developers/agentes/mcp/catalogo#tilopay-catalog-delete-item): Delete a catalog product - [`tilopay_delete_contact`](https://tilopay.com/en/developers/agentes/mcp/contactos#tilopay-delete-contact): Delete contact - [`tilopay_store_delete`](https://tilopay.com/en/developers/agentes/mcp/microtienda#tilopay-store-delete): Disable a micro-store - [`tilopay_store_domain_remove`](https://tilopay.com/en/developers/agentes/mcp/microtienda#tilopay-store-domain-remove): Remove a custom domain - [`tilopay_orders_disable`](https://tilopay.com/en/developers/agentes/mcp/microtienda#tilopay-orders-disable): Turn off order management - [`tilopay_cancel_reminder`](https://tilopay.com/en/developers/agentes/mcp/recordatorios#tilopay-cancel-reminder): Cancel reminder - [`tilopay_recurring_delete_plan`](https://tilopay.com/en/developers/agentes/mcp/recurrentes#tilopay-recurring-delete-plan): Delete subscription plan - [`tilopay_recurring_delete_coupon`](https://tilopay.com/en/developers/agentes/mcp/recurrentes#tilopay-recurring-delete-coupon): Delete a coupon - [`tilopay_remove_saved_card`](https://tilopay.com/en/developers/agentes/mcp/tarjetas-guardadas#tilopay-remove-saved-card): Delete a customer's saved card **Change a subscriber's status and their future charges** - [`tilopay_recurring_manage_subscriber`](https://tilopay.com/en/developers/agentes/mcp/recurrentes#tilopay-recurring-manage-subscriber): Pause, reactivate or delete a subscriber - [`tilopay_recurring_edit_subscriber`](https://tilopay.com/en/developers/agentes/mcp/recurrentes#tilopay-recurring-edit-subscriber): Edit a plan's subscriber **Cut access: unlinking the account and removing an employee** - [`tilopay_unlink_account`](https://tilopay.com/en/developers/agentes/mcp/cuenta#tilopay-unlink-account): Unlink my account - [`tilopay_remove_employee`](https://tilopay.com/en/developers/agentes/mcp/cuenta#tilopay-remove-employee): Remove an employee's access ## Permission inheritance [#herencia-de-permisos] The server uses your API credentials, so it inherits exactly its permissions: it does not impose additional barriers per tool. An active user can issue refunds and run bulk charges; those operations require the confirmation code sent to the email (see [Confirmations Tilopay requires](#confirmaciones)). ## Controls that do exist [#controles] - A per-user activation switch, which cuts access without deleting the account. - A separate administrator role, which only serves to manage users and credentials. - A rate limit of 60 calls per minute per user. ## Confirmations Tilopay requires [#confirmaciones] Tilopay enforces server-side confirmations for the operations detailed below. For other write tools, human confirmation is the responsibility of the MCP client. **6-digit code by email (second factor).** Required by `tilopay_modify_transaction`, `tilopay_charge_saved_card`, `tilopay_saved_cards_create_payments`, `tilopay_remove_saved_card`, `tilopay_delete_payment_link` and `tilopay_recurring_manage_subscriber`. The cycle is: 1. Call the tool without `confirmation_code`. 2. The response carries `structuredContent` with `{requires_confirmation: true, channel: "email", expires_in_seconds: 600, summary, details[], risk_level, second_channel}` and Tilopay sends the code to the merchant's email. 3. Repeat the SAME call with the same parameters plus `confirmation_code`. The code expires after 10 minutes, allows 5 attempts, 3 codes can be requested every 10 minutes and each new code invalidates the previous one. With elevated risk the same code also arrives by SMS if a number is configured. **Deposit extensions.** `tilopay_deposit_extend` requires a 6-digit email code and is owner-only. Its schema does not declare `confirmation_code`, so the two-call flow is completed through the WhatsApp Assistant. **Mandatory `confirm: true`.** `tilopay_unlink_account`, `tilopay_remove_employee` and `baas_get_account_number` only run with `confirm: true`. **Batch fingerprint.** `tilopay_recurring_settle_batch` returns a summary with a `fingerprint` and only executes with `confirm: true` and the same fingerprint (maximum 20 links). **Bank transfers.** The Banca Cliente portal sends the code to the email and it is passed as `code` to `baas_confirm_transfer` (or to `banca_decide_approval` under dual control). ### Human confirmation in the MCP client [#confirmacion-humana] For the remaining write tools, the MCP client must ask for human confirmation before running them. > **Warning:** `tilopay_charge_saved_card` and `tilopay_saved_cards_create_payments` do not deduplicate: repeating > the operation with a new code charges again. Before retrying, verify the result with > `tilopay_sales_summary` or `tilopay_saved_cards_list_collections`. `tilopay_catalog_delete_item` does not require an email code: the WhatsApp Assistant asks for confirmation in chat; MCP clients must ask for human confirmation. `tilopay_recurring_edit_subscriber` does not require an email code either; use `tilopay_recurring_manage_subscriber` for second-factor pause, reactivation or deletion. --- # Recipes > Fourteen end-to-end flows with the MCP server: insights dashboard, selling over WhatsApp, recovering failed recurring charges, B2B collections, cash projection with banking data, anomaly detection, month-end close, refunds, recurring subscriptions, plan optimisation, stored-card charges, pausing or cancelling subscribers, integration debugging and Tilopay as a tool for other agents. - kind: guide - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/recetas ## Build a real-time insights dashboard [#dashboard-insights] 1. `tilopay_sales_summary` brings totals, costs and net for the period you want to monitor. 2. `tilopay_analyze_sales` turns those numbers into a narrative summary with trends and observations. 3. `tilopay_list_transactions` lists recent transactions to complete the board. 4. `tilopay_get_transaction` opens the detail of any operation that needs review. > **Warning:** The summary uses UTC, not local time. If you have low volume, trends and top customers may > come back empty. ### Example dashboard questions [#ejemplos-dashboard] - “How did I do today?” → `tilopay_sales_summary` + `tilopay_analyze_sales`. - “What are my best-selling products or services this week?” → `tilopay_analyze_sales` over the last 7 days. - “Do I have failed or pending transactions?” → `tilopay_list_transactions` filtered by status, then `tilopay_get_transaction` for each case. - “How are my net earnings evolving versus last month?” → compare two `tilopay_sales_summary` calls with different date windows. ## Sell over WhatsApp from the catalogue [#cobrar-por-whatsapp] 1. `tilopay_catalog_list_items` lists your products or services so the agent answers the customer with real names and prices. 2. `tilopay_catalog_get_item` opens the chosen item when price, currency or description must be confirmed before building the order. 3. The agent builds the order, computes the total and confirms it with the customer before charging. 4. `tilopay_save_contact` stores the buyer (name, email, phone) to attach them to the charge and reuse them in future sales. 5. `tilopay_create_charge` creates the link for the order total, with a description that identifies the order. 6. `tilopay_send_payment_link_whatsapp` prepares the message to the customer. 7. `tilopay_get_payment_link` checks the link status to confirm payment and close the conversation with the receipt. `tilopay_send_payment_link_whatsapp` does not send the message: it returns a `wa.me` link with the text ready to send in one tap. If there is no phone and no saved contact with that name, the tool asks for it. The agent can keep polling `tilopay_get_payment_link` to notify you as soon as the link is paid. > **Note:** With this recipe a merchant without an online store sells end to end inside the chat: catalogue, > order, charge and confirmation, with no human intervention beyond what you choose to > require. ## Recover failed recurring charges (smart dunning) [#dunning-inteligente] 1. `tilopay_recurring_list_plans` lists your active plans. 2. `tilopay_recurring_subscriber_payments` walks the payments of each plan's subscribers and separates the period's declined charges. 3. `tilopay_get_transaction` opens each decline to read the response code and classify the cause: insufficient funds, expired card, blocked card, suspected fraud. 4. Based on the cause, the agent decides the action and proposes it to you: wait for the plan's retry when the decline is transient (insufficient funds), or ask the customer for a new payment method when it is final (expired or blocked card). 5. `tilopay_create_charge` creates a link for the outstanding amount when the customer must pay the instalment with another card. 6. `tilopay_send_payment_link_whatsapp` or `tilopay_send_payment_link_email` prepares the message to the customer with wording adapted to the decline reason. 7. `tilopay_get_payment_link` confirms when the customer paid, and `tilopay_recurring_get_subscriber` verifies the subscriber's status after recovery. An agent running this recipe every morning turns the decline list into two queues: the one that resolves itself with the plan's retry and the one that needs a message to the customer. You only receives the summary: how many charges were recovered and which remain pending. > **Warning:** Do not use `tilopay_recurring_manage_subscriber` to "punish" a decline: `pause` and `delete` > affect future charges and `delete` cannot be undone. Pausing or removing a subscriber is a > merchant decision with explicit human confirmation, never an automatic reaction to a failed > charge. ## B2B collections with accounts receivable [#cobranza-b2b] 1. `tilopay_list_contacts` brings your business customers; `tilopay_save_contact` registers the missing ones with the payer's name, email and phone. 2. `tilopay_create_charge` creates a link per outstanding invoice, with the invoice number in the description. 3. `tilopay_send_payment_link_email` prepares the first send; `tilopay_send_payment_link_whatsapp` prepares the reminder when the invoice is still unpaid. 4. `tilopay_get_payment_link` checks each link's status to update the receivables list without opening the panel. 5. `baas_search_payment` or `baas_list_payments` look in your bank accounts for a direct transfer (SINPE or bank transfer) matching the amount and customer, to mark as paid an invoice the customer settled outside the link. 6. `tilopay_delete_payment_link` removes the link of an invoice already paid by transfer, to avoid a double charge. This recipe answers the "have they paid me yet?" that eats administrative hours in businesses that invoice companies: one list with invoices, links, status and payments received through any channel. > **Note:** This recipe only uses read-only `baas_*` tools. `baas_send_transfer` exists, but it requires the > code sent to the email and is not part of this recipe. The `baas_*` tools require Tilopay to have > registered the merchant's banking API credentials; if they are not configured, the tool says so > and the recipe keeps working with payment links alone. ## Project cash with gateway and bank data (pocket CFO) [#cfo-de-bolsillo] 1. `tilopay_sales_summary` brings the last weeks of sales to estimate expected income for the next period. 2. `tilopay_recurring_list_plans` and `tilopay_recurring_get_plan` quantify committed recurring charges and their dates. 3. `baas_list_balances` or `baas_get_balance` bring the real balance of each of your bank accounts. 4. `baas_list_payments` lists the last months of transfers to detect recurring outflows: payroll, rent, suppliers. 5. `baas_analyze_payments` summarises those movements in natural language to identify patterns and peak dates. 6. The agent combines balance, expected inflows and expected outflows into a daily projection, flags the days with minimum or negative balance and proposes actions. 7. `tilopay_create_charge` and `tilopay_send_payment_link_email` put outstanding invoices out for collection when you approve that action. The value is in crossing two sources that normally live apart: the gateway knows what is coming in and the bank knows what is there and what goes out. A weekly routine produces a short briefing with the projection and the alerts, before the business opens. > **Warning:** This recipe only uses read-only `baas_*` tools. `baas_send_transfer` exists, but it requires the > code sent to the email and is not part of this recipe. If the projection suggests moving funds > between accounts, a person executes that with the bank's confirmation; the agent only recommends > and records it. The projection is an estimate, not a financial statement. ## Detect anomalies and act [#detector-anomalias] 1. `tilopay_sales_summary` compares the current window (for example the last hour or the day) against the same reference period to detect approval drops, decline spikes or sudden changes in average ticket. 2. `tilopay_list_transactions` filters by status to see whether declines concentrate on one payment method, one currency or a pattern of repeated attempts. 3. `tilopay_get_transaction` opens representative cases to read the response codes. 4. `tilopay_diagnostics` checks credentials and endpoint availability to separate a problem in the merchant's integration from a problem with customers or issuers. 5. `tilopay_help_guides` searches the official guides for the explanation and the fix that match the response code or the symptom. 6. The agent raises the alert with the diagnosis already done: what changed, since when, probable cause and suggested action. > **Warning:** The sales summary uses UTC. When comparing short windows, align the hours; a "drop" at local > midnight may just be the UTC day cut. Many declined attempts in a few minutes with small amounts > and different cards may indicate stolen-card testing: report it to support instead of retrying. ## Close the month sales [#cierre-de-mes] 1. `tilopay_sales_summary` returns totals, costs and net for the period. 2. `tilopay_analyze_sales` produces the natural-language report. 3. `tilopay_get_transaction` brings the detail of the transactions that need review. > **Warning:** The hours and days in the summary are in UTC, not local time. Trends and top customers come back > empty when the sample is small. ## Refund with human confirmation [#reembolso] 1. `tilopay_list_transactions` locates the transaction in the date range. 2. `tilopay_get_transaction` shows the detail to confirm it is the right one. 3. The agent asks the user for confirmation. That step is enforced by the MCP client, not by Tilopay. 4. `tilopay_modify_transaction` runs the modification with the action and the amount. ## Review a recurring subscription [#suscripcion-recurrente] 1. `tilopay_recurring_list_plans` lists you recurring billing plans. 2. `tilopay_recurring_get_plan` opens the plan you care about by its ID. 3. `tilopay_recurring_get_subscriber` shows the subscriber detail. 4. `tilopay_recurring_subscriber_payments` lists that subscriber payments to see which went through and which did not. Useful to answer "why does this customer show no charge this month?" without opening the dashboard. ## Optimise subscription plans [#optimizar-planes] 1. `tilopay_recurring_list_plans` lists the active plans with price and frequency. 2. `tilopay_recurring_get_plan` opens each plan for its subscriber count and configuration. 3. `tilopay_recurring_subscriber_payments` walks payments per subscriber to compute, per plan, the successful-charge rate, the month when most subscribers stop paying and the average tenure. 4. `tilopay_recurring_get_subscriber` reviews the outliers the analysis flags. 5. The agent presents the comparison between plans: retention, revenue per subscriber and churn point, and proposes changes to price, frequency or content. 6. `tilopay_create_charge` lets you test a new offer with a small segment of contacts before modifying a plan. This is pricing analysis with real charge data. The agent does not modify plans: it proposes, and you decide in the panel. ## Pause or cancel a subscriber [#pausar-suscriptor] 1. `tilopay_recurring_get_subscriber` confirms the subscriber is the right one. 2. The agent asks the user for confirmation, with the name and the plan in plain sight. 3. `tilopay_recurring_manage_subscriber` runs `pause`, `reactivate` or `delete`. > **Warning:** `tilopay_recurring_manage_subscriber` is marked sensitive: it affects future charges and `delete` > cannot be undone. Never run it without explicit human confirmation. ## Charge a group with stored cards [#cobro-masivo] 1. `tilopay_saved_cards_list_groups` lists the collection groups. 2. `tilopay_saved_cards_list_affiliates` shows the affiliates of the chosen group, so you know how many cards will be charged. 3. The agent summarises amount, currency, reason and number of affiliates, and asks for confirmation. 4. `tilopay_saved_cards_create_payments` creates the charges. 5. `tilopay_saved_cards_list_collections` and `tilopay_saved_cards_collection_detail` review the result: the second one needs the `code` returned by the first. > **Warning:** `tilopay_saved_cards_create_payments` moves real money from the stored cards. Treat it as a > one-shot operation: confirm before, and verify after with the collection detail. ## Debug an integration and support a developer [#depurar-integracion] 1. `tilopay_diagnostics` tests the credentials and one endpoint per API group, to separate a credentials problem from a request problem. 2. `tilopay_debug_code` receives the code snippet, log or stack trace and returns diagnosis, root cause, fix and risks. 3. `tilopay_help_guides` answers panel usage questions with the official guides. 4. `tilopay_list_transactions` and `tilopay_get_transaction` confirm whether the operation the developer believes failed actually reached Tilopay, and with what result. `tilopay_debug_code` does not read the repository or run the code: it analyses only what you pass in. Strip credentials from the snippet before sending; the tool masks what it recognises as a secret, but that does not replace your review. With these four tools a support agent handles the whole technical onboarding: verifies the account, finds the cause of the error, delivers the fix with the official guide and confirms against real transactions. Repeated questions are the signal of which guide or example the portal still needs. > **Note:** Sandbox test cards are in the portal's testing guide. Sandbox mode is switched from the account; > there is no separate host. ## Tilopay as a tool for other agents [#tilopay-como-herramienta] 1. Access is self-service: you add the MCP server in your client with the URL `https://mcp.tilopay.com/mcp` and authorize the session with your Tilopay account email and password, plus the verification code we send. There is no form and no prior approval. 2. The external agent (an ERP assistant, an online-store bot, an accounting agent) connects to the MCP server with that OAuth session and runs `tilopay_diagnostics` to verify the connection. 3. With the tool catalogue loaded, the external agent uses the read tools (`tilopay_sales_summary`, `tilopay_list_transactions`, `tilopay_recurring_*`, `baas_*`) to answer business questions inside its own conversation. 4. For actions that create or move money (`tilopay_create_charge`, `tilopay_modify_transaction`, `tilopay_saved_cards_create_payments`, `tilopay_recurring_manage_subscriber`) the external agent applies the confirmation policy described on the permissions page before executing. This recipe changes the MCP's position: it is not just an assistant inside Tilopay, it is the payments layer any agent can call. Every other recipe on this page works the same when a third-party agent runs it. > **Warning:** MCP usage may carry additional costs depending on volume; Tilopay communicates them to the > merchant. Tools marked as sensitive require human confirmation in whichever > MCP client runs them, Tilopay's or a third party's. Want these recipes packaged and repeatable? [Install a skill](/en/developers/agentes/mcp/skills). --- # Skills for agents > Ready-made instruction packages so an agent can use the Tilopay MCP for concrete tasks, such as the cash close, charging over WhatsApp or recovering declined payments. - kind: guide - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/skills ## What a skill is [#que-es] A skill is a package of instructions (a `SKILL.md` plus its references) that the agent loads only when you ask for something on that topic. It requires no code and no credentials: it uses the Tilopay MCP that is already connected, and the instructions tell the agent which tools to call, in what order and how to validate the numbers before answering. The difference from [recipes](/en/developers/agentes/mcp/recetas) is that a recipe is the example prompt; a skill packages it so it is repeatable, with calculation and safety rules. ## Before you start [#antes] You need a Tilopay account. If you install the plugin (option 2 below), the MCP connector is included and you only have to authorize it with your account the first time. For the other options, first [connect the MCP in your client](/en/developers/agentes/mcp/conectar). Skills never ask for or store credentials. ## Available skills [#skills] ### All skills in one plugin All six skills and the Tilopay MCP connector in one plugin for Cowork, the claude.ai chat and Claude Code. Install it from the Tilopay marketplace with the commands below. ```text /plugin marketplace add tilopay/claude-plugins /plugin install tilopay-mcp-skills@tilopay ``` - [View on GitHub](https://github.com/tilopay/claude-plugins) - Version: 1.0.1 - Copy prompt for Claude Code: “Read the Tilopay skills index at https://tilopay.com/en/developers/skills/index.md. For each skill, create a folder with its name inside ~/.claude/skills/, save its SKILL.md there and, if the index lists reference files, save them in a references/ subfolder with the same file name. When you finish, tell me which skills were installed.” ### cierre-de-caja Cash close for the day, week or month by currency: approved, declined, refunds, exact breakdown of commissions and withholdings, net to settle and next deposit, validating that the numbers match Tilopay's official balance. - Example prompt: «Give me today's cash close» - MCP tools: `tilopay_list_transactions`, `tilopay_sales_summary`, `tilopay_settlement_balance`, `tilopay_settlements_list`, `tilopay_switch_commerce` - Groups: [Sales and transactions](https://tilopay.com/en/developers/agentes/mcp/ventas) - Version: 1.0.1 - [SKILL.md](https://tilopay.com/developers/skills/cierre-de-caja/SKILL.md) - Prompt to use it without installing: “Read https://tilopay.com/developers/skills/cierre-de-caja/SKILL.md and the files it references, and follow those instructions using my Tilopay connector. Give me today's cash close” ### cobrar-por-whatsapp Turns a sentence into a real charge and a WhatsApp message ready to send; generates the QR code when asked and later checks whether the customer has paid. - Example prompt: «Charge Ana 25,000 colones for the photography course» - MCP tools: `tilopay_create_charge`, `tilopay_send_payment_link_whatsapp`, `tilopay_send_payment_link_email`, `tilopay_payment_link_qr`, `tilopay_list_receipts`, `tilopay_find_payment`, `tilopay_list_contacts` - Groups: [Payment links](https://tilopay.com/en/developers/agentes/mcp/enlaces-de-pago), [Payment receipts](https://tilopay.com/en/developers/agentes/mcp/comprobantes), [Contacts](https://tilopay.com/en/developers/agentes/mcp/contactos) - Version: 1.0.1 - [SKILL.md](https://tilopay.com/developers/skills/cobrar-por-whatsapp/SKILL.md) - Prompt to use it without installing: “Read https://tilopay.com/developers/skills/cobrar-por-whatsapp/SKILL.md and the files it references, and follow those instructions using my Tilopay connector. Charge Ana 25,000 colones for the photography course” ### rechazos-y-recuperacion Analyzes declined payments, groups them by reason using the labels of the official “Decline reasons” guide, detects patterns (same customer retrying, same order recovered later, channel, time of day) and proposes concrete actions, including the message to the customer and a new link. - Example prompt: «Why were payments declined this month and which ones can I recover?» - MCP tools: `tilopay_list_transactions`, `tilopay_sales_summary`, `tilopay_create_charge`, `tilopay_send_payment_link_whatsapp`, `tilopay_help_guides` - Groups: [Sales and transactions](https://tilopay.com/en/developers/agentes/mcp/ventas), [Payment links](https://tilopay.com/en/developers/agentes/mcp/enlaces-de-pago), [Support and diagnostics](https://tilopay.com/en/developers/agentes/mcp/soporte) - Version: 1.0.1 - [SKILL.md](https://tilopay.com/developers/skills/rechazos-y-recuperacion/SKILL.md) - Prompt to use it without installing: “Read https://tilopay.com/developers/skills/rechazos-y-recuperacion/SKILL.md and the files it references, and follow those instructions using my Tilopay connector. Why were payments declined this month and which ones can I recover?” - [“Decline reasons” guide (Spanish)](https://tilopay.com/guias/panel-transacciones/motivos-de-rechazo) ### conciliacion-mensual Accounting reconciliation for a month, half-month or date range: cross-checks approved sales, refunds and deposited settlements, with all six cost fields labeled, and delivers a CSV or Excel file for the accountant plus a summary of discrepancies. - Example prompt: «Reconcile September for my accountant» - MCP tools: `tilopay_list_transactions`, `tilopay_refunds_list`, `tilopay_settlements_list`, `tilopay_settlement_transactions`, `tilopay_settlement_balance`, `tilopay_billing_list`, `tilopay_switch_commerce` - Groups: [Sales and transactions](https://tilopay.com/en/developers/agentes/mcp/ventas), [Settlements, deposits and billing](https://tilopay.com/en/developers/agentes/mcp/liquidaciones), [Account, commerce and team](https://tilopay.com/en/developers/agentes/mcp/cuenta) - Version: 1.0.0 - [SKILL.md](https://tilopay.com/developers/skills/conciliacion-mensual/SKILL.md) - Prompt to use it without installing: “Read https://tilopay.com/developers/skills/conciliacion-mensual/SKILL.md and the files it references, and follow those instructions using my Tilopay connector. Reconcile September for my accountant” ### cfo-de-bolsillo Financial analysis of sales: compares periods of equal length, average transaction value, best days and hours, customer concentration, Tilopay's effective cost as a percentage of sales, and a simple projection of month-end results and cash flow with the next deposit, always labeled as an estimate. - Example prompt: «How am I doing this month compared with last month?» - MCP tools: `tilopay_list_transactions`, `tilopay_sales_summary`, `tilopay_settlement_balance`, `tilopay_settlements_list`, `tilopay_get_business_profile` - Groups: [Sales and transactions](https://tilopay.com/en/developers/agentes/mcp/ventas), [Settlements, deposits and billing](https://tilopay.com/en/developers/agentes/mcp/liquidaciones), [Business profile and recipes](https://tilopay.com/en/developers/agentes/mcp/perfil) - Version: 1.0.0 - [SKILL.md](https://tilopay.com/developers/skills/cfo-de-bolsillo/SKILL.md) - Prompt to use it without installing: “Read https://tilopay.com/developers/skills/cfo-de-bolsillo/SKILL.md and the files it references, and follow those instructions using my Tilopay connector. How am I doing this month compared with last month?” ### catalogo-y-cobros-fijos Manages the product and service catalog: finds products with their payment links, creates or edits them from a sentence or a list, provides a product QR code or a permanent countertop QR code, and connects the catalog to Facebook/Instagram, Google Shopping, TikTok or Pinterest. - Example prompt: «Add Yoga class to the catalog for 8,000 colones» - MCP tools: `tilopay_catalog_list_items`, `tilopay_catalog_get_item`, `tilopay_catalog_create_item`, `tilopay_catalog_update_item`, `tilopay_catalog_delete_item`, `tilopay_payment_link_qr`, `tilopay_static_payment_qr`, `tilopay_catalog_feed`, `tilopay_store_list`, `tilopay_send_payment_link_whatsapp` - Groups: [Catalog](https://tilopay.com/en/developers/agentes/mcp/catalogo), [Payment links](https://tilopay.com/en/developers/agentes/mcp/enlaces-de-pago), [Micro-store and orders](https://tilopay.com/en/developers/agentes/mcp/microtienda) - Version: 1.0.0 - [SKILL.md](https://tilopay.com/developers/skills/catalogo-y-cobros-fijos/SKILL.md) - Prompt to use it without installing: “Read https://tilopay.com/developers/skills/catalogo-y-cobros-fijos/SKILL.md and the files it references, and follow those instructions using my Tilopay connector. Add Yoga class to the catalog for 8,000 colones” ## How to install [#instalar] **Option 1 · Use it right now, without installing anything (Claude, ChatGPT or another assistant with the MCP connected).** 1. Open a new conversation with the Tilopay MCP connected. 2. On the skill's card, tap "Copy prompt". 3. Paste it and send it. The assistant reads the instructions from tilopay.com and applies them in that conversation. Next time, paste the prompt again (or use option 2 to keep it set up). For example, the cash close prompt: ```text Read https://tilopay.com/developers/skills/cierre-de-caja/SKILL.md and the files it references, and follow those instructions using my Tilopay connector. Give me today's cash close ``` **Option 2 · Install all six at once in Claude (recommended).** Tilopay publishes its skills as a plugin on GitHub. Add it once and it stays saved in your account: it works in Cowork, in the claude.ai chat and in Claude Code. The plugin includes the Tilopay MCP connector: the first time you use it, Claude asks you to authorize it with your Tilopay account (in Cowork, from the plugin's Connectors tab). In the Claude app (Cowork): 1. Open the Cowork tab, then Customize in the sidebar. 2. Go to Plugins and click Add → Add marketplace → Add from a repository. 3. Enter `tilopay/claude-plugins` and confirm. 4. Go back to Discover, choose tilopay-mcp-skills and click Install. In Claude Code (terminal), run these two commands: ```text /plugin marketplace add tilopay/claude-plugins /plugin install tilopay-mcp-skills@tilopay ``` From then on, when you ask for something on that topic (“give me today's cash close”) with the Tilopay MCP connected, Claude loads the skill automatically. To get improvements, use Check for updates in the marketplace or turn on Sync automatically (in Claude Code: `/plugin marketplace update tilopay`).

Plugins are available on Claude's paid plans. If your organization limits which marketplaces you can add, an Owner must allow tilopay/claude-plugins or sync it for everyone from the organization settings.

**Option 3 · Keep one skill set up in a Claude Project.** The simplest way without installing anything: create a Project in Claude, open the skill's `SKILL.md` (the link on the card), copy all its content and paste it into the project instructions. For the assistant to use it in any conversation, paste the option 1 prompt whenever you need it. **Option 4 · Set it up by hand in Claude Code (without the marketplace).** Paste this prompt into Claude Code: ```text Read the Tilopay skills index at https://tilopay.com/en/developers/skills/index.md. For each skill, create a folder with its name inside ~/.claude/skills/, save its SKILL.md there and, if the index lists reference files, save them in a references/ subfolder with the same file name. When you finish, tell me which skills were installed. ``` The prompt asks Claude Code to create one folder per skill under `~/.claude/skills/` (or in the project's `.claude/skills/` to share it with your team), saving each skill's published `SKILL.md` and, when it has reference files, those files too in a `references/` subfolder. **Other MCP clients.** Use the `SKILL.md` as system or project instructions, or paste the prompt from option 1. ## How it works inside [#como-funciona] Taking the cash close as an example, the sequence is: 1. The agent converts "today" to your local time. 2. It calls `tilopay_list_transactions` with that range expressed in UTC. 3. It classifies each row only by `code`: `1` is approved and `1101` is a refund. 4. It adds up the six cost fields by their exact names: `commission`, `iva_commission`, `rent`, `retention_iva`, `cost` and `iva_tilopay`. 5. It validates that gross − deductions = net and cross-checks it with `tilopay_settlement_balance` before answering. > **Warning:** Skills never call tools that move money without your confirmation. ## Create your own skill [#crear] The minimum structure of a `SKILL.md` is a frontmatter with `name` and `description`, followed by the procedure: ```markdown --- name: my-skill description: What the skill does and when the agent should use it. --- 1. Procedure step, with the exact tool being called. 2. Validation rule before answering. ``` Three tips: 1. Name the exact tools the agent must call. 2. Include the validation rules for the numbers. 3. Say what NOT to do. To see every available tool, check the manifest [`/developers/mcp.json`](/developers/mcp.json). If you built a skill that is useful for other merchants, send it to [soporte@tilopay.com](mailto:soporte@tilopay.com). --- # Acquiring tools > Summary of the MCP tool groups over the acquiring API: sales and transactions, payment links, product catalog, contacts, business profile and recipes, micro-store and orders, alerts and notifications, payment receipts, reminders, recurring billing, stored cards and bulk collections, settlements, deposits and billing, account, merchant and team, and support and diagnostics. - kind: mcp-index - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/adquirencia ## What it covers [#que-cubre] These are the tools that work over Tilopay's **acquiring API**: card payments, payment links and the data around them. They come enabled with standard MCP access, with no extra request, and they always operate on the account tied to the MCP user. The [banking API](/en/developers/agentes/mcp/api-bancario) tools are a separate case: they are enabled on request and require banking API credentials. ## Groups [#grupos] - [Sales and transactions](https://tilopay.com/en/developers/agentes/mcp/ventas) — 8 tools: Transaction lookups, sales metrics and modifying a transaction (capture, refund or reversal). - [Payment links](https://tilopay.com/en/developers/agentes/mcp/enlaces-de-pago) — 7 tools: Creating, reading and deleting payment links, plus preparing delivery over WhatsApp or email. - [Catalog](https://tilopay.com/en/developers/agentes/mcp/catalogo) — 6 tools: The merchant's product and service catalog: reading, creating and editing items (without changing their payment link), the feed for external channels and deleting items. Includes sensitive operations. - [Contacts](https://tilopay.com/en/developers/agentes/mcp/contactos) — 3 tools: The merchant address book: saved contacts to reuse phone and email when sending a payment link. - [Business profile and recipes](https://tilopay.com/en/developers/agentes/mcp/perfil) — 3 tools: The merchant business profile (what they do, what they sell, how they charge and what they want to improve) and the automation recipes the agent suggests from it. - [Micro-store and orders](https://tilopay.com/en/developers/agentes/mcp/microtienda) — 13 tools: Micro-stores with a cart built from the catalog, plus managing the orders they generate: setup, summary, listing, pending deliveries and closing. - [Alerts and notifications](https://tilopay.com/en/developers/agentes/mcp/avisos) — 2 tools: The merchant WhatsApp alerts: approved and declined charges, daily summary and orders. These tools only read and change the settings; they do not send messages on their own. - [Payment receipts](https://tilopay.com/en/developers/agentes/mcp/comprobantes) — 2 tools: Receipts for approved charges: listing them and resending a customer the receipt for their payment. - [Reminders](https://tilopay.com/en/developers/agentes/mcp/recordatorios) — 3 tools: WhatsApp reminders, one-off or recurring, which can run an agent action when they fire (sales summary, pending links, balances). - [Recurring billing](https://tilopay.com/en/developers/agentes/mcp/recurrentes) — 23 tools: End-to-end recurring billing: creating, editing and deleting plans, coupons and subscribers, subscription links, failed charges, batch catch-up (with a confirmation fingerprint) and the guided step-by-step to build a plan. Includes sensitive operations. - [Stored cards and bulk collections](https://tilopay.com/en/developers/agentes/mcp/tarjetas-guardadas) — 10 tools: Customers' stored cards and bulk collections: tokenization, available payment methods, charging a stored card, removing a card and collection groups. - [Settlements, deposits and billing](https://tilopay.com/en/developers/agentes/mcp/liquidaciones) — 7 tools: - [Account, merchant and team](https://tilopay.com/en/developers/agentes/mcp/cuenta) — 8 tools: - [Support and diagnostics](https://tilopay.com/en/developers/agentes/mcp/soporte) — 3 tools: Answers taken from the official guides and a connectivity test against each API group. The access level of each tool and the detail of its parameters live on its group page; the permissions summary is in [Permissions](/en/developers/agentes/mcp/permisos). --- # Sales and transactions > MCP tools to query transactions, measure sales and modify a transaction. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/ventas ## What it covers [#que-cubre] This group covers reading transactions and analysing your sales, plus modifying a transaction: capture, refund or reversal. The modification moves real money and is marked as sensitive. ## Tools [#herramientas] ### Sales summary and trends [#tilopay-sales-summary] - `tilopay_sales_summary` - Access: Read only - API operation: `POST /api/v1/consultTransactions (y cálculo local)` — https://tilopay.com/en/developers/api/procesos-operativos/consult-transactions Computes sales metrics from the transactions: totals by currency, average ticket, approval rate, sales by day, weekday and hour, top customers, decline reasons and trend. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `startDate` | string | yes | Start date "YYYY-MM-DD HH:mm:ss" | | `endDate` | string | yes | End date "YYYY-MM-DD HH:mm:ss" | | `includeDeclined` | boolean | — | Include declined transactions to measure the approval rate (true by default) | | `environment` | string (production | test) | — | "production" (default) or "test" (your account's test mode) | | `currency` | array | — | Array of ISO codes, e.g. ["CRC","USD"]; empty = all | **Returns** `{ summary, trends, environmentNote }` summary carries range, timezoneNote, totalRows, payments {total, approved, declined, approvalRate}, refunds {total, approved, failed, successRate}, byCurrency per currency with payments, itemised costs (commission, iva_commission, cost, cost_iva, retention_iva, retention_rent, totalDeducted, taxWithholdings, pspCost), netToLiquidate, reconciles and reconciliationDelta; refunds; and netForPeriod. It also returns daily, sample, byWeekday, byHour, topCustomers, declineReasons, refundFailureReasons and transactionTypes. byWeekday, byHour and topCustomers come back null when the sample is too small (fewer than 30 rows or fewer than 5 distinct days). trends comes back null in that same case. Hours and days are in UTC. ### Sales analyst agent [#tilopay-analyze-sales] - `tilopay_analyze_sales` - Access: Read only - API operation: `POST /api/v1/consultTransactions (y análisis con modelo)` — https://tilopay.com/en/developers/api/procesos-operativos/consult-transactions Agent that analyses the transactions in a date range and returns a natural-language report: performance, trends, seasonality, approval quality, risks and actionable recommendations. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `startDate` | string | yes | Start date "YYYY-MM-DD HH:mm:ss" | | `endDate` | string | yes | End date "YYYY-MM-DD HH:mm:ss" | | `question` | string | — | Specific question or focus for the analysis | | `environment` | string (production | test) | — | "production" (default) or "test" (your account's test mode) | | `currency` | array | — | Array of ISO codes, e.g. ["CRC","USD"]; empty = all | **Returns** `{ report, summary, trends, environmentNote }` report is the natural-language report; summary and trends are the same ones from tilopay_sales_summary. When there are no transactions in the range it returns only a text saying so, with no structuredContent. ### Did they pay me yet? [#tilopay-find-payment] - `tilopay_find_payment` - Access: Read only Checks whether a customer already paid: it reviews the Tilopay charges and, if the banking session is open, also the incoming entries of the bank account. Use it for questions like 'did Ana pay me yet?' or 'did the ₡25,000 payment come in?'. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | — | Customer name (partial match) | | `amount` | number | — | Expected payment amount | | `currency` | string | — | Currency of the amount, e.g. CRC or USD | | `hours` | integer | — | Hours to look back (24 by default) | | `reference` | string | — | Order number or reference | **Returns** `{ found, bank_checked, payments }` payments = up to 5 matches with { source: "tilopay" | "baas", at, amount, currency, name, method, reference, email? }. ### List Tilopay transactions [#tilopay-list-transactions] - `tilopay_list_transactions` - Access: Read only - API operation: `POST /api/v1/consultTransactions` — https://tilopay.com/en/developers/api/procesos-operativos/consult-transactions Queries Tilopay transactions in a date range. It can filter by currency, order number, customer email and environment. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `startDate` | string | yes | Start date, e.g. "2026-08-01 00:00:00" | | `endDate` | string | yes | End date, e.g. "2026-08-31 23:59:59" | | `status` | string (approved | declined | all) | — | approved = only approved, declined = only declined, all = all | | `sort` | string (newest | oldest) | — | newest = most recent first | | `onlyAproved` | boolean | — | Compatibility: false is equivalent to status: "all" | | `environment` | string (production | test) | — | Environment, production by default | | `currency` | array | — | Currencies, e.g. ["USD","CRC"] | | `orderNumber` | string | — | Filter by order number | | `email` | string | — | Filter by customer email | | `limit` | integer | — | Maximum rows to return (100 by default, 500 maximum) | **Returns** `{ total, transactions[], environmentNote }` total = rows found before applying limit; transactions = the rows returned; environmentNote warns when the queried environment returned no rows. ### Get one transaction [#tilopay-get-transaction] - `tilopay_get_transaction` - Access: Read only - API operation: `POST /api/v1/consult` — https://tilopay.com/en/developers/api/procesos-operativos/consult Gets the detail of a specific Tilopay transaction from the order number. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `orderNumber` | string | yes | Order number of the transaction | | `merchantId` | string | — | Merchant ID (optional) | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Capture, refund or reverse [#tilopay-modify-transaction] - `tilopay_modify_transaction` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/processModification` — https://tilopay.com/en/developers/api/procesos-operativos/process-modification Modifies a Tilopay transaction: capture, refund or reversal for the given amount. The amount can never exceed that of the original sale: if the user asks for more, tell them the real sale amount and ask whether to refund that amount or part of it; do not ask for confirmation of a larger amount. It only refunds approved sales, never an order that is already a refund (it starts with «Re-»). When Tilopay confirms the refund, a text receipt ready to forward to the customer is generated. Sensitive operation: it affects real money and requires a second factor. Call it first without `confirmation_code`: a 6-digit code is sent to the merchant's email and the response includes `requires_confirmation`. Repeat the same call, with the same parameters, adding `confirmation_code`. Report the result exactly as the tool returns it (state): if it is pending or not confirmed, never say the refund was already made. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `orderNumber` | string | yes | Order number of the transaction | | `action` | string (capture | refund | reversal) | yes | Type of modification | | `amount` | number | yes | Amount to modify, greater than zero | | `currency` | string | — | Currency of the movement, e.g. CRC or USD | | `client_name` | string | — | Customer name, for the receipt | | `client_email` | string | — | Customer email, for the receipt | | `confirmation_code` | string | — | 6-digit code received by email to authorize the operation | **Returns** `{ result, state, receipt_code, receipt_url, receipt_text, receipt_whatsapp_url }` Raw Tilopay API response under the `result` key. ### Refund history [#tilopay-refunds-list] - `tilopay_refunds_list` - Access: Read only The merchant's refunds. pending=true returns the refunds pending processing; otherwise, the history. Query only: to refund use tilopay_modify_transaction. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `pending` | boolean | — | Pending only | | `page` | integer | — | Page (1 by default) | | `limit` | integer | — | Records per page (20 by default) | | `search` | string | — | Text to search | **Returns** `{ result }` The refunds listing as Tilopay delivers it; with pending: true, only the pending ones. ### Scheduled cash close [#tilopay-cash-close] - `tilopay_cash_close` - Access: Write Daily cash close (sent over WhatsApp): the day's sales per currency, declines grouped by reason and the next Tilopay settlement. Action 'get' shows the configuration; 'set' changes it (enabled, time_of_day 'HH:mm' in local time, days 0=Sunday…6=Saturday; 'Monday to Saturday' = [1,2,3,4,5,6]); 'preview' builds today's close right now. For 'turn off the close' use set with enabled=false. Only the owner can configure it. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `action` | string (get | set | preview) | yes | "get" shows the configuration, "set" changes it (owner only), "preview" builds today's close | | `enabled` | boolean | — | Only with set: true turns it on, false turns it off. Default 20:00, Monday to Saturday | | `time_of_day` | string | — | Local time 'HH:mm' (24 h). 8 pm = '20:00' | | `days` | array | — | 0=Sunday … 6=Saturday | **Returns** Text only: the current configuration, or today's close with preview. --- # Payment links > MCP tools to create, read and delete payment links, and prepare their delivery. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/enlaces-de-pago ## What it covers [#que-cubre] With this group the agent creates a payment link, reads its detail, deletes it and prepares delivery to the customer over WhatsApp or email. WhatsApp delivery does not send the message: it returns a link ready to send. ## Tools [#herramientas] ### Create a charge with a link [#tilopay-create-charge] - `tilopay_create_charge` - Access: Write Creates a charge and returns the Tilopay short link (tilo.co/s/…) that opens the secure payment form directly; if it is not available, the own link tilopay.shop/pagar/…. The response includes url (the short one when it exists) and fallback_url (the own one); both work. On WhatsApp, send the QR as an image together with the link at once; do not call the QR tool afterwards. If customer data is missing, the link page asks for it before charging. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `amount` | number | yes | Amount to charge | | `currency` | string | — | Currency, e.g. USD or CRC. If omitted, the merchant's country currency is used. | | `reference` | string | yes | Charge reference in plain text, with spaces. Do not convert to a slug or replace spaces with dashes or underscores. | | `description` | string | yes | Concept visible to the customer. Keep the merchant's text literally, with spaces, accents and capitals; e.g. 'prueba link', never 'prueba_link'. | | `client` | string | — | Customer name | | `client_email` | string | — | Customer email | | `client_phone` | string | — | Customer phone | **Returns** `{ code, url, fallback_url, orderNumber }` url = short link (tilo.co/s/… or https://tilopay.shop/pagar/{code}); fallback_url = the latter. ### Payment link detail [#tilopay-get-payment-link] - `tilopay_get_payment_link` - Access: Read only - API operation: `GET /api/v1/getDetailLinkPayment/{link_payment_id}/{api_key}` — https://tilopay.com/en/developers/api/links-de-pago/detail-link-payment Returns the detail of a payment link by its ID. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `linkPaymentId` | string | yes | Payment link ID | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Delete payment link [#tilopay-delete-payment-link] - `tilopay_delete_payment_link` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/deleteLinkPayment` — https://tilopay.com/en/developers/api/links-de-pago/delete-link-payment Deletes a payment link by its ID. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | ID of the payment link to delete | | `confirmation_code` | string | — | 6-digit code received by email to authorize the operation | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Send payment link over WhatsApp [#tilopay-send-payment-link-whatsapp] - `tilopay_send_payment_link_whatsapp` - Access: Write Prepares sending the payment link over WhatsApp: it returns a wa.me link with the message ready. It accepts the name of a saved contact or a phone number. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `link_url` | string | yes | Payment link URL | | `contact_name` | string | — | Name of the saved contact or of the customer | | `phone` | string | — | WhatsApp phone, e.g. +50688887777 | | `amount` | number | — | Only used to write the message; it does not change the link | | `currency` | string | — | Only used to write the message; it does not change the link | | `description` | string | — | Only used to write the message; it does not change the link | | `message` | string | — | Your own message text | | `save_contact` | boolean | — | Save the contact in the address book | | `send_now` | boolean | — | Deprecated: ignored. The wa.me link is always returned. | **Returns** `{ to, message, whatsapp_url }` It does not send the message: it returns the resolved phone, the drafted text and a wa.me link to send it with one tap. When there is no phone and no saved contact with that name, it returns an error asking for it. It saves the contact unless save_contact is false. ### Send payment link over email [#tilopay-send-payment-link-email] - `tilopay_send_payment_link_email` - Access: Write Emails the payment link to the customer. It accepts the name of a saved contact or an email address. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `link_url` | string | yes | Payment link URL | | `email` | string | — | Recipient email | | `contact_name` | string | — | Name of the saved contact or of the customer | | `amount` | number | — | Only used to write the message; it does not change the link | | `currency` | string | — | Only used to write the message; it does not change the link | | `description` | string | — | Only used to write the message; it does not change the link | | `message` | string | — | Additional note for the customer | | `save_contact` | boolean | — | Save the contact in the address book | **Returns** `{ sent, to }` sent is true and to is the address it was sent to. When there is no email and no saved contact with that name, it returns an error asking for it. ### QR of a payment link or product [#tilopay-payment-link-qr] - `tilopay_payment_link_qr` - Access: Read only Generates the QR of a payment link or of a catalog product and sends it as an image (PNG) (sending as an image is only in the WhatsApp assistant; other clients use qr_url). Use it when the merchant asks for the QR to share or print. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `link_url` | string | — | Full payment link (https://...) | | `catalog_id` | string | — | Catalog product ID | | `label` | string | — | Name for the image footer | **Returns** `{ link, qr_url }` link = payment link; qr_url = QR code image. ### Permanent open-amount QR [#tilopay-static-payment-qr] - `tilopay_static_payment_qr` - Access: Read only Returns the merchant's permanent open-amount link and QR (the customer scans, enters the amount and pays). Query only: if the merchant does not have it enabled, it explains so and does not enable it. **Parameters** No parameters. **Returns** `{ enabled, link, qr_url }` enabled = whether the static QR is active; link = the merchant's fixed link; qr_url = QR code image. --- # Catalog > MCP tools to browse, create, edit and delete products and services in your catalog, and get feeds for other channels. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/catalogo ## What it covers [#que-cubre] You can list your products and services, view their details, create new ones, edit or delete them, and get feeds for other channels. Deleting a product is sensitive: the WhatsApp Assistant asks for confirmation in chat and MCP clients must ask for human confirmation; no email code is required. ## Tools [#herramientas] ### List catalog of products or services [#tilopay-catalog-list-items] - `tilopay_catalog_list_items` - Access: Read only - API operation: `GET /api/v1/getLinkPaymentList/{api_key}/{limit}` — https://tilopay.com/en/developers/api/links-de-pago/payment-item-list Lists the products or services of the Tilopay catalog (title, price and short link). It can search by name or description (ignoring accents and case). The Tilopay listing may take a few minutes to reflect changes made in the dashboard; whatever is created from here appears immediately and the response of tilopay_catalog_create_item already includes the new item. Use refresh=true to force a fresh read. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | — | Maximum items (50 by default, 500 maximum) | | `search` | string | — | Text to search in the product name | | `page` | integer | — | Page (1 by default) | | `refresh` | boolean | — | true to ignore the short-term memory and read again | | `include_qr` | boolean | — | Include each item's full base64 QR (false by default; the listing returns only the link and has_qr) | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Catalog item detail [#tilopay-catalog-get-item] - `tilopay_catalog_get_item` - Access: Read only - API operation: `GET /api/v1/getLinkPaymentById/{link_payment_id}/{api_key}` — https://tilopay.com/en/developers/api/links-de-pago/payment-by-id Gets the detail of a product or service of the Tilopay catalog by its ID. The base64 QR only with include_qr=true. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `itemId` | string | yes | Catalog item ID | | `include_qr` | boolean | — | Include the base64 QR (false by default; use it only if the merchant asks for the QR) | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Add a product or service to the catalog [#tilopay-catalog-create-item] - `tilopay_catalog_create_item` - Access: Write - API operation: `POST /api/v1/createLinkPayment` — https://tilopay.com/en/developers/api/links-de-pago/create-link-payment Creates a product or service in the Tilopay catalog with its price and returns the short payment link and the QR as an image. If the merchant sent a photo on WhatsApp in the last 10 minutes, it uses it as the product image. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Product or service name | | `amount` | number | yes | Price | | `currency` | string | — | Currency, e.g. CRC or USD. If omitted, the merchant's country currency is used. | | `description` | string | — | Product or service description | | `media_ref` | string | — | Exact reference of the image received over WhatsApp, if known | | `use_last_image` | boolean | — | false to create the product without a photo even if there is a recent image | **Returns** `{ result, linkId, url }` result = the raw API response; linkId = id of the payment link representing the product; url = the product's charge link. ### Edit a catalog product [#tilopay-catalog-update-item] - `tilopay_catalog_update_item` - Access: Write Edits a product of the Tilopay catalog (name, price, currency, description or photo) without changing its payment link. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `itemId` | string | yes | Catalog item ID | | `name` | string | — | New name (or the same one) | | `amount` | number | — | New price (or the same one) | | `currency` | string | — | Currency; if omitted, the merchant's country currency | | `description` | string | — | New description (or the same one) | | `media_ref` | string | — | Exact reference of the image received over WhatsApp, if known | | `use_last_image` | boolean | — | false to keep the photo unchanged even if there is a recent image | **Returns** `{ item, short_link, changed }` item = the updated product; short_link = its payment link (unchanged); changed = the fields that changed. Changing the amount or the currency is owner-only. ### Delete a catalog product [#tilopay-catalog-delete-item] - `tilopay_catalog_delete_item` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/deleteLinkPayment` — https://tilopay.com/en/developers/api/links-de-pago/delete-link-payment Deletes a product from the Tilopay catalog and disables its payment link. No email code is required. The WhatsApp Assistant asks for confirmation in chat; in MCP clients, human confirmation is the client's responsibility. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `itemId` | string | yes | Catalog item ID | **Returns** `{ result }` result = API response deleting the payment link representing the product. ### Sync the catalog with social networks and search engines [#tilopay-catalog-feed] - `tilopay_catalog_feed` - Access: Write Delivers the synced catalog link and the steps to connect it in Facebook/Instagram, Google Shopping, TikTok or Pinterest (one channel at a time, the one the merchant asks for). With send_email=true it sends it to the merchant's email. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `platform` | string (facebook | google | tiktok | pinterest) | — | Requested channel. If the merchant did not say which, DO NOT call: ask first. | | `send_email` | boolean | — | true if they asked to receive it by email | **Returns** `{ url, urls, omitted_without_photo, platform }` url = the requested channel's feed; urls = [{ currency, url }] per currency; omitted_without_photo = products omitted for having no photo. --- # Contacts > MCP tools for your address book: view, save and delete contacts. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/contactos ## What it covers [#que-cubre] The address book lets the agent reuse phone and email when sending a payment link, without asking again. Deleting a contact is irreversible. ## Tools [#herramientas] ### Save contact [#tilopay-save-contact] - `tilopay_save_contact` - Access: Write Saves or updates a merchant contact (name, WhatsApp phone and email) to reuse it when sending links. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Contact name | | `phone` | string | — | WhatsApp phone, e.g. +50688887777 | | `email` | string | — | Contact email (saved only if valid) | | `note` | string | — | Internal note | **Returns** `{ contact }` The saved or updated contact. ### View contacts [#tilopay-list-contacts] - `tilopay_list_contacts` - Access: Read only Lists the merchant's saved contacts; it can filter by name. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `search` | string | — | Text to search in the name | **Returns** `{ contacts }` List of the merchant's saved contacts. ### Delete contact [#tilopay-delete-contact] - `tilopay_delete_contact` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Deletes a saved merchant contact by its name. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Contact name | **Returns** `{ removed }` removed is true. When no contact exists with that name, it returns an error. --- # Business profile and recipes > MCP tools to read and complete the business profile and to get automation recipes from it. - kind: mcp-tool - status: stable - access: write - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/perfil ## What it covers [#que-cubre] The agent keeps a short business profile: what the business does, what it sells, where it sells, what its charges look like and what it wants to improve. From that profile it builds the automation recipes it proposes to you, and it knows which question is still pending (the questionnaire is four questions at most). The profile is saved field by field: whatever is not sent is preserved. Recipes change with the modules the user has enabled: banking recipes only appear when the [banking API](/en/developers/agentes/mcp/api-bancario) is enabled. These tools do not call the Tilopay API: they work on the MCP server's own data. ## Tools [#herramientas] ### View business profile [#tilopay-get-business-profile] - `tilopay_get_business_profile` - Access: Read only Returns the business profile: the data the Tilopay account already has (name, line of business, location, phone, billing email, currencies, logo) and what is still missing to ask, with the next pending question. **Parameters** No parameters. **Returns** `{ profile, next_question, total_questions, is_complete }` profile = the saved business profile (or null if there is none); next_question = the next pending onboarding question; total_questions = how many questions the questionnaire has; is_complete = whether the profile is already complete. ### Save business profile [#tilopay-save-business-profile] - `tilopay_save_business_profile` - Access: Write Saves or completes the business profile with what the merchant answered or confirmed. It can save one field at a time; fields not sent are kept, and what is saved here is no longer overwritten by Tilopay data. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `business_type` | string | — | What the business does | | `main_products` | string | — | Main products or services | | `sales_channels` | string | — | Sales channels: in-store, online, social media, WhatsApp | | `ticket_pattern` | string | — | What the charges look like: amounts, frequency, currency | | `goals` | string | — | What the merchant wants to improve | | `notes` | string | — | Additional useful details | | `opening_hours` | string | — | Opening hours | | `tone` | string | — | Communication tone with customers | | `short_description` | string | — | Short business description | | `social_links` | string | — | Website and social networks | | `refund_policy` | string | — | Returns or refunds policy | | `business_name` | string | — | Correction of the business name | | `industry` | string | — | Correction of the business industry | | `phone` | string | — | Correction of the business phone | | `address` | string | — | Correction of the address | | `invoice_email` | string | — | Correction of the billing email | **Returns** `{ profile, next_question, is_complete }` profile = the already updated profile (fields that are not sent are kept); next_question = the next pending question; is_complete = whether the profile became complete with this save. ### Recipes and automations [#tilopay-recommend-automations] - `tilopay_recommend_automations` - Access: Read only Returns suggested recipes and automations for the merchant based on their business profile and the active MCP modules. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | — | How many recipes to return (5 by default) | **Returns** `{ profile_complete, next_question, recipes }` recipes = suggested recipes and automations based on the business profile and active modules (includes banking ones only when the user has BaaS enabled); profile_complete and next_question indicate whether the profile still needs completing. --- # Micro-store and orders > MCP tools to build micro-stores with a cart from the catalog and manage the orders they generate. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/microtienda ## What it covers [#que-cubre] From the products in the [catalog](/en/developers/agentes/mcp/catalogo) the agent builds a **micro-store with a cart**: a public page with several products where the customer picks quantities and pays for everything at once. Each micro-store has its own link. The second half of the group is **order management**: it is set up once (store name, optional logo and the time of the daily reminder), and from then on you can see the summary, list orders by status, see the ones paid but not delivered and mark them as delivered. Order statuses are `pending` (awaiting payment), `paid` (paid, not delivered) and `delivered`. Deactivating a micro-store or turning off order management does not delete catalog products or orders already recorded. These tools do not call the Tilopay API: they work on the MCP server's own data, except the payment itself, which goes through each product's payment link. ## Tools [#herramientas] ### Create a micro-store with cart [#tilopay-store-create] - `tilopay_store_create` - Access: Write Creates a micro-store link to share with a customer: it shows the catalog products, the customer builds a cart and pays at the end from the same page with a link that includes the cart detail. By default it includes the whole catalog; it can be limited to certain product IDs. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | — | Visible store title | | `item_ids` | array | — | Catalog product IDs to show; if omitted, the whole catalog is shown | **Returns** `{ slug, url, items }` slug = the micro-store identifier; url = public store link with cart; items = how many catalog products were included. ### List active micro-stores [#tilopay-store-list] - `tilopay_store_list` - Access: Read only Lists the merchant's active micro-stores with their sharing link. **Parameters** No parameters. **Returns** `{ stores[] }` stores = active micro-stores with slug, title, public link and the product ids each one includes. ### Disable a micro-store [#tilopay-store-delete] - `tilopay_store_delete` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Deactivates a micro-store by its slug: the link stops opening. Confirm with the merchant before using it. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `slug` | string | yes | Store slug (the end of its link) | **Returns** `{ slug, active }` Disables the micro-store: the link stops showing products. active is set to false; the catalog products are not deleted. ### Connect a custom domain to the store [#tilopay-store-domain-connect] - `tilopay_store_domain_connect` - Access: Write Connects a merchant's own domain (e.g. tienda.minegocio.com) to their micro-store. It registers it immediately and returns the CNAME instruction. Use it only when the merchant gives the domain. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `hostname` | string | yes | Domain, e.g. tienda.minegocio.com | | `slug` | string | — | Store slug; if omitted, the most recent | **Returns** ### Status of custom domains [#tilopay-store-domain-status] - `tilopay_store_domain_status` - Access: Read only Checks the status of the custom domains of the merchant's stores (via Cloudflare). **Parameters** No parameters. **Returns** `{ domains }` domains = custom domains connected to the merchant and their status. ### Remove a custom domain [#tilopay-store-domain-remove] - `tilopay_store_domain_remove` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Disconnects a custom domain from the store; the store stays at its tilopay.shop address. Confirm with the merchant before using it. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `hostname` | string | yes | Custom domain to disconnect (4–253 characters), connected by that merchant | **Returns** `{ hostname, removed }` hostname = disconnected domain; removed = true if it was removed. ### Turn on or configure order management [#tilopay-orders-setup] - `tilopay_orders_setup` - Access: Write Turns on order tracking for the micro-store and saves the store name, the logo and the time of the daily reminder for paid, undelivered orders. It is also used to change any of that data later. The logo can be an https link to an image or the image the merchant just sent over WhatsApp (pass the reference shown in the message). **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `store_name` | string | — | Visible store name | | `logo` | string | — | https link of the logo image, or the reference of the image sent over WhatsApp | | `reminder_time` | string | — | Local time of the daily reminder in HH:mm format (e.g. "18:00"); empty disables it | **Returns** `{ orders_enabled, store_name, has_logo, reminder_time }` Leaves order management on and returns the resulting configuration: the store name customers see, whether a logo is loaded and the time of the daily pending-orders reminder. ### View store and order settings [#tilopay-orders-settings] - `tilopay_orders_settings` - Access: Read only Shows whether order management is on, the store name, whether there is a logo and the time of the daily reminder. **Parameters** No parameters. **Returns** `{ orders_enabled, store_name, has_logo, reminder_time }` Current store and order configuration, without changing anything. ### Order summary [#tilopay-orders-status] - `tilopay_orders_status` - Access: Read only Summary of the micro-stores' orders: how many are pending payment, how many are paid and undelivered (with their amount) and how many were delivered today. **Parameters** No parameters. **Returns** `{ pending, paid, delivered, ... }` Order summary: how many are pending payment, paid and undelivered, and delivered, with the period's amounts. ### List orders [#tilopay-orders-list] - `tilopay_orders_list` - Access: Read only Lists the micro-stores' orders with their code, customer, total and detail. It can be filtered by status (pending = pending payment, paid = paid and undelivered, delivered = delivered) and by the last days. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `status` | string (pending | paid | delivered | all) | — | Order status; all by default | | `days` | integer | — | Only orders from the last N days | | `limit` | integer | — | 1–100 (25 by default) | **Returns** `{ orders[] }` orders = orders with code, customer, total, currency, status and date. It can be filtered by status (pending, paid, delivered, all) and by the last days. ### Paid, undelivered orders [#tilopay-orders-pending] - `tilopay_orders_pending` - Access: Read only Lists the orders already paid that have not been delivered yet. This is the tool used by the daily reminder. **Parameters** No parameters. **Returns** `{ pending, orders[] }` pending = how many orders are paid and undelivered; orders = those orders with code, customer, total and currency. ### Mark an order as delivered [#tilopay-order-mark-delivered] - `tilopay_order_mark_delivered` - Access: Write Marks one or several orders as delivered. It accepts the order code (e.g. "#A3F2", even several in the same message) or the customer name. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | — | Order code, e.g. "#A3F2"; several can be given | | `client` | string | — | Customer name of the order | **Returns** `{ delivered, missing }` delivered = how many orders were marked as delivered; missing = the codes that were not found. ### Turn off order management [#tilopay-orders-disable] - `tilopay_orders_disable` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Turns off order tracking and cancels the daily reminder. Already registered orders are kept. Ask for an explicit yes before using it. **Parameters** No parameters. **Returns** `{ orders_enabled: false }` Turns off order management. Already registered orders are kept. --- # Alerts and notifications > MCP tools to read and change your WhatsApp alerts: approved charges, declined charges, daily summary and orders. - kind: mcp-tool - status: stable - access: write - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/avisos ## What it covers [#que-cubre] The agent can notify you over WhatsApp when a charge comes in, when a charge is declined, when an order arrives, and send a daily summary at the chosen time. These two tools read and change those settings. They do not send messages on their own: they only turn alerts on and off and report the current settings. Fields that are not sent are preserved. ## Tools [#herramientas] ### View notification settings [#tilopay-get-notification-settings] - `tilopay_get_notification_settings` - Access: Read only Shows how the merchant's notifications are configured: whether payments received are notified over WhatsApp and/or email, which email, from which amount and whether there is a quiet-hours window. **Parameters** No parameters. **Returns** `{ settings }` settings = the merchant's notification configuration: which WhatsApp notifications are on (approved charges, failed charges, daily summary, orders) and what time the daily summary is sent. ### Configure the merchant's notifications [#tilopay-set-notification-settings] - `tilopay_set_notification_settings` - Access: Write Changes the merchant's notification settings: turn on or off the payment-received notification over WhatsApp and email, the destination email, the minimum amount to notify and the quiet-hours window (in the merchant's local time). Only send the fields the user asked to change. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `payment_whatsapp` | boolean | — | Notify over WhatsApp when a payment is received | | `payment_email` | boolean | — | Notify over email when a payment is received | | `notify_email` | string | — | Email that receives the notifications (if omitted, the account's) | | `min_amount` | number | — | Minimum amount to notify (0 = notify for any amount) | | `quiet_from` | integer | null | — | Local time when the quiet window starts (null to remove it) | | `quiet_to` | integer | null | — | Local time when the quiet window ends (null to remove it) | **Returns** `{ settings }` settings = the already updated notification configuration. Fields that are not sent are kept. --- # Payment receipts > MCP tools to list receipts of approved charges and resend a customer the receipt for their payment. - kind: mcp-tool - status: stable - access: write - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/comprobantes ## What it covers [#que-cubre] Every approved charge that goes through the agent produces a receipt with its own code, a page to view it and an image to share. With these two tools you list the receipts for a period and resends one to the customer over WhatsApp. The receipt is an informational document about the charge. It is not an electronic invoice. ## Tools [#herramientas] ### List payment receipts [#tilopay-list-receipts] - `tilopay_list_receipts` - Access: Read only Lists the merchant's latest payment receipts (amount, customer, order and code). Each one includes a text ready to forward to the customer. It does not charge anything. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | — | How many receipts to list (5 by default) | **Returns** `{ receipts[] }` receipts = payment receipts with code, amount, currency, customer, order number, payment date, receipt page link and image link. ### Resend a payment's receipt to the customer [#tilopay-send-receipt] - `tilopay_send_receipt` - Access: Write Sends the customer the receipt of a charge, by WhatsApp (by default: a one-tap wa.me link) or by email with the merchant's branding (channel=email). It searches by code, order number or customer name; without data it uses the most recent receipt. If the user says 'the last charge' or 'the last receipt', call it directly without code or order_number (do not query transactions first). Use send_now only if the user asks for it to be sent directly by WhatsApp. Never make up the customer's email or phone: if they are missing, ask for them. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | — | Receipt code (8 characters) | | `order_number` | string | — | Order number of the charge | | `client_name` | string | — | Name of the charge's customer, to find the receipt | | `channel` | string (whatsapp | email) | — | whatsapp (default) or email | | `email` | string | — | Customer email, if sending by email | | `contact_name` | string | — | Name of the saved contact or of the customer | | `phone` | string | — | WhatsApp phone, e.g. +50688887777 | | `message` | string | — | Your own text to go along with the receipt | | `save_contact` | boolean | — | Save the contact in the address book | | `send_now` | boolean | — | true sends the receipt directly to the customer; by default the wa.me link is delivered | **Returns** `{ sent | code, page_url, image_url, whatsapp_url }` Resends the receipt to the customer over WhatsApp: it returns the receipt code, its page link, the image link and, when not sent directly, the wa.me link ready to open. --- # Reminders > MCP tools to create, list and cancel WhatsApp reminders, with or without an agent action when they fire. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/recordatorios ## What it covers [#que-cubre] A reminder is a scheduled WhatsApp message: one-off, daily, weekly or monthly. It can be plain text or carry an **action** the agent runs when it fires, written in natural language ("give me today's sales summary", "list the pending payment links"), and the result is attached to the message. Rules worth knowing: - Times are your local time, based on the country of their WhatsApp number. - There is a maximum of 20 active reminders. - Reminders about payment links or charges **always** go to your WhatsApp, never to the customer. - To send to another number, save it first as a [contact](/en/developers/agentes/mcp/contactos). ## Tools [#herramientas] ### Create reminder [#tilopay-create-reminder] - `tilopay_create_reminder` - Access: Write Creates a WhatsApp reminder, one-time or recurring (daily, weekly or monthly). It can be text only or run an agent action when sent (with `action`): sales summary, the day's transactions, pending links, etc. It does NOT create banking reminders (balance, movements, statements, transfers): the merchant configures those in the calendar of the Banca Cliente portal. If the reminder is to send a payment link or charge, it ALWAYS goes to the WhatsApp of the merchant who asked for it, never to the customer. In other cases it is sent to the merchant's WhatsApp or to an already saved contact (contact_name); if the user gives a new phone, it must first be saved with tilopay_save_contact. Maximum 20 active reminders. The message goes out with a fixed prefix that identifies the merchant. Times in the merchant's local time (according to the country of their WhatsApp phone). **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `message` | string | yes | Reminder text (max. 500 characters, no HTML) | | `action` | string | — | Instruction the agent will run when sending it, in natural language, e.g. 'give me today's sales summary' or 'list pending payment links'. The result is attached to the message. Omit it for a plain-text reminder. | | `schedule` | string (once | daily | weekly | monthly) | yes | Frequency: once = one time only | | `when` | string | — | Only for once: local date and time 'YYYY-MM-DD HH:mm' | | `time_of_day` | string | — | For recurring ones: local time 'HH:mm' | | `weekday` | integer | — | For weekly: 0 = Sunday ... 6 = Saturday | | `day_of_month` | integer | — | For monthly: day of the month (1-28) | | `contact_name` | string | — | Name of an already saved contact (mcp_contact) who should receive it. If omitted, it reaches the merchant's WhatsApp. New phone numbers are not accepted: save them first with tilopay_save_contact. | **Returns** `{ id, message, action, phone, contact_name, schedule, when, next_run_local, is_active }` The created reminder: when it is sent in the merchant's local time, to which number, and what agent action runs when it is sent. Maximum 20 active reminders; charge-link ones always reach the merchant's WhatsApp, never the customer's. ### View reminders [#tilopay-list-reminders] - `tilopay_list_reminders` - Access: Read only Lists the merchant's active reminders with their next send date. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `include_inactive` | boolean | — | true to include those already sent or cancelled | **Returns** `{ reminders[] }` reminders = the merchant's reminders with their message, frequency, next run in local time and whether they are active. ### Cancel reminder [#tilopay-cancel-reminder] - `tilopay_cancel_reminder` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Cancels a reminder by its ID or by searching part of its text. It does not delete the history, it just stops being sent. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | — | Reminder ID | | `search` | string | — | Part of the reminder's text | **Returns** `{ cancelled }` cancelled = the reminder that was deactivated. --- # Recurring billing > MCP tools to read recurring plans, subscribers and their payments, and to pause, reactivate or delete a subscriber. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/recurrentes ## What it covers [#que-cubre] Four read-only tools over recurring billing plans and their subscribers, plus one sensitive tool that pauses, reactivates or deletes a subscriber. That last one affects future charges: ask for human confirmation before running it. ## Tools [#herramientas] ### List recurring plans [#tilopay-recurring-list-plans] - `tilopay_recurring_list_plans` - Access: Read only - API operation: `POST /api/v1/getPlansRepeat` — https://tilopay.com/en/developers/api/recurrentes/planes/get-plans Lists the recurring billing plans configured in Tilopay. **Parameters** No parameters. **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Recurring plan detail [#tilopay-recurring-get-plan] - `tilopay_recurring_get_plan` - Access: Read only - API operation: `POST /api/v1/getPlanRepeat` — https://tilopay.com/en/developers/api/recurrentes/planes/get-plan Returns the detail of a recurring billing plan by its ID. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Plan ID | | `include_qr` | boolean | — | Include the plan's base64 QR (false by default; use it only if the merchant asks for the QR) | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Subscriber detail [#tilopay-recurring-get-subscriber] - `tilopay_recurring_get_subscriber` - Access: Read only - API operation: `POST /api/v1/getSuscriptorRepeat` — https://tilopay.com/en/developers/api/recurrentes/suscriptores/get-suscriptor-plan Returns the detail of a subscriber of a recurring plan by its ID. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Subscriber ID | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Find a subscriber by name or email [#tilopay-recurring-find-subscriber] - `tilopay_recurring_find_subscriber` - Access: Read only Searches for a subscriber by name or email across all recurring plans at once. Use it before pausing, reactivating or editing someone when you do not have their ID. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | Subscriber name or email | **Returns** `{ result }` result = matching subscribers, each with its plan_id and plan. ### Subscriber payments [#tilopay-recurring-subscriber-payments] - `tilopay_recurring_subscriber_payments` - Access: Read only - API operation: `POST /api/v1/getSuscriptorPayments` — https://tilopay.com/en/developers/api/recurrentes/suscriptores/suscriptor-payments Lists the payments made by a recurring subscriber. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Subscriber ID | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Failed recurring charges [#tilopay-recurring-failed-payments] - `tilopay_recurring_failed_payments` - Access: Read only - API operation: `POST /api/v1/getSuscriptorPayments` — https://tilopay.com/en/developers/api/recurrentes/suscriptores/suscriptor-payments Lists the latest recurring charges that failed (plan, subscriber, amount, date and reason in plain language). A single call is enough. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | — | Maximum charges (10 by default) | | `startDate` | string | — | From, "YYYY-MM-DD HH:mm:ss" | | `endDate` | string | — | To, "YYYY-MM-DD HH:mm:ss" | **Returns** `{ total, source, failures[] }` failures = declined recurring charges, with plan, subscriber, amount, currency and decline reason; total = how many were found; source = the API route they were obtained from. It combines the query of plans, subscribers and their payments. ### Pause, reactivate or delete a subscriber [#tilopay-recurring-manage-subscriber] - `tilopay_recurring_manage_subscriber` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/pauseSuscriptorRepeat, /reactiveSuscriptorRepeat o /deleteSuscriptorRepeat` — https://tilopay.com/en/developers/api/recurrentes/suscriptores/pause-suscriptor-plan Pauses, reactivates or deletes a subscriber of a recurring plan in Tilopay. Sensitive operation: it affects future charges. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `idSubscriber` | string | yes | Subscriber ID | | `action` | string (pause | reactivate | delete) | yes | Action to perform | | `confirmation_code` | string | — | 6-digit code received by email to authorize the operation | **Returns** `{ result }` Raw response of the endpoint matching the action: pause, reactivate or delete. ### Create subscription plan [#tilopay-recurring-create-plan] - `tilopay_recurring_create_plan` - Access: Write - API operation: `POST /api/v1/createPlanRepeat` — https://tilopay.com/en/developers/api/recurrentes/planes/create-plan Creates a recurring billing plan (subscription) in Tilopay with its title, currency, frequency and modalities (name and amount). Returns the ID of the created plan; then use tilopay_recurring_subscription_url to get the link customers use to subscribe. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | yes | Plan title | | `modality` | array | yes | Plan modalities (at least one): name and amount per cycle | | `frequency` | string (diario | semanal | mensual | anual | quincenal | bimestral | trimestral | cuatrimestral | semestral) | yes | Charge frequency | | `description` | string | — | Plan description | | `currency` | string | — | ISO 4217 currency, e.g. USD or CRC. If omitted, the merchant's country currency is used. | | `first_amount` | number | — | Amount of the initial payment (0 if there is none) | | `trial_days` | integer | — | Free trial days (0 if there is no trial) | | `attempts` | integer | — | Retries on a failed charge (1 by default) | | `end_at` | string | — | Plan end date in DD-MM-YYYY format (optional) | | `thanks_url` | string | — | Thank-you URL after subscribing (optional) | | `webhook_subscribe` | string | — | Webhook when a customer subscribes (optional) | | `webhook_payment` | string | — | Webhook when a payment is charged (optional) | | `webhook_rejected` | string | — | Webhook when a charge is declined (optional) | | `webhook_unsubscribe` | string | — | Webhook when a subscription is cancelled (optional) | | `webhook_reactive` | string | — | Webhook when a subscription is reactivated (optional) | **Returns** `{ result, currency, frequency }` result = API response with the created plan; currency and frequency = the currency and frequency the plan ended up with. ### Edit subscription plan [#tilopay-recurring-edit-plan] - `tilopay_recurring_edit_plan` - Access: Write - API operation: `POST /api/v1/editPlanRepeat` — https://tilopay.com/en/developers/api/recurrentes/planes/edit-plan Edits an existing plan; this is not a partial update. title and frequency are required. Omitted fields are sent with defaults: empty description, first_amount 0, no free trial, attempts 1, empty webhooks and thank-you URL; omitted status is sent as 1 (active). The agent should read the plan with tilopay_recurring_get_plan and resend all fields. Optional modality keeps the price when omitted. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Plan ID | | `title` | string | yes | Plan title | | `frequency` | string (diario | semanal | mensual | anual | quincenal | bimestral | trimestral | cuatrimestral | semestral) | yes | Charge frequency | | `modality` | array | — | Optional array of {title: string (maximum 60 characters), amount: number > 0}; omitted keeps the price. | | `currency` | string | — | ISO 4217 currency | | `description` | string | — | Plan description; omitted sends an empty string | | `first_amount` | number | — | Initial payment; omitted sends 0 | | `trial_days` | integer | — | Free trial days; omitted sends no free trial | | `attempts` | integer | — | Retries on a failed charge; omitted sends 1 | | `status` | string (inactive | active | closed_to_new) | — | Status: inactive, active or closed_to_new; omitted sends 1 (active) | | `end_at` | string | — | Plan end date in DD-MM-YYYY format (optional) | | `thanks_url` | string | — | Thank-you URL; omitted sends an empty string | | `webhook_subscribe` | string | — | Webhook on subscribe; omitted sends an empty string | | `webhook_payment` | string | — | Webhook when a payment is charged; omitted sends an empty string | | `webhook_rejected` | string | — | Webhook when a charge is declined; omitted sends an empty string | | `webhook_unsubscribe` | string | — | Webhook on cancellation; omitted sends an empty string | | `webhook_reactive` | string | — | Webhook on reactivation; omitted sends an empty string | **Returns** `{ result }` result = API response editing the plan; omitted fields are sent with defaults, except modality, which keeps the price. ### Delete subscription plan [#tilopay-recurring-delete-plan] - `tilopay_recurring_delete_plan` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/deletePlanRepeat` — https://tilopay.com/en/developers/api/recurrentes/planes/delete-plan Deletes a recurring billing plan in Tilopay. It affects the plan's future charges: confirm with the merchant before running it. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Plan ID | **Returns** `{ result }` result = API response deleting the plan. The plan's subscribers stop being charged. ### Subscription link for a plan [#tilopay-recurring-subscription-url] - `tilopay_recurring_subscription_url` - Access: Read only - API operation: `POST /api/v1/recurrentUrl` — https://tilopay.com/en/developers/api/recurrentes/suscriptores/consult-url Returns the link a customer uses to sign up for a recurring plan (or renew it if the email is already subscribed). Deliver the full URL, without shortening it. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Plan ID | | `email` | string | — | Customer email; if already subscribed it returns the renewal link | **Returns** `{ url, result }` url = subscription link for the plan, to share with the customer; result = the raw API response. ### Edit a plan's subscriber [#tilopay-recurring-edit-subscriber] - `tilopay_recurring_edit_subscriber` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/editSuscriptorRepeat` — https://tilopay.com/en/developers/api/recurrentes/suscriptores/edit-suscriptor-plan Changes subscriber status (active, paused or deleted), expire (YYYY-MM-DD), modality (modality ID), amount (greater than 0) and planId; only present fields are sent. If planId is missing, it is looked up. Owners and employees can use it. No email code is required; tilopay_recurring_manage_subscriber provides second-factor pause, reactivation or deletion. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Subscriber ID | | `status` | string (active | paused | deleted) | — | New subscriber status | | `expire` | string | — | Plan expiration date for that subscriber in YYYY-MM-DD format | | `modality` | string | — | Modality ID | | `amount` | number | — | Amount greater than 0 | | `planId` | string | — | Plan ID; looked up if missing | **Returns** `{ result }` result = API response editing the present subscriber fields. ### Create discount coupon [#tilopay-recurring-create-coupon] - `tilopay_recurring_create_coupon` - Access: Write - API operation: `POST /api/v1/createCoupon` — https://tilopay.com/en/developers/api/recurrentes/cupones/create-coupon Creates a discount coupon for a subscription plan: percentage or fixed amount, expiration date, allowed emails and usage limits. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `planId` | string | yes | Recurring plan ID | | `discount_type` | string (percentage | fixed) | yes | Discount type: percentage or fixed | | `discount` | number | yes | Discount value | | `expire` | string | yes | Coupon expiration date in YYYY-MM-DD format | | `allow_existing_users` | boolean | — | true if already registered customers can also use it (false = new customers only) | | `emails` | array | — | Authorized emails; if omitted, any email can use it | | `usage` | integer | — | Total number of uses | | `renews` | integer | — | Renewals valid with the coupon | | `renews_by_user` | integer | — | Uses per single customer | **Returns** `{ result }` result = API response with the discount coupon created for the plan. ### A plan's coupons [#tilopay-recurring-list-coupons] - `tilopay_recurring_list_coupons` - Access: Read only - API operation: `POST /api/v1/getRepeatCoupons` — https://tilopay.com/en/developers/api/recurrentes/cupones/get-coupons Lists the discount coupons associated with a subscription plan. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `planId` | string | yes | Recurring plan ID | **Returns** `{ result }` result = the plan's coupons as returned by the API. ### Coupon detail [#tilopay-recurring-get-coupon] - `tilopay_recurring_get_coupon` - Access: Read only - API operation: `POST /api/v1/getCoupon` — https://tilopay.com/en/developers/api/recurrentes/cupones/get-coupon Returns the detail of a discount coupon by its ID. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Coupon ID | **Returns** `{ result }` result = coupon detail as returned by the API. ### Delete a coupon [#tilopay-recurring-delete-coupon] - `tilopay_recurring_delete_coupon` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/deleteCoupon` — https://tilopay.com/en/developers/api/recurrentes/cupones/delete-coupon Deletes a discount coupon of a subscription plan. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Coupon ID | **Returns** `{ result }` result = API response deleting the coupon. ### Start step by step for a subscription plan [#tilopay-recurring-plan-wizard-start] - `tilopay_recurring_plan_wizard_start` - Access: Write Starts the guided step-by-step to create (mode=create) or edit (mode=edit with plan_id) a subscription plan. Returns the first pending question: deliver it to the merchant as is and pass their answer to tilopay_recurring_plan_wizard_answer. Use it instead of tilopay_recurring_create_plan when some data is missing. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `mode` | string (create | edit) | yes | create for a new plan, edit to modify an existing one | | `plan_id` | string | — | Plan ID (required in mode=edit) | | `title` | string | — | Title, if the merchant already gave it | | `frequency` | string (diario | semanal | quincenal | mensual | bimestral | trimestral | cuatrimestral | semestral | anual) | — | Frequency, if the merchant already gave it | | `amount` | number | — | Amount per cycle, if the merchant already gave it | | `currency` | string | — | ISO currency if the merchant indicated it with a symbol or name ($ or dollars = USD, ₡ or colones = CRC) | | `description` | string | — | Description, if already given | **Returns** `{ draft }` draft = the plan draft in progress, with what has already been answered and the next pending step. It creates nothing in Tilopay until confirmed. ### Answer a step of the subscription plan [#tilopay-recurring-plan-wizard-answer] - `tilopay_recurring_plan_wizard_answer` - Access: Write Saves the merchant's answer to the current step of the subscription plan and returns the next question or the summary to confirm. Deliver the returned text as is, without adding questions of your own. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `answer` | string | yes | What the merchant answered, as is | **Returns** `{ draft }` draft = the updated draft with the step's answer and the next pending step. ### Confirm the subscription plan [#tilopay-recurring-plan-wizard-confirm] - `tilopay_recurring_plan_wizard_confirm` - Access: Write - API operation: `POST /api/v1/createPlanRepeat` — https://tilopay.com/en/developers/api/recurrentes/planes/create-plan Creates or edits the subscription plan with the step-by-step data, after the merchant said yes to the summary. When creating, it also returns the subscription link: deliver it in full, without shortening it. **Parameters** No parameters. **Returns** `{ plan_id, subscription_url }` Creates the plan with what was built step by step: plan_id = id of the created plan; subscription_url = subscription link to share. ### Cancel the plan step by step [#tilopay-recurring-plan-wizard-cancel] - `tilopay_recurring_plan_wizard_cancel` - Access: Write Discards the subscription plan that was in progress without creating or changing anything. **Parameters** No parameters. **Returns** Discards the plan draft in progress. It creates or changes nothing in Tilopay; it only returns the confirmation in text. ### Payment link to settle a failed recurring charge [#tilopay-recurring-settle-link] - `tilopay_recurring_settle_link` - Access: Write - API operation: `POST /api/v1/createLinkPayment` — https://tilopay.com/en/developers/api/links-de-pago/create-link-payment Creates a payment link for the amount of a recurring charge that failed and associates it with the plan's subscriber. When the customer pays that link, the subscription is settled automatically: the subscriber is reactivated and its expiration date moves to the next cycle. Use it when the merchant agrees to send the link to the customer of a failed charge (the data comes from tilopay_recurring_failed_payments). Returns the link URL and, if there is a phone number, a WhatsApp link ready to send to the customer. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `subscriber_id` | string | yes | ID of the subscriber with the failed charge | | `amount` | number | yes | Outstanding amount of the failed charge | | `currency` | string | — | Currency of the charge. If omitted, the merchant's country currency is used. | | `plan_id` | string | — | Subscription plan ID | | `plan_name` | string | — | Plan name, for the charge's concept | | `frequency` | string (diario | semanal | quincenal | mensual | bimestral | trimestral | cuatrimestral | semestral | anual) | — | Plan frequency; if omitted it is queried from the plan or assumed monthly | | `client_name` | string | — | Customer name | | `client_email` | string | — | Customer email | | `client_phone` | string | — | Customer phone in international format, for the WhatsApp link | | `message` | string | — | Your own message for the customer; if omitted one is drafted | **Returns** `{ url, linkId, whatsapp_url, currency, amount, subscriber_id, frequency }` Creates a one-time payment link for the subscriber to settle a failed recurring charge: url = payment link; whatsapp_url = WhatsApp link ready to send it; linkId = id of the created link. ### Links to bring several failed charges up to date [#tilopay-recurring-settle-batch] - `tilopay_recurring_settle_batch` - Access: Write Batch sending (maximum 20) of links to bring failed recurring charges up to date. First call it WITHOUT confirming: it returns the summary (count, total, who has no phone) and a fingerprint. Show that summary AS IS and ask for an explicit yes. Only if the merchant answers yes, call it again with confirm=true and the SAME fingerprint. Use it when they ask to 'send it to everyone'; for a single customer use tilopay_recurring_settle_link. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `confirm` | boolean | — | true only after the explicit yes to the summary | | `fingerprint` | string | — | Fingerprint returned by the summary | | `subscriber_ids` | array | — | Limit to these subscribers; if omitted, all pending ones (max. 20) | **Returns** `{ ok, preview, fingerprint, count } | { ok, created, count }` Preview: { ok, preview: true, fingerprint, count }. Execution (with confirm: true and the same fingerprint): { ok, created, count }. ### Settle a subscriber [#tilopay-recurring-mark-settled] - `tilopay_recurring_mark_settled` - Access: Write - API operation: `POST /api/v1/editSuscriptorRepeat` — https://tilopay.com/en/developers/api/recurrentes/suscriptores/edit-suscriptor-plan Marks the subscriber as up to date on their plan: it leaves them active and moves their expiration date to the next cycle. Use it when the pending charge was already paid by other means. If the payment was made with the tilopay_recurring_settle_link link, it is not needed: it updates by itself. Confirm with the merchant before running it. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `subscriber_id` | string | yes | Subscriber ID | | `plan_id` | string | — | Subscription plan ID | | `frequency` | string (diario | semanal | quincenal | mensual | bimestral | trimestral | cuatrimestral | semestral | anual) | — | Plan frequency; if omitted it is queried from the plan or assumed monthly | | `expire` | string | — | New expiration date YYYY-MM-DD; if omitted the next cycle is calculated | **Returns** `{ ok, expire, frequency }` Moves the subscriber's due date forward according to the plan's frequency, to settle them after recovering a failed charge. expire = the new expiration date. --- # Stored cards and bulk collections > MCP tools to store a customer card, charge it, remove it and work with bulk collection groups. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/tarjetas-guardadas ## What it covers [#que-cubre] This group works with the cards your customers already authorised to store (tokenization) and with bulk collections against those cards. How storing works: the agent generates a **secure link** and shares it with the customer; the customer types their card there. Neither the agent, nor the model, nor you see the full number: the read tools return the brand and the last digits. The sensitive operations in this group — charging a stored card, removing a card and creating bulk collections — require a **confirmation code** sent to your email. [Saved card payments API reference](/en/developers/api/cobros-tarjetas-guardadas). ## Tools [#herramientas] ### List collection groups [#tilopay-saved-cards-list-groups] - `tilopay_saved_cards_list_groups` - Access: Read only - API operation: `POST /api/v1/collect/get/groups` — https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-groups Lists the collection groups with stored cards (affiliates) in Tilopay. **Parameters** No parameters. **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### List affiliates with a stored card [#tilopay-saved-cards-list-affiliates] - `tilopay_saved_cards_list_affiliates` - Access: Read only - API operation: `POST /api/v1/collect/get/affiliates` — https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-affiliates Lists the affiliates (customers with a stored card) of a collection group. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `group` | integer | — | Group ID (0 for all) | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### List bulk collections [#tilopay-saved-cards-list-collections] - `tilopay_saved_cards_list_collections` - Access: Read only - API operation: `POST /api/v1/collect/get/massive` — https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-massive Lists the bulk collections made with stored cards in Tilopay. **Parameters** No parameters. **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Bulk collection detail [#tilopay-saved-cards-collection-detail] - `tilopay_saved_cards_collection_detail` - Access: Read only - API operation: `POST /api/v1/collect/get/massive/detail` — https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/get-massive-detail Returns the detail of a bulk collection. It requires the collection `code` (the `code` field returned by tilopay_saved_cards_list_collections). **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | yes | Bulk collection code (the `code` field of the collections list) | **Returns** `{ result }` Raw Tilopay API response under the `result` key. ### Save a customer's card (tokenize) [#tilopay-tokenize-card] - `tilopay_tokenize_card` - Access: Write - API operation: `POST /api/v1/processTokenize` — https://tilopay.com/en/developers/api/cof/process-tokenize Generates the secure Tilopay link where the customer enters their card to have it saved (tokenized). The merchant never sees or receives the card data. Once saved, it can be charged in bulk with tilopay_saved_cards_create_payments or individually with tilopay_charge_saved_card (both ask for a confirmation code by email). If a contact or phone is given, it also returns a WhatsApp link with the message ready to send to the customer. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | Cardholder's email | | `first_name` | string | yes | Cardholder's first name | | `last_name` | string | yes | Cardholder's last name | | `language` | string (es | en) | — | Form language (es by default) | | `redirect_url` | string | — | URL the customer returns to when finished (optional) | | `contact_name` | string | — | Name of the saved contact or of the customer, to draft the WhatsApp message | | `phone` | string | — | Customer's WhatsApp phone, e.g. +50688887777 | | `message` | string | — | Your own text for the message to the customer | | `save_contact` | boolean | — | Save the contact in the address book | **Returns** `{ tokenize_url, whatsapp_url, email, contact }` tokenize_url = secure link where the customer enters their card to save it; whatsapp_url = WhatsApp link ready to send it. The card is typed by the customer: the agent never receives the number. ### List a customer's saved cards [#tilopay-list-customer-cards] - `tilopay_list_customer_cards` - Access: Read only Lists the cards a customer already has stored (tokenized) in Tilopay, identified by the customer's email. Returns the token of each card, which is what tilopay_charge_saved_card needs to charge it. Only cards stored with the link of this assistant or of the MCP can be charged. It does not charge anything. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | Email of the customer who owns the cards | | `currency` | string | — | Reference currency, e.g. CRC or USD. If omitted, the merchant's country currency is used. | **Returns** `{ cards, count }` cards = that customer's already saved cards, with brand, last digits and their identifier to charge; count = how many there are. It never returns the full card number. ### Available payment methods and installments [#tilopay-payment-methods] - `tilopay_payment_methods` - Access: Read only Lists the payment methods enabled for the merchant in the Tilopay checkout (cards, SINPE Móvil, Yappy, installments or zero rate when active) and whether the account responds in production or test. Use it when the merchant asks what they can offer their customer or whether they have installments / zero rate. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `amount` | number | — | Reference amount; some methods and installments depend on the amount | | `currency` | string | — | Reference currency. If omitted, the merchant's country currency is used. | | `email` | string | — | Customer email (optional; only to also see their saved cards) | **Returns** `{ methods, installments, cards, environment, currency }` methods = payment methods enabled for the merchant; installments = installments available when the method allows them; cards = customer's saved cards if their email was sent; environment = production or test. ### Charge a customer's saved card [#tilopay-charge-saved-card] - `tilopay_charge_saved_card` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/processRecurrentPayment` — https://tilopay.com/en/developers/api/cof/realizar-cobro Charges an amount to a customer's already saved (tokenized) card. Requires the customer's email and the card token (obtained with tilopay_list_customer_cards). Sensitive operation: it moves real money and requires a second factor. Call it first without `confirmation_code`: a 6-digit code is sent to the merchant's email and the response carries `requires_confirmation`. Repeat the same call, with the same parameters, adding `confirmation_code`. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | Cardholder's email | | `card` | string | yes | Token of the saved card (v2 version) | | `amount` | number | yes | Amount to charge | | `currency` | string | — | Currency of the charge. If omitted, the merchant's country currency is used. | | `order_number` | string | — | Order number; if omitted a unique one is generated | | `capture` | boolean | — | Shown in the authorization email but currently not sent in the charge. | | `confirmation_code` | string | — | 6-digit code received by email to authorize the operation | **Returns** `{ result, approved, orderNumber, currency, receipt_code, receipt_url, receipt_image_url }` Charges an already saved card of the customer: approved = whether the charge was approved; orderNumber = generated order number; receipt_* = the charge's receipt when generated. Requires a second factor by email. ### Delete a customer's saved card [#tilopay-remove-saved-card] - `tilopay_remove_saved_card` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/user/card-remove` — https://tilopay.com/en/developers/api/cof/remove-card Deletes a customer's saved card (token) in Tilopay. Sensitive operation: requires a second factor. Call it first without `confirmation_code` and repeat it later with the 6-digit code sent to the merchant's email. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | Email of the customer who owns the card | | `token` | string | yes | Token of the card to delete | | `confirmation_code` | string | — | 6-digit code received by email to authorize the operation | **Returns** `{ result }` result = API response deleting the customer's saved card. Requires a second factor by email. ### Charge stored cards [#tilopay-saved-cards-create-payments] - `tilopay_saved_cards_create_payments` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/v1/collect/set/payments` — https://tilopay.com/en/developers/api/cobros-tarjetas-guardadas/set-payments Creates charges to affiliates and/or groups with stored cards in Tilopay (owner only; requires the confirmation code sent by email). Only affiliates whose cards were saved with tilopay_tokenize_card can be charged; in a group, all its members must qualify. No idempotency: repeating the operation with a new code charges again. currency and capture appear in the authorization email but are currently not sent in the charge. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string | yes | Reason for the charge, e.g. "Monthly fee" | | `capture` | boolean | — | Shown in the authorization email but currently not sent in the charge. | | `users` | array | — | Array of { id: string, amount: number > 0, currency: string, date?: "YYYY-MM-DD" }; date schedules the charge; currency is shown in the authorization email but is not sent in the charge | | `groups` | array | — | Array of { id: string, amount: number > 0 (amount per affiliate), currency: string, date?: "YYYY-MM-DD" }; currency is shown in the authorization email but is not sent in the charge | | `confirmation_code` | string | — | 6-digit code received by email to authorize the operation | **Returns** `{ result }` Raw Tilopay API response under the `result` key. --- # Settlements, deposits and billing > MCP tools to query the next settlement, the settlement history, security deposits and the invoices Tilopay issues to you. - kind: mcp-tool - status: stable - access: write - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/liquidaciones ## What it covers [#que-cubre] This group covers the money Tilopay settles to you: the pending balance and the date of the next deposit, already processed settlements with their transactions, security deposits (pre-authorizations held on customers) and the invoices Tilopay issues for using the platform. Everything is read-only except extending a security deposit, which requires a six-digit code sent by email. ## Tools [#herramientas] ### Next settlement and balance to settle [#tilopay-settlement-balance] - `tilopay_settlement_balance` - Access: Read only OFFICIAL Tilopay data: date of the next deposit (next_settlement), settlement frequency, destination IBAN account and, per currency, the accumulated pending balance with its breakdown (subtotal, commission, iva_commission, retention, retention_iva, cost, iva_tilopay, transfer, transfer_iva, net total). **Parameters** No parameters. **Returns** `{ next_settlement, commerce, accumulated_balance, accumulated_balance_details }` next_settlement = next settlement; commerce = merchant data with the masked IBAN; accumulated_balance = [{ total, currency }]; accumulated_balance_details = the detail. ### Settlement history [#tilopay-settlements-list] - `tilopay_settlements_list` - Access: Read only Settlements already processed by Tilopay: deposit date (date_to_report), period, gross amount, deductions and total transferred. Use it for 'did they deposit Friday's money yet?' or 'how much did they deposit last month?'. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | — | Page (1 by default) | | `limit` | integer | — | Records per page (20 by default) | | `search` | string | — | Text to search | **Returns** ### Transactions of a settlement [#tilopay-settlement-transactions] - `tilopay_settlement_transactions` - Access: Read only Transactions included in an already processed settlement (id obtained from tilopay_settlements_list). **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `settlement_id` | string | yes | Settlement id | **Returns** ### Security deposits (pre-authorizations) [#tilopay-deposits-list] - `tilopay_deposits_list` - Access: Read only Security deposits or pre-authorizations held on customers (car rental, hospitality, security deposits): amount, status and expiration. Not to be confused with settlements. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | — | Page (1 by default) | | `limit` | integer | — | Records per page (20 by default) | | `search` | string | — | Text to search | **Returns** ### Detail of a security deposit [#tilopay-deposit-detail] - `tilopay_deposit_detail` - Access: Read only Detail of a security deposit (id from tilopay_deposits_list). **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `deposit_id` | string | yes | Deposit id | **Returns** ### Extend a security deposit [#tilopay-deposit-extend] - `tilopay_deposit_extend` - Access: Write Extends a security deposit authorization, owner only. Requires a 6-digit email code, but confirmation_code is not declared in the schema: the two-call flow is completed through the WhatsApp Assistant. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `deposit_id` | string | yes | Deposit id | **Returns** After authorization, the extension data returned by Tilopay. ### Tilopay service invoices [#tilopay-billing-list] - `tilopay_billing_list` - Access: Read only Invoices Tilopay issues to the merchant for using the platform (they are not the merchant's sales). Use it for 'how much did Tilopay charge me this month?' or 'where is my Tilopay invoice?'. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | — | Page (1 by default) | | `limit` | integer | — | Records per page (20 by default) | | `search` | string | — | Text to search | **Returns** --- # Account, merchant and team > MCP tools to link or unlink the account, choose the active merchant, manage the assistant's employees and query dashboard users and enabled payment methods. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/cuenta ## What it covers [#que-cubre] This group covers the Tilopay account linked to the assistant: linking or unlinking it, choosing the active merchant when there are several, giving or removing employee access, and querying the web dashboard users and the enabled payment methods and currencies. Unlinking the account and removing an employee's access are marked as sensitive and ask for explicit confirmation before running. ## Tools [#herramientas] ### Selected merchant [#tilopay-switch-commerce] - `tilopay_switch_commerce` - Access: Write Shows the active merchant of the linked Tilopay account and lets you switch it when the account has several. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `commerce_id` | string | — | Merchant to activate. If omitted, only the current one is shown. | **Returns** `{ selected, selected_name, commerces }` selected = active merchant id; selected_name = its name; commerces = [{ id, name }] available. ### Unlink my account [#tilopay-unlink-account] - `tilopay_unlink_account` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Unlinks the Tilopay account from the assistant: revokes ALL access (WhatsApp, Claude and other applications), deletes the stored keys (including the bank ones) and closes the banking sessions. Before calling it with confirm=true, repeat that scope to the merchant and ask for confirmation. Use it when the merchant asks to unlink, disconnect or remove their account from the assistant. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `confirm` | boolean | yes | Must be true: the merchant confirmed they want to unlink the account | | `all_channels` | boolean | — | true only if the merchant expressly asked to also unlink the other channel (WhatsApp and MCP clients). | **Returns** `{ unlinked, message }` unlinked = true when the account was unlinked. ### Link my account [#tilopay-send-link-flow] - `tilopay_send_link_flow` - Access: Write Sends the merchant, on WhatsApp, the button to link (or relink) the Tilopay account with the assistant (only in the WhatsApp assistant). Use it for any request to link, connect or activate the assistant with the Tilopay account. **Parameters** No parameters. **Returns** ### Give an employee access [#tilopay-invite-employee] - `tilopay_invite_employee` - Access: Write Invites an employee to use the assistant with their own Tilopay user (owner only). Asks for the employee's name and WhatsApp phone. Returns a wa.me link for the owner to send the invitation. In your answer, first repeat the `prerequisito` line verbatim and then deliver the link. The employee can query sales, create charges and send receipts; they cannot refund, charge stored cards, create or delete products, change prices or change the configuration. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Employee name | | `phone` | string | yes | Employee's WhatsApp, e.g. 8888-8888 | **Returns** `{ prerequisito, invite_id, expires_at, whatsapp_url }` prerequisito = the line the agent must repeat: the employee needs their own Tilopay user created at admin.tilopay.com › Usuarios; the invitation expires in 72 h. ### View my employees [#tilopay-list-employees] - `tilopay_list_employees` - Access: Read only Lists the employees with access to the assistant and the pending invitations (owner only). **Parameters** No parameters. **Returns** `{ employees }` employees = [{ name, phone, status }]; the phone is masked and status is "activo" or "invitado". ### Remove an employee's access [#tilopay-remove-employee] - `tilopay_remove_employee` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Removes an employee's access to the assistant (owner only): revokes their whole session, or cancels the invitation if they had not linked yet. Before calling it with confirm=true, confirm with the owner who is being removed. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | — | Employee name | | `phone` | string | — | Employee phone | | `confirm` | boolean | yes | true: the owner confirmed removing their access | **Returns** `{ removed }` removed = employees whose access was removed. ### Enabled payment methods and currencies [#tilopay-accepted-payment-methods] - `tilopay_accepted_payment_methods` - Access: Read only Payment methods the merchant has enabled in Tilopay (card brands, SINPE, etc.), configured currencies and PayFac configuration. Use it for 'which cards do I accept?', 'is SINPE active?', 'can I charge in dollars?'. **Parameters** No parameters. **Returns** `{ methods, currencies, payfac }` methods = accepted payment methods; currencies = currencies; payfac = PayFac configuration. ### Tilopay dashboard users [#tilopay-panel-users] - `tilopay_panel_users` - Access: Read only Users with access to the merchant's Tilopay web dashboard and their modules. Different from the assistant's employees (tilopay_list_employees). **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | — | Page (1 by default) | | `limit` | integer | — | Records per page (20 by default) | | `search` | string | — | Text to search | **Returns** --- # Support and diagnostics > MCP tools to answer with the official guides and test the API connection. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/soporte ## What it covers [#que-cubre] Three read tools: one answers dashboard usage questions with the official guides at tilopay.com/guias, another tests the credentials and one endpoint of each API group to know which part responds when something fails, and the third analyses a code snippet or stack trace you pass it and returns a diagnosis, root cause, fix and risks. That last one does not read repositories and does not run code. ## Tools [#herramientas] ### Tilopay connection diagnostics [#tilopay-diagnostics] - `tilopay_diagnostics` - Access: Read only Verifies the user credentials and tests one endpoint of each group (sales and transactions, catalog, recurring, stored cards). Useful when a tool fails, to know which part of the API is responding. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `environment` | string (production | test) | — | Environment for the transactions test (production by default) | **Returns** `{ checks, failed }` checks is a list with check, ok and detail for each test: credentials, transactions, catalog, recurring and stored cards. failed is the number of failing tests. Credentials come back masked. ### Help from Tilopay guides and tutorials [#tilopay-help-guides] - `tilopay_help_guides` - Access: Read only Answers questions about using the Tilopay dashboard with the official guides at tilopay.com/guias. It returns excerpts and the links to those guides. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `question` | string | yes | The merchant's question, as written | **Returns** `{ articles }` Up to 3 articles with title, url and excerpt, taken from tilopay.com/guias. With no match it returns an empty articles list and a text with the available guides. ### Debug code [#tilopay-debug-code] - `tilopay_debug_code` - Access: Read only Analyses a code snippet or stack trace sent by the user and returns a diagnosis, root cause, a concrete fix and risks. It does not read repositories and does not run code. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | yes | Code snippet, log or stack trace as the user sent it | | `error` | string | — | Error message or observed behaviour, if any | | `language` | string | — | Language or stack (e.g. typescript, php, react). Detected when not given | | `context` | string | — | What the code should do or when it fails | **Returns** `{ report, truncated, redacted, model }` report is the diagnosis in text (diagnosis, root cause, fix and risks). truncated says whether the code was cut down to 12,000 characters; redacted, whether possible credentials were hidden before the analysis; model, the model used. It does not read repositories and does not run the code. --- # Banking tools > MCP tools to read accounts, balances, payments and statements, and manage transfers with email authorization. Enabled on request and only with banking API credentials. - kind: mcp-tool - status: stable - access: write - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario > **Warning:** These tools are not enabled by default. They are turned on on request and only work for > users with Tilopay banking API credentials registered by Tilopay on the server side. Standard > MCP access does not include the group; and if a tool is called without those credentials, it > replies that they do not exist. To request it, [contact us](/en/contacto) and state that you > need the banking API. ## What it covers [#que-cubre] Nineteen tools over the [banking API](/en/developers/api-bancario): contexts (assignments), credential diagnostics, accounts, balances, full account number, payments, statements, an analytical payments report, validating a destination account, the transfer with its authorization, cancellation and result notice, dual-control approval and closing the banking session. **Thirteen of the nineteen only read information.** `baas_request_statement` generates a read-only document and moves no money. `baas_send_transfer` creates the intent of a transfer, without executing the payment. The Banca Cliente portal sends an authorization code to your email and `baas_confirm_transfer` executes the transfer with that code. If the operation is held under dual control, the approver decides it with `banca_decide_approval` and a code sent to their email. So it cannot run on the model's decision alone. The own account is chosen by precedence: `accountValue` (only if it matches one of the user's accounts) → `currency` (first account in that currency) → `accountIndex` (base 1: the number shown by `baas_list_accounts`) → if there is a single account, that one. `baas_get_account_number` accepts only `currency` or `accountIndex`. Dates are RFC 3339 (`2026-01-01T00:00:00Z`) and `assignmentId` is only needed when the user has several contexts. ## Tools [#herramientas] The email authorization and dual-control flow described here belongs to MCP and Banca Cliente. Direct payment creation in the [Banking API](/en/developers/api-bancario/pagos#procesamiento) has no dual control: it processes the payment when the token, permissions and data are valid. Statements are generated asynchronously: the API returns `202 ACCEPTED` with `request_id` and `PENDING`. The status lookup returns `200` with `PENDING`, `PROCESSING`, `DONE` or `ERROR`; when `DONE`, `download_url` is valid for **48 hours**. > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - [Other BaaS operations](https://tilopay.com/en/developers/agentes/mcp/api-bancario/otras-operaciones) — `baas_list_assignments`, `baas_diagnostics`, `baas_list_accounts`, `baas_get_balance`, `baas_get_account_number`, `baas_get_statement_status`, `baas_cancel_transfer`, `banca_decide_approval`, `baas_close_session` (Read only / Read only / Read only / Read only / Read only / Read only / Write / Sensitive / Write): Assignments, credential diagnostics, account listing, balance of one account, full account number, status of a statement request, cancelling the pending transfer, dual-control approval and closing the banking session. - [Banking API account balances](https://tilopay.com/en/developers/agentes/mcp/api-bancario/list-balances) — `baas_list_balances` (Read only): Lists the real-time balances of the accessible accounts: account, amounts and cut-off date. - [List banking API payments](https://tilopay.com/en/developers/agentes/mcp/api-bancario/list-payments) — `baas_list_payments` (Read only): Queries (read-only) the movements/payments of one of the merchant's own bank accounts. Indicate the currency (CRC/USD) or the account number from the list: do not write the IBAN. Filters by date, status, currency, method and direction, with cursor pagination. - [Find a payment](https://tilopay.com/en/developers/agentes/mcp/api-bancario/search-payment) — `baas_search_payment` (Read only): Searches for a BaaS payment within an account by exactly one of: paymentId (UUID), publicId (number) or clientReference. Identifiers only from data a tool already returned. - [Payment detail](https://tilopay.com/en/developers/agentes/mcp/api-bancario/get-payment) — `baas_get_payment` (Read only): Gets the detail of a BaaS payment by its internal UUID (read-only). Use only identifiers that another tool returned in this same turn. - [Request an account statement](https://tilopay.com/en/developers/agentes/mcp/api-bancario/request-statement) — `baas_request_statement` (Write): Requests asynchronous generation of an account statement for a banking API account, with a 60-day maximum range. It moves no money: it generates a read-only document. The API always returns 202 ACCEPTED with request_id and status PENDING. Check baas_get_statement_status for the download; when DONE, download_url is valid for 48 hours. - [Banking API payments analyst](https://tilopay.com/en/developers/agentes/mcp/api-bancario/analyze-payments) — `baas_analyze_payments` (Read only): Analyses, read only, the payments of an account in a date range and returns a natural-language report: success, failures, reversals, net amounts and recommendations. It runs a language model over the computed figures. - [Validate BaaS destination account](https://tilopay.com/en/developers/agentes/mcp/api-bancario/validate-destination) — `baas_validate_destination` (Read only): Validates a destination account before transferring: PHONE for SINPE Móvil or IBAN for account transfer. Returns the holder name the bank confirms. - [Send BaaS transfer](https://tilopay.com/en/developers/agentes/mcp/api-bancario/send-transfer) — `baas_send_transfer` (Sensitive): Creates the intent of a transfer from the merchant's bank account in the Banca Cliente portal (not the payment yet). The portal validates the destination and sends a 6-digit code to the merchant's email; the payment only executes when baas_confirm_transfer is called with that code (valid for 30 minutes). Default method: SINPE Móvil (destination by phone); for an account transfer, destinationAccountType=IBAN. baas_validate_destination is optional to show the holder beforehand; this tool validates the destination on its own. - [BaaS transfer status](https://tilopay.com/en/developers/agentes/mcp/api-bancario/get-transfer-status) — `baas_get_transfer_status` (Read only): Does not query the bank: it states that the transfer result arrives as a notice as soon as the bank confirms it. paymentId is optional and unused. - [Authorize the pending transfer](https://tilopay.com/en/developers/agentes/mcp/api-bancario/confirm-transfer) — `baas_confirm_transfer` (Sensitive): Executes the transfer whose intent was created by baas_send_transfer, with the 6-digit code the Banca Cliente portal sent to the merchant's email. Never write or repeat the code in the answer. --- # Banking API account balances > Lists the real-time balances of the accessible accounts: account, amounts and cut-off date. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/list-balances > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_list_balances` - Access: Read only - API operation: `GET /api/public/v1/accounts/balances` — https://tilopay.com/en/developers/api-bancario/cuentas Lists the real-time balances of the accessible accounts: account, amounts and cut-off date. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `accountType` | string | — | Filter by scheme (IBAN) | | `accountValue` | string | — | Filter by identifier | | `limit` | integer | — | Page size, between 1 and 100 | | `offset` | integer | — | Pagination offset | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | **Returns** `{ result }` result = the banking API response as returned. --- # List banking API payments > Reads your account's payments: valid accountValue → currency → accountIndex (1-based) → sole account. Date, status, currency, method and direction filters; cursor pagination. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/list-payments > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_list_payments` - Access: Read only - API operation: `GET /api/public/v1/transactions/payments` — https://tilopay.com/en/developers/api-bancario/pagos Queries (read-only) the movements/payments of one of the merchant's own bank accounts. Indicate the currency (CRC/USD) or the account number from the list: do not write the IBAN. Filters by date, status, currency, method and direction, with cursor pagination. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `currency` | string | — | ISO 4217 currency (CRC, USD) | | `accountIndex` | integer | — | Account number in the list shown by baas_list_accounts (base 1). Precedence for choosing the own account: accountValue (only if it matches one of the user's accounts) → currency (first account in that currency) → accountIndex (base 1: the number baas_list_accounts shows) → if there is a single account, that one. | | `accountValue` | string | — | Only if already returned by a tool and matching one of your accounts. Account precedence: accountValue → currency (first matching account) → accountIndex (1-based) → sole account. | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | | `dateFrom` | string | — | From, RFC 3339 (2026-01-01T00:00:00Z) | | `dateTo` | string | — | To, RFC 3339 | | `status` | string (pending | processing | confirmed | posted | failed) | — | Public payment status | | `paymentMethodCode` | string (PIN | SINPE_MOVIL) | — | Payment method | | `direction` | string (OUT | IN) | — | OUT = sent, IN = received | | `clientReference` | string | — | Merchant reference | | `limit` | integer | — | Page size, 100 maximum (20 by default) | | `cursor` | string | — | Cursor from the previous page | **Returns** `{ result }` result = the banking API response as returned, with its pagination cursor. --- # Find a payment > Finds a payment inside an account by exactly one of: paymentId (UUID), publicId (number) or clientReference. Sending more than one returns an error. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/search-payment > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_search_payment` - Access: Read only - API operation: `POST /api/public/v1/transactions/payments/search` — https://tilopay.com/en/developers/api-bancario/pagos Searches for a BaaS payment within an account by exactly one of: paymentId (UUID), publicId (number) or clientReference. Identifiers only from data a tool already returned. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `currency` | string | — | Currency of the account to query (CRC or USD). Recommended form. | | `accountIndex` | integer | — | Account number in the user's account list | | `accountValue` | string | — | Only if already returned by a tool and matching one of your accounts. Account precedence: accountValue → currency (first matching account) → accountIndex (1-based) → sole account. | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | | `paymentId` | string | — | Internal payment UUID | | `publicId` | string | — | Numeric public identifier | | `clientReference` | string | — | Merchant reference | **Returns** `{ result }` result = the banking API response as returned. --- # Payment detail > Gets the detail of a banking API payment from its internal UUID, read only. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/get-payment > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_get_payment` - Access: Read only - API operation: `GET /api/public/v1/transactions/payments/{payment_id}` — https://tilopay.com/en/developers/api-bancario/pagos Gets the detail of a BaaS payment by its internal UUID (read-only). Use only identifiers that another tool returned in this same turn. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `paymentId` | string | yes | Internal payment UUID | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | **Returns** `{ result }` result = the banking API response as returned. --- # Request an account statement > Requests the generation of an account statement for a banking API account, with a 60-day maximum range. It moves no money: it generates a read-only document and returns request_id with status PENDING. - kind: mcp-tool - status: stable - access: write - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/request-statement > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_request_statement` - Access: Write - API operation: `POST /api/public/v1/accounts/statements` — https://tilopay.com/en/developers/api-bancario/cuentas Requests asynchronous generation of an account statement for a banking API account, with a 60-day maximum range. It moves no money: it generates a read-only document. The API always returns 202 ACCEPTED with request_id and status PENDING. Check baas_get_statement_status for the download; when DONE, download_url is valid for 48 hours. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `currency` | string | — | Account currency (CRC or USD) | | `accountIndex` | integer | — | Account number in the list | | `accountValue` | string | — | Only if already returned by a tool and matching one of your accounts. Account precedence: accountValue → currency (first matching account) → accountIndex (1-based) → sole account. | | `dateFrom` | string | yes | From, RFC 3339 (2026-01-01T00:00:00Z) | | `dateTo` | string | yes | To, RFC 3339, at most 60 days after dateFrom | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | **Returns** `{ result }` result = the banking API response with request_id and status. The tool requests the document without email notification. ## Generation and download [#generacion] The Banking API always returns `202 ACCEPTED` with `request_id` and `status: PENDING`; the document is generated asynchronously. Check its status with `baas_get_statement_status`: the API returns `200` with `PENDING`, `PROCESSING`, `DONE` or `ERROR`. `DONE` includes `download_url`, valid for **48 hours**. --- # Banking API payments analyst > Analyses, read only, the payments of an account in a date range and returns a natural-language report: success, failures, reversals, net amounts and recommendations. It runs a language model over the computed figures. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/analyze-payments > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_analyze_payments` - Access: Read only Analyses, read only, the payments of an account in a date range and returns a natural-language report: success, failures, reversals, net amounts and recommendations. It runs a language model over the computed figures. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `accountIndex` | integer | — | Account number in the list shown by baas_list_accounts (base 1). Precedence for choosing the own account: accountValue (only if it matches one of the user's accounts) → currency (first account in that currency) → accountIndex (base 1: the number baas_list_accounts shows) → if there is a single account, that one. | | `accountValue` | string | — | Only if already returned by a tool and matching one of your accounts. Account precedence: accountValue → currency (first matching account) → accountIndex (1-based) → sole account. | | `dateFrom` | string | yes | From, RFC 3339 | | `dateTo` | string | yes | To, RFC 3339 | | `currency` | string | — | ISO 4217 currency | | `direction` | string (OUT | IN) | — | OUT = sent, IN = received | | `question` | string | — | Specific focus for the analysis | | `maxItems` | integer | — | Maximum payments to analyse, between 1 and 1000 (300 by default) | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | **Returns** `{ summary }` summary carries account, range, totals {count, succeeded, failed, reversed, pending, successRate}, byCurrency with succeededAmount, reversedAmount, netAmount and averageTicket, failureReasons and sample. The written report comes in the text. With no payments in the range it says so and does not analyse. --- # Validate a banking API destination account > Checks a destination account before sending a transfer to it: holder, account type, currency and status. It does not move money. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-07 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/validate-destination > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_validate_destination` - Access: Read only - API operation: `POST /api/public/v1/accounts/validate` — https://tilopay.com/en/developers/api-bancario/cuentas Validates a destination account before transferring: PHONE for SINPE Móvil or IBAN for account transfer. Returns the holder name the bank confirms. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `accountType` | string (PHONE | IBAN) | yes | Identifier scheme: PHONE for SINPE Móvil, IBAN for account. | | `accountValue` | string | yes | E.164 phone number or IBAN | | `assignmentId` | string | — | Banking API context (optional) | **Returns** `{ result }` result = data the banking API returns about the destination account (holder, account type, currency and status) before sending a transfer. It does not move money. --- # Send a banking API transfer > Creates the intent of a transfer from one of the merchant bank accounts in the Banca Cliente portal; the payment only executes when confirmed with the code sent to the merchant email. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/send-transfer > **Warning:** This tool moves real money. It does not send the payment yet: it creates the intent in the > Banca Cliente portal, which validates the destination and sends a 6-digit code to the > merchant's email (valid for 30 minutes). The payment only executes when calling > [baas_confirm_transfer](/en/developers/agentes/mcp/api-bancario/confirm-transfer) with that code. > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_send_transfer` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones - API operation: `POST /api/public/v1/transactions/payments` — https://tilopay.com/en/developers/api-bancario/pagos Creates the intent of a transfer from the merchant's bank account in the Banca Cliente portal (not the payment yet). The portal validates the destination and sends a 6-digit code to the merchant's email; the payment only executes when baas_confirm_transfer is called with that code (valid for 30 minutes). Default method: SINPE Móvil (destination by phone); for an account transfer, destinationAccountType=IBAN. baas_validate_destination is optional to show the holder beforehand; this tool validates the destination on its own. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `amount` | number | yes | Amount to transfer | | `currency` | string | yes | ISO 4217 currency (CRC, USD) | | `contactName` | string | — | Name of the saved contact (alternative to destinationAccountValue; one of the two is required) | | `destinationAccountType` | string (PHONE | IBAN) | — | PHONE = SINPE Móvil (default), IBAN = account transfer | | `destinationAccountValue` | string | — | Phone: +50688887777, 506 8888-7777 or 88887777 are accepted and normalized to 8 digits; IBAN in uppercase without spaces. Alternative to contactName: one of the two is required. | | `destinationName` | string | — | Recipient name (if not validated via contact) | | `detailReference` | string | — | Transfer concept (minimum 15 characters) | | `assignmentId` | string | — | Not used in this flow | | `allowDuplicate` | boolean | — | Only true when the user literally confirmed they want to repeat a recent identical transfer (e.g. 'yes, it is another one'). Never enabled on the agent's own initiative. | **Returns** `{ requires_transfer_code: true, channel: "email", via: "portal", resumen: { beneficiario_nombre, beneficiario_confirmado_por_banco, beneficiario_identificacion_enmascarada, entidad_financiera, destino_tipo, destino_legible, monto, moneda, concepto, origen, datos_no_informados[] }, requires_approval, message }` It does not return an intentId: the intent stays stored on the server. Special responses: requires_duplicate_confirmation (same destination, amount and currency within 120 s; repeat with allowDuplicate: true only if the user literally confirmed it) and requires_open_intent_resolution (there is already an open intent: confirm it with baas_confirm_transfer or cancel it with baas_cancel_transfer). --- # Confirm the transfer > Executes the transfer whose intent was created by baas_send_transfer, with the 6-digit code the Banca Cliente portal sent to the merchant's email. - kind: mcp-tool - status: stable - access: destructive - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/confirm-transfer > **Warning:** This tool moves real money. It executes the transfer that > [baas_send_transfer](/en/developers/agentes/mcp/api-bancario/send-transfer) left as an intent, > using the 6-digit code the Banca Cliente portal sent to the merchant's email (valid for 30 > minutes). Never write or repeat the code in the response. The final result is not in this response: it arrives later as a notice from the bank (transfer completed or failed); there is no need to poll for it. If the account has dual control, the response stays at `awaiting_approval` and the approver decides with `banca_decide_approval`. > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_confirm_transfer` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones Executes the transfer whose intent was created by baas_send_transfer, with the 6-digit code the Banca Cliente portal sent to the merchant's email. Never write or repeat the code in the answer. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | yes | 6-digit code the Banca Cliente portal sent to the merchant's email | | `intentId` | string | — | Intent identifier (optional; if missing, the user's open intent is used) | **Returns** `{ status, via, message }` status = "enviada" or "awaiting_approval"; via = "portal". The final result arrives later as a notice from the bank (transfer completed or failed); there is no need to poll for it. --- # Banking API transfer status > Checks the current status of a transfer by its payment_id: status, result, amount, currency, destination and date. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/get-transfer-status > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. - `baas_get_transfer_status` - Access: Read only Does not query the bank: it states that the transfer result arrives as a notice as soon as the bank confirms it. paymentId is optional and unused. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `paymentId` | string | — | Internal payment UUID | **Returns** `{ message }` message = text explaining that the result arrives as a notice from the bank. --- # Other BaaS operations > Assignments, credential diagnostics, account listing, balance of one account, full account number, status of a statement request, cancelling the pending transfer, dual-control approval and closing the banking session, on a single page. - kind: mcp-tool - status: stable - access: read - last_verified: 2026-10-22 - url: https://tilopay.com/en/developers/agentes/mcp/api-bancario/otras-operaciones > These tools are enabled on request and only work for users with Tilopay banking API credentials. They are not part of the default MCP access: Tilopay enables the group case by case. Requires: Tilopay banking API credentials registered by Tilopay for that user. ### Banking API contexts (assignments) [#baas-list-assignments] - `baas_list_assignments` - Access: Read only Lists the tenant/account contexts (assignments) available for the user's banking API credentials. Use it when another tool asks for assignmentId. **Parameters** No parameters. **Returns** `{ assignments[] }` assignments = available contexts with assignment_id, tenant_code, owner_type, country_code and status. ### Banking API diagnostics [#baas-diagnostics] - `baas_diagnostics` - Access: Read only Checks the user's banking API credentials, the login, the token exchange and access to accounts. Use it when a query fails. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | **Returns** `{ checks[], failed }` checks = one row per check (credentials, login + assignments, accounts) with ok and detail; failed = how many failed. The credential email comes back masked. ### List banking API accounts [#baas-list-accounts] - `baas_list_accounts` - Access: Read only - API operation: `GET /api/public/v1/accounts` — https://tilopay.com/en/developers/api-bancario/cuentas Lists the accessible banking API accounts, without balances: identifier type, value and currency. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `accountType` | string | — | Filter by scheme (IBAN) | | `accountValue` | string | — | Filter by identifier | | `limit` | integer | — | Page size, between 1 and 100 | | `offset` | integer | — | Pagination offset | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | **Returns** `{ result }` result = the banking API response as returned. ### Balance of one account [#baas-get-balance] - `baas_get_balance` - Access: Read only - API operation: `POST /api/public/v1/accounts/balance` — https://tilopay.com/en/developers/api-bancario/cuentas Gets the current balance of a banking API account. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `currency` | string | — | Account currency (CRC or USD): first account in that currency. Precedence for choosing the own account: accountValue (only if it matches one of the user's accounts) → currency (first account in that currency) → accountIndex (base 1: the number baas_list_accounts shows) → if there is a single account, that one. | | `accountIndex` | integer | — | Account number in the list shown by baas_list_accounts (base 1). Precedence for choosing the own account: accountValue (only if it matches one of the user's accounts) → currency (first account in that currency) → accountIndex (base 1: the number baas_list_accounts shows) → if there is a single account, that one. | | `accountValue` | string | — | Only if a tool already returned it and it matches one of the user's accounts; do not write account numbers from memory. | | `assignmentId` | string | — | Banking API context (optional) | **Returns** `{ account: { value, currency }, amounts: { available, posted }, as_of }` account = the queried account; amounts.available = available balance; amounts.posted = posted balance; as_of = cutoff date. ### Statement request status [#baas-get-statement-status] - `baas_get_statement_status` - Access: Read only - API operation: `GET /api/public/v1/accounts/statements/{request_id}` — https://tilopay.com/en/developers/api-bancario/cuentas Reads the status of an asynchronous account statement request. The API returns 200 with status PENDING, PROCESSING, DONE or ERROR. When DONE, it includes download_url, valid for 48 hours, and its expiry. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `requestId` | string | yes | request_id returned when the statement was requested | | `assignmentId` | string | — | Banking API context (assignment); only needed when there are several | **Returns** `{ result }` result = the banking API response as returned. ### Merchant's full account number [#baas-get-account-number] - `baas_get_account_number` - Access: Read only Shows the FULL IBAN of one of the merchant's own accounts. Use it ONLY when the merchant explicitly asks for their account number (for example to give it to a customer or supplier) and with confirm=true. It is recorded in the audit log. In any other answer the masked number is used. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `confirm` | boolean | yes | true only if the merchant explicitly asked for their full account number | | `currency` | string | — | Account currency (CRC or USD) | | `accountIndex` | integer | — | Account number in the list | | `assignmentId` | string | — | Banking API context (optional) | **Returns** `{ result: { account_id, currency, iban } }` result = the own account with its full IBAN. ### Cancel the pending transfer [#baas-cancel-transfer] - `baas_cancel_transfer` - Access: Write Cancels the transfer that is waiting for the authorization code. Use it when the merchant says they do not want to make it. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `intentId` | string | — | Intent identifier (optional) | **Returns** `{ message }` message = cancellation confirmation. ### Approve or reject a pending operation [#banca-decide-approval] - `banca_decide_approval` - Access: Sensitive - Requires confirmation: https://tilopay.com/en/developers/agentes/mcp/permisos#confirmaciones The dual-control approver approves or rejects a pending transfer, with the 6-digit code that arrived at their email. Never write the code in the answer. **Parameters** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `decision` | string (aprobar | rechazar) | yes | The approver's decision | | `code` | string | yes | 6-digit code from the approver's email | | `approvalId` | string | — | Approval identifier (optional; if missing, the most recent pending approval is used) | | `reason` | string | — | Rejection reason (max. 255 characters, optional) | **Returns** `{ status, message }` status = result of the decision; message = confirmation text. ### Close banking session [#baas-close-session] - `baas_close_session` - Access: Write Immediately closes the active banking session (only in the WhatsApp assistant). **Parameters** No parameters. **Returns** ## Document status and download [#estado-documento] `baas_get_statement_status` checks an asynchronously created request (`202 ACCEPTED`, `request_id`, `PENDING`). The API returns `200` with `PENDING`, `PROCESSING`, `DONE` or `ERROR`; `DONE` includes `download_url`, valid for **48 hours**. --- # Machine-readable documentation > The artifacts the portal publishes for agents: llms.txt, raw Markdown on any URL, openapi.json and mcp.json. - kind: guide - status: stable - last_verified: 2026-10-20 - url: https://tilopay.com/en/developers/agentes/documentacion-legible ## Indexes for models [#llms] - [tilopay.com/llms.txt](https://tilopay.com/llms.txt) — Spanish site index, with the URL and a description of every page. - [tilopay.com/llms-full.txt](https://tilopay.com/llms-full.txt) — the same index with the full content. - [tilopay.com/en/llms.txt](https://tilopay.com/en/llms.txt) and [tilopay.com/en/llms-full.txt](https://tilopay.com/en/llms-full.txt) — the English versions, with `/en` URLs. ## Raw Markdown [#markdown] Any URL on the site returns its Markdown by adding `.md`: for example [tilopay.com/en/developers/sdk/instalacion.md](https://tilopay.com/en/developers/sdk/instalacion.md) or [tilopay.com/en/tarifas.md](https://tilopay.com/en/tarifas.md). It is served as `text/markdown`, with a metadata header (page kind, status, version and last verification date). A URL that does not exist returns 404, not HTML. ## API spec [#openapi] [Saved card payments](/en/developers/api/cobros-tarjetas-guardadas) includes the 10 public collect operations in the acquiring OpenAPI. - [`openapi.json`](/developers/openapi.json) — OpenAPI 3.1 spec of the Tilopay API: endpoints, parameters, responses and examples. It is the source of truth for the API. - [`openapi.yaml`](/developers/openapi.yaml) — the same content in YAML. - [`api-bancario/openapi.json`](/developers/api-bancario/openapi.json) — OpenAPI 3.0.3 spec of the banking API (BaaS), a separate artifact from the acquiring API. - [`api-bancario/openapi.yaml`](/developers/api-bancario/openapi.yaml) — the same content in YAML. Both routes answer with `Access-Control-Allow-Origin: *`, so they can be read from the browser. ## MCP catalog [#mcp-json] [`mcp.json`](/developers/mcp.json) describes the MCP server: its URL, the access model and the full tool catalog with parameters, output and access level. When a tool wraps an API operation, the entry includes that endpoint. The catalog is in Spanish: tool descriptions are written for the WhatsApp assistant and published as-is. The index of installable skills is at [`/developers/skills/index.md`](/developers/skills/index.md). The root-level `verified_at` field carries the date (`YYYY-MM-DD`) on which the catalog was compared against the server. The server version does not change when tools are added, so that is the signal to check whether a cached copy is stale. ---