# REST API

> Every endpoint with its fields, the payment object, errors and payment statuses.

Base URL: https://talopartnerhub.vercel.app. Auth: `Authorization: Bearer tph_…`. JSON in and out; every answer is a JSON object at the top level (no wrapper). OpenAPI with schemas: https://talopartnerhub.vercel.app/openapi.json.

## The payment object

Returned by create, get and list (`{ payments: [...] }`), and inside webhook events as `data.payment`:

| Field | Type | |
|---|---|---|
| `id` | string | Talo's payment id, e.g. `PAR-talo-hub-<external_id>` |
| `status` | string | PENDING, SUCCESS, OVERPAID, UNDERPAID, EXPIRED |
| `external_id` | string | Your order id |
| `amount` | number | ARS asked for |
| `currency` | string | `ARS` |
| `cvu`, `alias` | string | Where the buyer transfers |
| `paid`, `refunded` | number | ARS received and refunded; 0 when none |
| `refunds` | array | `{ id, status: PENDING\|SUCCESS\|FAILED, type: FULL\|PARTIAL, amount: number, currency, created_at }` |
| `expires_at`, `created_at` | string | ISO 8601 (created_at may be missing on create) |
| `motive` | string | The concept you sent |
| `payment_url` | string | Talo's hosted page for the payment, when Talo provides one (`redirect_url` is where it sends the buyer back) |
| `checkout_token` | string | Only in the answers to create and get (not in lists or webhook events): a read-only token (`ct_…`) for this payment's public view, valid 15 days from when it was issued. Safe in the browser, for the widget; see [checkout](https://talopartnerhub.vercel.app/docs/checkout.md) |

Rules:
- `amount`: ARS, greater than 0, up to 2 decimals.
- `external_id`: up to 100 characters; unique per account (sandbox and production are different accounts). Creating a payment with an external_id that already has one returns **that existing payment**, whatever its amount or status. So after EXPIRED, create the new payment with a **new** external_id (e.g. `order-1001-2`).
- Statuses only move forward with transfers: PENDING → UNDERPAID → SUCCESS/OVERPAID, or → EXPIRED. Reading a payment also brings its events up to date.

## Polling

There is no published rate limit. Poll a payment every 3 to 10 seconds while a buyer is on the page, and stop when it is SUCCESS, OVERPAID or EXPIRED. Talo credits sandbox transfers in about 10 to 25 seconds. From the browser, poll the public view with the payment's checkout_token (`GET /api/public/payments/{id}?token=…`), never with your key; the `<talo-checkout>` widget does it for you ([checkout](https://talopartnerhub.vercel.app/docs/checkout.md)).

## Errors

`{ "error": { "code", "message", "details"? } }`

| Code | HTTP | Meaning |
|---|---|---|
| unauthorized | 401 | Missing, invalid or revoked key |
| insufficient_scope | 403 | The key's scope doesn't allow it (read < charge < full) |
| sandbox_only | 403 | Only with a tph_test_ key |
| payment_not_found | 404 | No payment with that id in this account |
| not_found | 404 | Unknown webhook endpoint or key id; on the public payment view, no payment for that checkout_token (bad, expired or another payment's: it doesn't say which) |
| link_not_found | 404 | No payment link with that id in this account |
| link_disabled | 410 | The payment link was already disabled |
| invalid_amount, invalid_field, invalid_date, invalid_json, invalid_url, invalid_scope, invalid_cursor, invalid_title, invalid_description, invalid_redirect_url | 422 | The request is malformed; the message says which field |
| talo_rejected | 400/404/409/422 | Talo refused it; its reason is in details.talo_message (e.g. refund.already.pending) |
| talo_reconnect_required | 409 | Talo no longer accepts the account's authorization: the person runs `npx talo-hub login` (plus `--production` for a tph_live_ key) or re-authenticates the MCP server |
| talo_partner_unsupported | 501 | Talo doesn't offer balance and movements to partner integrations yet; payments always work |
| talo_unavailable | 502 | Talo didn't answer properly or in time. It may have done it anyway: read the payment before retrying (creating a payment again with the same external_id is safe); then retry with backoff |
| not_configured | 503 | The hub is not set up for that environment yet |

A new account Talo hasn't activated yet (status PENDING, up to 36 h) can't create CVUs: payments fail with talo_rejected until it is ACTIVE.

## Payment statuses

