API

Referencia de la API

La API de Polya conecta tu sistema con tu cuenta: consultá tu saldo, enviá transferencias a cualquier CBU, CVU o alias, seguí las que recibís y cobrá con pagos por transferencia, cada uno con su propio CVU y alias.

Es una API REST: cada recurso tiene su URL, las requests y respuestas van en JSON, usamos códigos HTTP estándar y te autenticás con tu clave secreta. No hay pagos con tarjeta.

URL base:  https://api.polya.cash

Autenticación

Mandá tu clave secreta en el header Authorization con el esquema Bearer. Creala en Negocio › Developers › API Keys: la ves completa una sola vez. Las claves sk_test_ operan en sandbox; las sk_live_ en producción.

curl https://api.polya.cash/v1/balance \
  -H "Authorization: Bearer $POLYA_SECRET_KEY"

Errores de autenticación: API_KEY_MISSING, API_KEY_INVALID, API_KEY_EXPIRED (401) y API_KEY_ACCOUNT_LOCKED (403, data.reason es user_blocked o kyc_required).

Errores

Los 2xx indican éxito; los 4xx, algo a corregir o que no se pudo hacer; los 5xx, un problema nuestro. Cada respuesta trae el header Polya-Request-Id. El body de un error:

{
  "error": {
    "code": "PARAMETER_INVALID",
    "message": "amount tiene que ser un número con hasta 2 decimales (por ejemplo, 1500 o 1500.5).",
    "data": { "field": "amount" }
  }
}

Códigos frecuentes: INVALID_JSON, PARAMETER_MISSING, PARAMETER_INVALID, PARAMETER_UNKNOWN (400) · MERCHANT_NOT_CONFIGURED, TRANSFERS_BLOCKED, KYC_REQUIRED (403) · TRANSFER_NOT_FOUND, PAYMENT_NOT_FOUND (404) · IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_KEY_IN_USE, PAYMENT_NOT_REFUNDABLE (409) · INSUFFICIENT_BALANCE, LIMIT_EXCEEDED, DESTINATION_NOT_FOUND, DESTINATION_NOT_SUPPORTED, REFUND_FAILED (422) · RATE_LIMITED (429) · BANK_UNAVAILABLE, CVU_UNAVAILABLE (503).

Idempotencia

Con el header Idempotency-Key podés reintentar un POST sin miedo a duplicarlo. Es obligatorio en Crear una transferencia y recomendado en el resto. Usá un UUID v4 nuevo por operación; la guardamos 24 horas. Misma clave con otros datos: 409 IDEMPOTENCY_KEY_REUSED.

Paginación

Los listados devuelven un objeto list del más nuevo al más viejo, con limit (1–100, por defecto 20) y cursor opaco.

{ "object": "list", "data": [ … ], "has_more": true, "next_cursor": "MjAyNi0x…" }

Balance

GET/v1/balance— Saldo disponible en pesos y fondos a liberar.
{ "object": "balance", "available": 152340.5, "pending_release": 12000, "currency": "ARS" }

Transferencias

POST/v1/transfers— Enviá pesos a un CBU, CVU o alias. Idempotency-Key obligatorio.
GET/v1/transfers/:id— Una transferencia enviada o recibida.
GET/v1/transfers— Listado; filtros direction, status, created_gte, created_lt.
POST /v1/transfers
{ "amount": 25000, "destination": "0000003100010000000001" }

201 Created
{
  "id": "8f4d2c1e-…", "object": "transfer", "direction": "outbound", "status": "pending",
  "amount": 25000, "fee": 0, "net_amount": 25000, "currency": "ARS",
  "counterparty": { "name": "Proveedora Ejemplo S. A.", "tax_id": "30123456782", "account": "0000003100010000000001", "alias": null },
  "description": "Transferencia", "reference": null, "created_at": "2026-11-10T13:58:12.000Z"
}

Validamos la cuenta destino y te devolvemos el titular. A cuentas de Polya se completa al instante; al resto queda pending y te avisamos con transfer.sent o transfer.failed.

Pagos

Un pago es un cobro único por transferencia. Al crearlo le asignamos un CVU y un alias exclusivos; quien paga transfiere ahí y el pago pasa a succeeded solo. Estados: pending → succeeded (o failed si transfieren de menos: lo devolvemos y queda refunded; expired si nadie paga a tiempo).

POST/v1/payments— Crear un pago (amount, description, external_reference, return_url, metadata).
GET/v1/payments/:id— Obtener un pago.
GET/v1/payments— Listar pagos; filtros status, link_id, created_gte, created_lt.
POST/v1/payments/:id/refund— Reembolso total a la cuenta de origen, hasta 30 días.
{
  "id": "c2a7e5d1-…", "object": "payment", "status": "pending",
  "amount": 15000, "amount_received": null, "currency": "ARS",
  "description": "Pedido #1042", "external_reference": "pedido-1042",
  "bank_transfer": { "cvu": "0000001700000010000010", "alias": "pago.k7x2mq" },
  "checkout_url": "https://polya.ar/pay/c2a7e5d1-…", "return_url": null,
  "payer": null, "failure_code": null, "metadata": {},
  "expires_at": "2026-11-11T14:00:00.000Z", "paid_at": null, "refunded_at": null, "created_at": "2026-11-10T14:00:00.000Z"
}
POST/v1/payment_links— Crear un link (title, description, amount opcional, max_uses, expires_at).
GET/v1/payment_links— Listar links.
PATCH/v1/payment_links/:id— Pausar/activar o editar.

Cada persona que abre el link genera un pago propio con su CVU exclusivo; los ves en Pagos con payment_link_id.

Webhooks

Configurá destinos en Developers › Webhooks. Cada evento llega como POST JSON con los headers Polya-Event-Id, Polya-Event-Type, Polya-Delivery-Id y Polya-Signature. Respondé 2xx en menos de 15 segundos; si falla reintentamos a 1 min, 5 min, 30 min, 2 h, 5 h, 10 h y 24 h.

Eventos: transfer.received, transfer.sent, transfer.failed, payment.created, payment.succeeded, payment.failed, payment.refunded.

Verificar la firma

El header Polya-Signature trae t (timestamp) y v1. Calculá HMAC-SHA256 con el secreto del destino (whsec_…) sobre `${t}.${body}` y comparalo en tiempo constante. Rechazá si t difiere más de 5 minutos.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, header: string, secret: string) {
  const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Sandbox

Con claves sk_test_ todo opera con dinero simulado. Para simular que alguien transfirió a un CVU o alias (incluido el CVU de un pago):

curl https://api.polya.cash/sandbox/bank/incoming \
  -H "Content-Type: application/json" \
  -d '{ "to": "pago.k7x2mq", "amount": 15000, "from_name": "MARIA EJEMPLO" }'
PONER LEGALES ACA