PaymentLink
POST /v1/payment_links — crear un cobro
Alcance: payment_links:write. Idempotencia: requerida.
| campo | tipo | requerido | constraint |
|---|---|---|---|
amount | integer | sí | unidad más pequeña de la moneda; > 0 |
currency | string | sí | BRL | USD |
reference | string | no | ≤ 255 chars; su clave de conciliación |
payment_methods | array<string> | no | subconjunto de ["pix","card"]; default ["pix"] |
max_installments | integer | no | 1–21; default 12; solo si card está habilitado |
fee_handling | string | no | absorb | pass_through; default absorb |
expires_in | integer | no | segundos hasta expirar; >= 0 (0 = sin expiración) |
campaign | boolean | no | default false; true mantiene el link abierto tras cada pago y cotiza FX por sesión |
description | string | no | ≤ 500; nombre del producto mostrado en el checkout |
product_ref | string | no | ≤ 255; SKU/referencia de producto (mostrado) |
details | string | no | ≤ 500; descripción larga (mostrada) |
product_images | array<string> | no | ≤ 5; imágenes del producto |
collect_fields | array<string> | no | subconjunto de ["phone","address"]; si se omite, hereda la config del tenant; recolecta esos campos del pagador en el checkout |
metadata | object | no | ≤ 20 claves; ver Metadata |
El checkout_url lo genera la plataforma: pay.pgz.link por defecto, y su propia dirección cuando tiene la marca de su tienda configurada. Use siempre el checkout_url devuelto, nunca arme la URL — el host puede cambiar sin aviso y un link armado a mano no abre.
amount + currency son la única denominación que usted informa. target_currency, charge_currency y merchant_payout_currency son derivados por la plataforma y devueltos en la respuesta — nunca son input. La cobertura cambiaria — el margen sobre el tipo de cambio que protege contra la variación entre la cotización y la liquidación — se aplica siempre que hay conversión, independientemente de la moneda de denominación del link.
campaign=false crea un link de cobro único: tras el primer pago capturado/liquidado, se rechazan nuevos intentos. campaign=true crea un link de campaña: acepta múltiples pagos, cada sesión bloquea el tipo de cambio mostrado por 900 segundos, y pagos diferentes pueden usar tipos de cambio diferentes.
Response 201 — objeto PaymentLink:
{
"id": "pl_9f8c...", "mode": "live",
"status": "active", "checkout_url": "https://pay.pgz.link/pay/pl_9f8c...",
"amount": 250000, "currency": "BRL", "reference": "pedido-8842",
"target_currency": "BRL", "charge_currency": "BRL", "merchant_payout_currency": "USD",
"payment_methods": ["pix"], "max_installments": 1, "fee_handling": "absorb",
"expires_at": 1790000000, "description": "Rolex Submariner", "product_ref": "126610LN",
"details": null, "product_images": [], "collect_fields": [], "metadata": {}, "created": 1789990000
}Errores: unknown_parameter (400), invalid_request (400), missing_idempotency_key (400), invalid_idempotency_key (400), idempotency_key_reuse (409), invalid_api_key (401), insufficient_scope (403), merchant_net_not_positive (400 — ver Protección del link).
GET /v1/payment_links/{id}
Alcance: payment_links:read. Response 200: objeto PaymentLink. Un enlace inexistente, de otro merchant o modo devuelve 404 payment_link_not_found.
GET /v1/payment_links
Alcance: payment_links:read. Query: limit, starting_after, status, reference. Response 200: { data, has_more }. Tamaño por defecto 20, máximo 100; valores mayores devuelven 400 invalid_request. Orden por creación e ID, ambos descendentes. El cursor pertenece al mismo merchant/modo. reference usa igualdad exacta; el estado filtra active, expired, paid o suspended antes de limitar la página, incluidos vencimiento y pago de enlaces únicos. campaign indica reutilización. Consultar no fija precio ni cambio ni inicia un pago.
POST /v1/distributed_payment_links/preview y /drafts — preparar una distribución
Alcance: payment_links:write. La idempotencia es obligatoria. La vista previa estima el importe por método de pago y número de cuotas; el precio se recalcula al iniciar el pago. /drafts guarda solo un borrador suspended. Su checkout_url no puede aceptar pagos antes de la activación explícita descrita abajo. Producción sigue bloqueada hasta completar los controles financieros y humanos del ADR 0056. No comparta la URL de un borrador suspendido con el pagador.
Body: amount_cents (entero positivo en la moneda nominal), currency (BRL o USD), payment_methods (pix, card, ted, habilitados para el Merchant), fee_handling (absorb o pass_through), max_installments (1–21 para tarjeta), description opcional y rules (1–100 partes). Cada regla contiene part_id, beneficiary_id, role (recipient o merchant_reserve) y exactamente uno de amount_cents o percent_bps. La parte necesita una cuenta Merchant o una invitación de beneficiario confirmada. El resto del neto pertenece al Merchant; la reserva voluntaria usa el ID del propio Merchant.
payout_currency opcional (BRL o USD) elige la moneda de recibimiento. Los importes fijos y porcentajes permanecen en la moneda nominal currency, después de tarifas; no representan un neto fijo en la moneda destino. Referencia: ADR 0068. Para monedas distintas, la API asigna primero y devuelve conversion en cada parte con principal, bruto, tarifa y neto estimados, spot USD/BRL completo y política del Merchant/modo/dirección. Política ausente o desactivada: 409 conversion_policy_unavailable; cotización inválida: 409 conversion_rate_unavailable. Cotización, tarifa o neto enviados por el cliente se rechazan. No se cobran procesamiento ni comisión nuevamente. conversion_required: true identifica partes del split en monedas diferentes. El replay conserva la estimación original sin garantizar esa cotización al pagar. Si no se ha instalado la integración financiera, la respuesta incluye también activation_blocker: "mixed_currency_settlement_pending" y se rechaza la activación. Preparar un borrador nunca crea un pago o saldo.
El panel Merchant utiliza el mismo contrato en /merchant/v1/distributed-links/preview y /merchant/v1/distributed-links/drafts, con una sesión Merchant.
El borrador exige Idempotency-Key también en el panel. Enlace, plan y registro de repetición son atómicos: la misma clave y contenido devuelven el mismo id; contenido diferente devuelve 409 idempotency_conflict. Campos opcionales: product_ref, details, media_urls (hasta 5), expires_in (0 = sin vencimiento), campaign y collect_fields (phone, address). La vista previa repetida conserva su validez original; no representa cotización nueva ni autorización de pago.
POST /v1/distributed_payment_links/{id}/activate — solo sandbox
Alcance: payment_links:write. Idempotency-Key obligatoria. Body estricto: { "confirm": true, "conversion_policy_ids": ["id-politica-presentada"] }. conversion_policy_ids es obligatorio para conversión inicial: envía solo los IDs mostrados en la vista previa, una vez por dirección. Sin conversión, omite la lista o envíala vacía. Panel: POST /merchant/v1/distributed-links/{id}/activate, con sesión Merchant y el mismo body. La clave/sesión debe pertenecer al Merchant del enlace y al modo sandbox. Referencias: ADR 0066 y ADR 0069.
Revalida identidad, acceso de las partes, reserva propia y composición del neto para todos los medios y cantidades de cuotas de tarjeta. Las partes del split pueden recibir BRL o USD; importes fijos y porcentajes siguen en la moneda del cobro. La activación acepta la versión de tarifa propia mostrada. Cambiar o desactivar esa política bloquea nuevos pagos del enlace (409 distribution_conversion_terms_changed), sin alterar pagos ya confirmados. Cada sesión fija cotización, vigencia y pagos convertidos antes de llamar al proveedor. Reabrir una sesión vencida presenta nueva cotización; el cambio diario de Tesorería no altera el importe prometido. Registra una autorización inmutable y un evento de auditoría en la misma transacción que activa el enlace. Activar no crea pago, caja ni saldo disponible. Solo entonces el checkout sandbox puede iniciar un pago y fijar su snapshot financiero antes de llamar al proveedor de prueba.
Response 200: { id, status: "active", mode: "sandbox", activation_ready: true, replayed, checkout_url }. La URL usa el dominio verificado o subdominio Merchant, mediante la resolución pública existente. La misma clave recupera el resultado sin repetir activación ni auditoría. Otra referencia/identidad con esa clave: 409 idempotency_conflict. Enlace vencido, pausado tras activarse o acceso cambiado: 409 distribution_activation_changed. Entorno financiero incompleto: 409 distribution_not_ready. Modo live: 409 distribution_live_not_ready. Un enlace activo sin autorización persistida también permanece bloqueado.
Quote — la única superficie externa de precio
El precio de un cobro se obtiene exclusivamente por este endpoint. Ninguna tarifa, porcentaje o tipo de cambio se publica en este contrato: usted le pregunta al endpoint en el momento.
POST /v1/quotes — cotizar
Alcance: quotes:write. Idempotencia: requerida.
| campo | tipo | requerido | constraint |
|---|---|---|---|
amount | integer | sí | unidad más pequeña; > 0 |
currency | string | sí | BRL | USD |
payment_method | string | no | pix | ted | card; si se omite, devuelve todos los escenarios |
installments | integer | no | 1–21; default 12 |
fee_handling | string | no | absorb | pass_through; si se omite, usa el default de la regla de precio |
También se acepta el shape legado { "payment_intent_id": "pi_..." } con payment_method? e installments?.
Response 201 — objeto Quote cuando se informa payment_method:
{
"id": "qt_9f8c...",
"tenant_id": "three-hands",
"mode": "live",
"payment_intent_id": null,
"payment_method": "card",
"installments": 5,
"payer_total": 5000000,
"payer_currency": "BRL",
"receiver_net": 843375,
"receiver_currency": "USD",
"fx": {
"rate": 5.195993,
"base": "USD",
"quote": "BRL",
"locked_at": 1789990000,
"valid_until": 1789990900,
"rate_history_id": 123
},
"breakdown": {
"fee_handling": "absorb",
"total_fee": { "amount": 118905, "currency": "USD" },
"service_fee": { "final_cents": 118905, "final_currency": "USD", "percent_amount_cents": 118905, "fixed_cents": 0, "raw_total_cents": 118905, "min_applied": false, "max_applied": false },
"installment_surcharge": { "amount": 0, "currency": "USD" },
"tax": { "iof_amount_cents": 0, "iof_applicable": false, "iof_enabled": false, "iof_currency": "USD" }
},
"fx_rate_history_id": 123,
"rule_id": "cg_three-hands_5x",
"expires_at": 1789991800,
"signature": "sha256...",
"created_at": 1789990000
}La Quote es válida por 1800 segundos para ser aceptada. Esa validez de negociación es diferente de la validez interna del FX (fx.valid_until) y de la sesión de checkout de 900 segundos. Los valores numéricos de arriba son ilustrativos de forma, no de precio.
Cuando las condiciones comerciales incluyen un recargo al pago, ya está incluido en payer_total. En ese caso, breakdown.additional_fee contiene { "amount": entero, "currency": string }, en centavos de la moneda de cobro. Este campo opcional está separado de total_fee, la tarifa de servicio y los impuestos; no debe sumarse de nuevo al total. Siempre presenta y utiliza los importes devueltos por la cotización.
Para Merchants configurados con comisión en todas las ventas, el flujo directo POST /v1/payments exige un quote_id válido, explícito o asociado a la sesión de checkout. Si falta, devuelve 400 representative_quote_required sin iniciar el cobro. Los pagos directos que usan una cotización conservan sus condiciones hasta vencer. Los enlaces de cobro siguen usando las condiciones vigentes en el checkout. La plataforma configura las reglas internas de atribución, no el pagador.
Cuando se omite payment_method, la respuesta contiene scenarios.pix, scenarios.ted y scenarios.card[] con tarjeta desde 1x hasta el límite del tenant. Cada escenario lleva quote_id, método, cuotas, total del pagador, neto del merchant, FX público, expires_at y signature.
Errores: unknown_parameter (400), invalid_request (400), payment_method_unsupported (400), quote_fx_required (400), idempotencia, auth.
POST /v1/quotes/{id}/accept — aceptar una cotización
Alcance: payment_intents:write.
| campo | tipo | requerido | constraint |
|---|---|---|---|
name | string | sí | título mostrado en el checkout; 1–500 chars |
description | string | no | descripción larga mostrada en el checkout; ≤ 500 chars |
media_urls | array<string> | no | hasta 5 URLs alojadas por Poolvo |
expires_in | integer | no | segundos hasta expirar; 0 = sin expiración |
campaign | boolean | no | default false; true no guarda el FX de la quote en el link y cotiza FX por sesión |
reference | string | no | ≤ 255 chars; su clave de conciliación |
metadata | object | no | metadata libre |
payer_country | string | no | ISO 3166-1 alpha-2 |
Crea un PaymentIntent interno y un PaymentLink. El intent usa los valores persistidos en la quote: amount, currency, legal_entity_id, settlement_term, fee_strategy y fx_rate_history_id. Un link único (campaign=false) usa el fx_rate_history_id de la quote, aplica el trinquete del 1% en el checkout y expira en 24h cuando se omite expires_in. Un link de campaña (campaign=true) no guarda el FX de la quote en el link; cada sesión cotiza y bloquea el tipo de cambio mostrado por 900 segundos. Una quote expirada devuelve 400 quote_expired; una quote de otro tenant devuelve 404 quote_not_found; una quote ya consumida devuelve 400 quote_consumed.
Response 201: link de pago.
{
"id": "pl_9f8c...",
"url": "https://pay.pgz.link/pay/pl_9f8c...",
"checkout_url": "https://pay.pgz.link/pay/pl_9f8c...",
"amount": 5000000,
"currency": "BRL",
"payment_method": "card",
"installments": 5,
"payer_total": 5000000,
"payer_currency": "BRL",
"receiver_net": 843375,
"receiver_currency": "USD",
"fx_rate_history_id": 123,
"payment_intent_id": "pi_9f8c...",
"expires_at": 1790000000,
"reference": "pedido-8842"
}PaymentIntent
POST /v1/payment_intents — crear un intent legado
Alcance: payment_intents:write.
| campo | tipo | requerido | constraint |
|---|---|---|---|
legal_entity_id | string | no | default: la única entidad activa del tenant |
amount | integer | sí | unidad más pequeña; > 0 |
currency | string | sí | BRL | USD |
payer_country | string | no | ISO 3166-1 alpha-2 |
settlement_term | string | no | default: tenants.settlement_window_days como D+N |
fee_strategy | object | no | legado |
metadata | object | no | metadata libre |
reference | string | no | ≤ 255 chars |
Este endpoint permanece por compatibilidad. El flujo canónico es POST /v1/quotes seguido de POST /v1/quotes/{id}/accept. quote_id no se acepta en este endpoint y devuelve 400 unknown_parameter.
Payment — objeto de conciliación
El Payment es la fuente de verdad de la conciliación. Sus valores son los mismos producidos al momento del cobro, en la misma precisión (centésimo de basis point), leídos de la misma fuente persistida. Ninguna capa recalcula el precio para mostrar o notificar. Una diferencia entre lo que el pagador vio en el checkout y lo que usted recibe aquí es un bug crítico, no redondeo.
Cada objeto Payment lleva los campos de abajo; los campos condicionales obedecen a la regla de su propia fila (pueden ser omitidos o null exactamente cuando la fila lo indica):
| campo | significado para la conciliación |
|---|---|
id | identificador del pago (pay_...) |
link_id | el PaymentLink que lo originó, o null |
status | estado persistido del pago |
reference | su clave libre, definida al crear el link, devuelta aquí y en cada webhook |
created_at | cuándo se inició el pago (unix) |
updated_at | última actualización (unix; puede ser null) |
amount + currency | alias legado del bruto cobrado |
gross_amount + gross_currency | bruto cobrado al pagador |
fee_amount + fee_currency | tarifa total persistida cuando está disponible |
net_amount + net_currency | neto del merchant |
fee_handling | absorb | pass_through — quién pagó la tarifa de servicio en este pago (valor efectivo); independiente de breakdown/fees[] |
fx | { rate, base, quote, locked_at, valid_until, rate_history_id } — tipo de cambio bloqueado con cobertura. Presente solo cuando hay conversión de moneda. Sin conversión (ej.: BRL→BRL) el campo fx se omite — nunca fabricamos un objeto FX, rate: 1.0, un objeto vacío o null solo para satisfacer la forma. No expone spot, cobertura ni fuente interna |
payment_method | pix | ted | card |
installments | integer cuando aplica, si no null |
failure_code | presente cuando status = failed — código Poolvo del motivo (ver Códigos de rechazo) |
Códigos de rechazo (payment.failed)
Cuando un pago falla, decline_code es siempre un código semántico de Poolvo. El motivo original de cualquier sistema upstream nunca se repasa: se mapea internamente a uno de estos códigos, y lo que no tiene mapeo conocido se vuelve payment_declined.
decline_code | significado |
|---|---|
insufficient_funds | saldo/límite insuficiente |
card_declined | rechazado por el emisor |
expired_card | tarjeta expirada |
invalid_card | datos de tarjeta inválidos |
card_not_supported | marca/tipo no soportado |
authentication_failed | autenticación (3-D Secure) no completada |
pix_expired | el QR expiró sin pago |
pix_rejected | PIX rechazado por el banco del pagador |
transfer_not_confirmed | el comprobante de transferencia no cuadra en la conferencia |
payment_declined | rechazo genérico (motivo sin mapeo) |
GET /v1/payments/{id}
Alcance: payments:read. Response 200: objeto Payment. Errores: payment_not_found (404).
GET /v1/payments — cierre de período
Alcance: payments:read. Es el endpoint de conciliación de período.
Query:
| campo | tipo | requerido | descripción |
|---|---|---|---|
from | string | sí | fecha inicial en America/Sao_Paulo, YYYY-MM-DD, inclusiva en 00:00:00 |
to | string | sí | fecha final en America/Sao_Paulo, YYYY-MM-DD, inclusiva en 23:59:59 |
limit | integer | no | default 20, máximo 100 |
starting_after | string | no | cursor: id del último pago recibido en la página anterior |
Los límites siguen el calendario bancario brasileño usado en el cockpit: un pago a las 2026-08-20T22:00:00-03:00 pertenece al día 2026-08-20, aunque sea 2026-08-21 en UTC.
Orden: created_at DESC, id DESC. No hay filtro de status: el integrador recibe todos los pagos del período y filtra localmente si quiere.
Response 200: { data: Payment[], has_more, next_cursor }.
GET /v1/financial-position — saldo conciliado y depósitos
Scope: payments:read. El Merchant y el entorno provienen exclusivamente de la credencial. Endpoint de solo lectura; no solicita ni ejecuta transferencias.
Query opcional: currency (BRL o USD), kind (release, reserve, unreserve, hold, release_hold, payout, reverse_payout), from y to (Unix seconds inclusivos, from <= to), source_after y operation_after (ID del último elemento de cada lista). Campos desconocidos o filtros inválidos devuelven finance_invalid_filters (400).
Response 200: { updated_at, balances, sources, sources_has_more, operations, operations_has_more, reconciliation, unreconciled, deposit_returns }.
updated_at: instante de la consulta en Unix seconds.balances: saldo actual porcurrency, en centavos enteros:pending_cents(confirmado, pendiente de liberación),available_cents,reserved_cents,held_cents(disputas),paid_cents(depósitos netos de correcciones),blocked_cents(en revisión, no disponible),in_transit_cents(comprometido en transferencias) yadjusted_cents(ajustes manuales confirmados). El disponible excluye compromisos y ajustes. Los filtros del historial no alteran los saldos. Las monedas nunca se suman ni convierten implícitamente.sources: hasta 50 pagos reconocidos, ordenados porid ASC, conid,payment_id,reference,currency,amount_cents,created_at,payment_status,eligible,cash_confirmed,in_transit_centsy saldos en centavos.available_centsexcluye compromisos y ajustes.eligible=falseimpide usar el saldo de la fuente.operations: hasta 50 registros, ordenados poroccurred_at DESC, id DESC, conid,kind,amount_cents,currency,reference,reason,occurred_at,created_at,related_operation_id,reversedyallocations.reasonusa una descripción controlada sin notas internas. Cada asignación tienesource_id,payment_idyamount_cents; las retenciones también presentanremaining_cents. Un depósito puede agrupar pagos y un pago puede formar parte de varios depósitos.reconciliation:unrecognized_count,missing_economics_countylegacy_settled_count.unreconciledcontiene hasta 100 pagos antiguos capturados pendientes de reconocimiento cuyo neto y moneda se conocen, conpayment_id,reference(puede ser nulo),currencyynet_cents.deposit_returns: hasta 100 asignaciones recientes de devoluciones bancarias, conid,operation_id,source_id,payment_id,currency,amount_cents,occurred_atyrecorded_at. No altera el depósito original. El extracto paginado permite consultar todo el historial de devoluciones.
Los valores capturados ingresan en Pendiente de liberación; la disponibilidad exige una entrada totalmente conciliada. Preparación, envío y crédito son estados distintos. El historial ya liquidado no se reabre automáticamente. Los depósitos registran transferencias verificadas y las correcciones preservan el registro original. Esta conciliación no cambia el estado legado de Payment ni emite un nuevo webhook de liquidación. kind=payout lista depósitos con sus asignaciones; reversed=true indica un registro totalmente revertido.
GET /v1/financial-statement — extracto de la posición financiera
Scope: payments:read; el Merchant y el entorno provienen de la credencial. Solo lectura. Decisión de contrato: ADR-0046.
Query: currency obligatorio (BRL o USD), from y to opcionales en Unix seconds inclusivos (valores por defecto: 0 e instante de consulta), after opcional con el id del último movimiento recibido. Filtros desconocidos o período inválido: 400 finance_invalid_filters.
Response 200: { currency, from, to, basis: "recorded_at", opening_cents, credits_cents, debits_cents, closing_cents, entries, has_more }.
opening_cents + credits_cents - debits_cents = closing_cents, por moneda. El saldo representa la obligación restante, incluidas reservas, retenciones y transferencias en tránsito. No representa saldo bancario ni incluye depósitos ya pagados. Las fechas del período usan el instante del registro; occurred_at preserva por separado la fecha efectiva. Los totales abarcan todo el período, no solo la página.
entries: hasta 50 movimientos en recorded_at ASC, id ASC, con id, kind, payment_id, amount_cents, delta_cents, recorded_at y occurred_at. kind: recognition, release, reserve, unreserve, hold, release_hold, payout, reverse_payout, bank_return o manual_adjustment. Los movimientos entre compartimentos tienen delta_cents=0; los depósitos y ajustes reducen el saldo; el reconocimiento y las devoluciones lo aumentan. No se expone ningún dato interno de Tesorería.
fee_handling y el flujo de reconocimiento de tarifa
fee_handling define quién paga la tarifa de servicio:
absorb— el pagador paga el valor del producto; la tarifa reduce el neto del merchant. Es el default.pass_through— el pagador paga producto + tarifa; el merchant recibe el valor completo.
En pass_through, la página de checkout exige el reconocimiento explícito del total por parte del pagador antes de generar el cobro (la plataforma valida que el total reconocido coincida, al centavo, con el total bloqueado). El reconocimiento se registra como el evento interno payment.fees_acknowledged. En absorb no hay reconocimiento. Usted no maneja este flujo por la API — pertenece al checkout alojado; usted solo recibe el fee_handling efectivo en el Payment.
Reglas del link
- Monedas:
BRLyUSD. El cobro de PIX y tarjeta se liquida en BRL al pagador; conversión y cobertura son derivadas. - Expiración:
expires_inen segundos;0= sin expiración. Tras expirar, el link no acepta pago y emitelink.expired. - Cuotas:
1–21(solo tarjeta). El default de producto de un link es12(ADR-0006). - Métodos:
pixy/ocard. - Protección económica (
absorb): si, con el valor informado, el neto del merchant sería≤ 0tras las tarifas, la creación falla conmerchant_net_not_positive(400). Usepass_througho aumente el valor.