- PENDING: Esperando la transferencia.
- SUCCESS: Pagado exacto.
- OVERPAID: Pagó de más.
- UNDERPAID: Pagó de menos: no entregues, pedí el resto.
- EXPIRED: Venció sin pagarse.

## Endpoints

### GET /api/v1/account (scope read)

Cuenta Talo conectada: estado de activación y, cuando Talo los informa, prefijo de alias y vencimiento de pagos.

### PATCH /api/v1/account (scope full)

Configura la cuenta. Solo cambian los campos enviados.

- `alias_prefix` (body, string): Hasta 8 caracteres: minúsculas, números y guiones bajos.
- `cancellation_period_minutes` (body, number): Vencimiento de los pagos nuevos, de 5 a 20160 minutos.
- `automatic_cancellation` (body, boolean): Cancela solos los pagos vencidos.

### DELETE /api/v1/account (scope full)

Desconecta la cuenta del hub, todo junto: borra la copia de la autorización de Talo, revoca todas las keys, desactiva los webhooks y cierra las sesiones del dashboard. Los pagos y la plata siguen en Talo, y el hub conserva su registro de pagos y eventos pasados. Para volver, autorizá de nuevo (npx talo-hub login). Es para la persona dueña de la cuenta: un agente no debería llamarlo sin que se lo pida.

### POST /api/v1/payments (scope charge)

Crea un pago por transferencia y devuelve el CVU y el alias para pagarlo. Repetir el mismo external_id devuelve el pago existente: los reintentos nunca cobran dos veces. La respuesta trae payment_url (la página de Talo del pago) y checkout_token, un token de solo lectura de ese pago para el widget (ver Checkout).

- `amount` (body, number, required): Monto en ARS.
- `external_id` (body, string): Tu ID de orden. Hace seguros los reintentos.
- `motive` (body, string): Concepto que ve el comprador.
- `redirect_url` (body, string): Adónde vuelve el comprador desde la página de pago de Talo.
- `client_data` (body, object): first_name, last_name, email, phone, dni, cuit.

### GET /api/v1/payments (scope read)

Pagos de un período (por defecto, los últimos 30 días). Talo no les deja listar pagos a las integraciones de partners: entonces el hub devuelve los que se crearon a través de él, con el último estado que registró, y `source: "hub"` (con `source: "talo"`, la lista viene de Talo). GET /api/v1/payments/{id} trae el detalle completo.

- `from` (query, string): Fecha ISO 8601.
- `to` (query, string): Fecha ISO 8601.

### GET /api/v1/payments/{id} (scope read)

Estado, montos pagados y reembolsados, y reembolsos de un pago. paid y refunded son números (0 si no hay). refunds: [{ id, status: PENDING|SUCCESS|FAILED, type: FULL|PARTIAL, amount, currency, created_at }]. Leer un pago también pone al día sus eventos y trae un checkout_token nuevo (15 días).

### PUT /api/v1/payments/{id}/price (scope full)

Cambia el monto. Talo recalcula el estado de un pago ya pagado.

- `amount` (body, number, required): Nuevo monto en ARS.

### PUT /api/v1/payments/{id}/expiration (scope full)

Extiende el vencimiento de un pago PENDING.

- `expires_at` (body, string, required): Fecha ISO 8601.

### POST /api/v1/payments/{id}/refunds (scope full)

Reembolsa un pago, total o parcial. Asincrónico: empieza PENDING.

- `amount` (body, number): ARS para un reembolso parcial. Sin amount, reembolso total.

### POST /api/v1/payment-links (scope charge)

Crea un link de pago reutilizable (/l/<id>): cada comprador que toca Pagar recibe un pago nuevo y va a la página de Talo con el CVU y el alias. Mandá amount (precio fijo) o min_amount y max_amount (el comprador escribe el monto). Devuelve url y qr_url. Sus pagos llevan external_id link_<id>_<al azar> (el id tiene 12 letras y números: external_id.slice(5, 17)) y motive = title, y el pedido se entrega con el webhook firmado, como cualquier pago. Hasta 20 pagos por hora desde una misma red (IP) por link, y un freno de 200 por link cada 10 minutos; quien vuelve al link recupera el pago que ya había empezado (también si transfirió de menos), y eso no cuenta.

- `title` (body, string, required): Qué se paga, de 1 a 80 caracteres. Lo ve el comprador y es el motive de cada pago.
- `description` (body, string): Hasta 280 caracteres.
- `amount` (body, number): Precio fijo en ARS: positivo, hasta 2 decimales y 100.000.000.
- `min_amount` (body, number): Con max_amount y sin amount: el comprador escribe el monto, dentro de ese rango.
- `max_amount` (body, number): El monto más alto que puede escribir el comprador.
- `redirect_url` (body, string): URL https adonde la página de Talo devuelve al comprador (http solo para localhost).

### GET /api/v1/payment-links (scope read)

Tus links de pago, del más nuevo al más viejo (hasta 100), con los desactivados (active: false).

### GET /api/v1/payment-links/{id} (scope read)

Un link de pago y los pagos que se crearon con él: payments [{ id, status, amount, external_id, created_at }], del más nuevo al más viejo.

### DELETE /api/v1/payment-links/{id} (scope charge)

Desactiva un link de pago para siempre. Los pagos que ya empezaron con él se pueden seguir pagando. No se puede reactivar: creá otro. Un link que ya estaba desactivado responde 410 link_disabled.

### GET /api/public/payments/{id} (no key: the payment's checkout_token)

La vista pública de un pago, para el widget o tu propia página: estado, monto, pagado, CVU, alias, vencimiento y payment_url. Sin API key: la autoriza el checkout_token del pago (lo devuelven POST /api/v1/payments y GET /api/v1/payments/{id}), que solo sirve para leer ese pago y vence a los 15 días. Cualquier origen puede llamarla (CORS *) y el CDN guarda cada respuesta 3 segundos. Un token inválido, vencido o de otro pago, una cuenta desconectada y un pago que no existe responden lo mismo: 404 not_found.

- `token` (query, string, required): El checkout_token del pago (ct_…).

### POST /api/v1/sandbox/transfers (scope charge, sandbox only)

Simula la transferencia del comprador a un pago de sandbox. Con menos monto probás UNDERPAID; con más, OVERPAID. Dispara los mismos eventos que una transferencia real.

- `payment_id` (body, string, required): El pago a pagar.
- `amount` (body, number): ARS. Por defecto, el monto del pago.

### GET /api/v1/balance (scope read)

Saldo de la cuenta. Talo todavía no lo habilita para integraciones de partner: hoy responde talo_partner_unsupported (501).

### GET /api/v1/transactions (scope read)

Transferencias entrantes y salientes, paginadas. Talo todavía no lo habilita para integraciones de partner: hoy responde talo_partner_unsupported (501). El estado de cada cobro siempre está en /api/v1/payments.

- `from` (query, string): Fecha ISO 8601.
- `to` (query, string): Fecha ISO 8601.
- `limit` (query, number): Hasta 200.
- `cursor` (query, string): El next_cursor de la página anterior.

### GET /api/v1/events (scope read)

Eventos de la cuenta, para escuchar sin exponer un servidor. Con after: los posteriores a ese ID, del más viejo al más nuevo (seguí con next_after). Con recent: los últimos n, del más nuevo al más viejo.

- `after` (query, number): Devuelve los eventos posteriores a este ID.
- `recent` (query, number): Los últimos n eventos (1 a 200).
- `limit` (query, number): Hasta 200.
- `payment_id` (query, string): Solo los eventos de ese cobro.
- `type` (query, string): Solo ese tipo, p. ej. payment.paid.

### GET /api/v1/webhook-endpoints (scope read)

Tus webhooks.

### POST /api/v1/webhook-endpoints (scope full)

Registra un webhook https. Devuelve el secreto de firma una sola vez.

- `url` (body, string, required): URL https que recibe los eventos.

### DELETE /api/v1/webhook-endpoints/{id} (scope full)

Da de baja un webhook.

### POST /api/v1/webhook-endpoints/{id}/test (scope charge)

Manda ya un evento webhook.test firmado y dice si tu endpoint respondió 2xx. No mueve plata: sirve en sandbox y en producción para verificar tu handler.

### GET /api/v1/readiness (scope read)

Checklist para pasar a producción: qué está hecho y cómo resolver lo que falta.

### GET /api/v1/api-keys (scope read)

Tus API keys (solo el prefijo).

### POST /api/v1/api-keys (scope full)

Crea una API key. Se muestra una sola vez. Solo con una key full (o desde el dashboard). La nueva puede tener cualquier scope y es charge si no pedís otro.

- `name` (body, string): Para reconocerla después.
- `scope` (body, string): read, charge o full (por defecto charge).

### DELETE /api/v1/api-keys/{id} (scope full)

Revoca una API key.

---
Human page: https://talopartnerhub.vercel.app/docs/api · All pages: https://talopartnerhub.vercel.app/docs/index.md
