La API y los webhooks son gratis en sandbox en cualquier plan. Crea tu cuenta, cambia la vista a Pruebas y genera un token que empieza con dp_test_ en Desarrolladores → API Tokens.
DeonPay · Developers · OpenAPI 3.1
Generada directamente desde la especificación OpenAPI que sirve la API pública v1 — siempre refleja los endpoints reales. Cada operación incluye método, path, permiso requerido y un ejemplo de curl.
Autenticación
Authorization: Bearer dp_xxxoX-API-Key: dp_xxxServidor
https://uat.deonpay.mxErrores
Formato uniforme: { error: { code, message } }. Rate limit: 60 req/min lecturas, 20 req/min escrituras por comercio (en el 429 llegan los headers X-RateLimit-* y Retry-After).
7 endpoints
/api/v1/linksListar links de pagoLista los links de pago del comercio (excluye siempre status=deleted). Si el caller es un vendedor (JWT operator), solo ve los links que le pertenecen.
permiso: links.readmódulo: mod_payment_linksQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.statustypesearch— Busca en name, short_code y merchant_reference.date_from— ISO 8601. Filtra created_at >= date_from. Se compara tal cual (sin convertir a hora de México): sin offset se asume UTC. Envía timestamp completo con offset, p. ej. 2026-10-01T00:00:00-06:00.date_to— ISO 8601. Filtra created_at <= date_to. Se compara tal cual: un date_to de solo fecha (2026-10-31) equivale a 00:00 UTC y deja fuera casi todo ese día. Envía timestamp completo con offset.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/links" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/linksCrear link de pagoEl amount se calcula automáticamente de line_items si no se especifica. Soporta display_currency para mostrar precios en otra moneda (USD, EUR, GBP, CAD) — requiere exchange_rate salvo que se pueda auto-calcular. capture_method='manual' (retención de fondos, operativa hotelera) requiere el módulo mod_preauth activo y es incompatible con subscription_id.
permiso: links.createmódulo: mod_payment_linksEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/links" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "string",
"description": "string",
"amount": 10000,
"type": "single",
"max_uses": 10000,
"expires_at": "2026-01-15T10:00:00Z"
}'/api/v1/links/{id}Obtener link de pago{id} acepta UUID o short_code. Incluye bloque `stats` (pagos totales/exitosos/fallidos, revenue). Si el link expiró, se marca expired automáticamente en esta llamada.
permiso: links.readmódulo: mod_payment_linksEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/links/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/links/{id}Actualizar link de pagoSolo se actualizan los campos presentes en el body. metadata se REEMPLAZA completo (no hace merge). status solo acepta 'active'|'paused' aquí (no 'expired'/'deleted'). Si el link tiene display_currency, amount va en centavos de esa moneda (igual que al crear) y el servidor recalcula el cobro en MXN con el exchange_rate guardado.
permiso: links.updatemódulo: mod_payment_linksEjemplo
curl -X PATCH "https://uat.deonpay.mx/api/v1/links/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"amount": 10000,
"status": "active",
"max_uses": 10000,
"expires_at": "2026-01-15T10:00:00Z"
}'/api/v1/links/{id}Actualizar link de pago (alias de PATCH)Alias de PATCH /v1/links/{id}: mismo handler, misma semántica de actualización parcial (PUT aquí NO es full-replace). Ambos verbos existen por compatibilidad con integradores que esperaban PUT.
permiso: links.updatemódulo: mod_payment_linksEjemplo
curl -X PUT "https://uat.deonpay.mx/api/v1/links/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"amount": 10000,
"status": "active",
"max_uses": 10000,
"expires_at": "2026-01-15T10:00:00Z"
}'/api/v1/links/{id}Eliminar link de pagoSoft delete: status pasa a 'deleted'.
permiso: links.deletemódulo: mod_payment_linksEjemplo
curl -X DELETE "https://uat.deonpay.mx/api/v1/links/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/links/{id}/transactionsTransacciones de un linkTransacciones de un link
permiso: transactions.readmódulo: mod_payment_linksQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.statusdate_from— ISO 8601. Filtra created_at >= date_from. Se compara tal cual (sin convertir a hora de México): sin offset se asume UTC. Envía timestamp completo con offset, p. ej. 2026-10-01T00:00:00-06:00.date_to— ISO 8601. Filtra created_at <= date_to. Se compara tal cual: un date_to de solo fecha (2026-10-31) equivale a 00:00 UTC y deja fuera casi todo ese día. Envía timestamp completo con offset.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/links/SU_ID/transactions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"5 endpoints
/api/v1/checkout/sessionsListar checkout sessionsSesiones pending vencidas se marcan expired automáticamente durante esta llamada (dispara webhook checkout.session.expired).
permiso: checkout.sessions.readmódulo: mod_payment_linksQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.statusdate_from— ISO 8601. Filtra created_at >= date_from. Se compara tal cual (sin convertir a hora de México): sin offset se asume UTC. Envía timestamp completo con offset, p. ej. 2026-10-01T00:00:00-06:00.date_to— ISO 8601. Filtra created_at <= date_to. Se compara tal cual: un date_to de solo fecha (2026-10-31) equivale a 00:00 UTC y deja fuera casi todo ese día. Envía timestamp completo con offset.client_reference_idEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/checkout/sessions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/checkout/sessionsCrear checkout sessionRequiere line_items, subscription_id, o monto abierto (min_amount/max_amount) — mutuamente excluyentes. success_url_delay controla la espera (0-30s, default 1.5) antes de redirigir. metadata no puede usar las claves reservadas internas (pos_terminal_qr, pos_transaction_id, pos_intent_id, pos_order_id, pay_at_table).
permiso: checkout.sessions.createmódulo: mod_payment_linksEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/checkout/sessions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"line_items": [
{
"product_id": "uuid",
"name": "string",
"description": "string",
"quantity": 1,
"unit_amount": 10000,
"image_url": "string"
}
],
"success_url": "string",
"success_url_delay": 1.5,
"cancel_url": "string",
"mode": "embedded",
"allow_msi": true
}'/api/v1/checkout/sessions/{id}Obtener checkout session{id} acepta UUID o session_id (cs_test_*/cs_live_*). Incluye bloque `payment` con la última transacción asociada.
permiso: checkout.sessions.readmódulo: mod_payment_linksEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/checkout/sessions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/checkout/sessions/{id}Cancelar checkout sessionÚnico uso soportado: {"status":"cancelled"}. Solo funciona si la sesión está en status=pending.
permiso: checkout.sessions.cancelmódulo: mod_payment_linksEjemplo
curl -X PATCH "https://uat.deonpay.mx/api/v1/checkout/sessions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"status": "cancelled"
}'/api/v1/checkout/sessions/{id}Cancelar checkout session (alias de PATCH)Alias de PATCH /v1/checkout/sessions/{id}: mismo handler y comportamiento. Existe por compatibilidad con integradores que esperaban PUT.
permiso: checkout.sessions.cancelmódulo: mod_payment_linksEjemplo
curl -X PUT "https://uat.deonpay.mx/api/v1/checkout/sessions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"status": "cancelled"
}'6 endpoints
/api/v1/productsListar productosListar productos
permiso: products.readmódulo: mod_productsQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.search— Busca en name, sku y description.is_activesort_bysort_orderEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/products" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/productsCrear productoCrear producto
permiso: products.createmódulo: mod_productsEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/products" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"unit_amount": 10000,
"currency": "MXN",
"image_url": "string",
"sku": "string"
}'/api/v1/products/{id}Obtener productoObtener producto
permiso: products.readmódulo: mod_productsEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/products/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/products/{id}Actualizar productoTodos los campos opcionales; solo se actualizan los presentes. El mínimo de unit_amount es $1.00 MXN (100 centavos), igual que en la creación.
permiso: products.updatemódulo: mod_productsEjemplo
curl -X PATCH "https://uat.deonpay.mx/api/v1/products/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"unit_amount": 10000,
"currency": "MXN",
"image_url": "string",
"sku": "string"
}'/api/v1/products/{id}Actualizar producto (alias de PATCH)Alias de PATCH /v1/products/{id}: mismo handler, misma semántica de actualización parcial (PUT aquí NO es full-replace). Existe por compatibilidad con integradores que esperaban PUT.
permiso: products.updatemódulo: mod_productsEjemplo
curl -X PUT "https://uat.deonpay.mx/api/v1/products/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"unit_amount": 10000,
"currency": "MXN",
"image_url": "string",
"sku": "string"
}'/api/v1/products/{id}Eliminar productoHard delete (a diferencia de links, que hacen soft delete).
permiso: products.deletemódulo: mod_productsEjemplo
curl -X DELETE "https://uat.deonpay.mx/api/v1/products/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"4 endpoints
/api/v1/transactionsListar transaccionesEl environment lo dicta el token, no el toggle del comercio. Si el caller es un vendedor (JWT operator), solo ve transacciones que él distribuyó. **Sync incremental (CRM):** pasa `updated_since` + `order=updated_at` (ascendente) y avanza con `starting_after` = `meta.next_cursor`; `updated_at` también cambia con reembolsos y cambios de estado, así que lo modificado se re-trae. **Modo cursor:** con `starting_after` no se calculan totales (`meta.total` y `meta.total_pages` = null) y `meta.has_more` se decide con limit+1; sin él, `meta.next_cursor` ya viene poblado cuando hay más páginas, para arrancar un recorrido por cursor. Orden por defecto: `created_at` descendente, con `id` como desempate. Se excluyen las validaciones de tarjeta.
permiso: transactions.readQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.updated_since— ISO 8601 (fecha o fecha-hora). Filtra updated_at >= updated_since. Inválido → 400 `invalid_parameter`.order— `created_at` (default, descendente) o `updated_at` (ascendente; REQUIERE `updated_since`, si no → 400 `invalid_parameter`). Otro valor → 400.starting_after— UUID de una transacción (normalmente `meta.next_cursor`): devuelve las que siguen en el orden activo. Si no corresponde a una transacción del comercio/ambiente del token → 400 `invalid_cursor`. Activa el modo cursor (sin totales).channel— Canal(es) de origen, lista separada por comas: `link`, `checkout`, `terminal`, `subscription`, `other` (ej. `channel=terminal,link`). Valor fuera del catálogo → 400 `invalid_parameter`.statussource_typepayment_link_idcheckout_session_idcustomer_emailmerchant_referencecard_branddate_from— Filtra created_at >= date_from. `YYYY-MM-DD` = inicio de ese día en la zona horaria del comercio (día local); un timestamp ISO 8601 completo se usa tal cual (sin offset se asume UTC). Valor inválido → 400 `invalid_parameter`.date_to— Filtra created_at hasta date_to. `YYYY-MM-DD` = día local del comercio, INCLUSIVO hasta el fin de ese día; un timestamp ISO 8601 completo se usa tal cual (<=). Valor inválido → 400 `invalid_parameter`.amount_min— Centavos.amount_max— Centavos.branch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/transactions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/transactions/{id}Obtener transacciónDevuelve el objeto completo, incluyendo timeline de NetPay, refunds históricos y remaining_refundable, además de `channel`, `fees` y `refunded_amount`.
permiso: transactions.readEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/transactions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/transactions/{id}Reembolsar o cancelar transacciónMotor único (executeRefundOrVoid) decide automáticamente entre VOID (cancelación, mismo día antes de las 21:00 CDMX, sin costo) y REFUND (reembolso, días anteriores). Si amount se omite, reembolsa el saldo disponible completo.
permiso: transactions.refundEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/transactions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount": 10000,
"reason": "string"
}'/api/v1/balance_transactionsListar ledger de conciliaciónFeed inmutable de movimientos de dinero (charge/fee/refund) derivado de transactions + transaction_refunds, para conciliar contra tu contabilidad. Paginado por CURSOR (no page/offset). El environment lo dicta el token. Si el caller es un vendedor (JWT operator), solo ve movimientos de transacciones que él distribuyó. Reglas: una fila `charge` se emite por cada transacción con status completed/refunded/partially_refunded (el cargo ocurrió aunque luego se reembolsara); NO se emiten filas para retenciones preauth (authorized/partially_captured) ni para referencias OXXO sin pagar. Una fila `fee` solo existe si la transacción ya tiene commission_snapshot con totalFees>0 — si no, la fila charge trae `fee_pending: true` (no se inventa un fee de 0). Una fila `refund` solo se emite por reembolsos con status=succeeded; los pending/pending_verification NO generan fila y se resumen en `meta.reserved_pending_centavos`. Si el comercio tiene configurado (del lado de DeonPay, no vía request) ocultar el desglose de comisiones, la respuesta omite todas las filas `fee` y el campo `fee_pending` de las filas `charge` — no asumas que toda `charge` trae su `fee` pareja.
permiso: balance_transactions.readQuery params
cursor— Cursor opaco de `meta.next_cursor` de la respuesta anterior. Omitir para la primera página.limit— Elementos por página. Máximo 100.typecreated_from— ISO 8601. Filtra created_at >= created_from. Se compara tal cual; sin offset se asume UTC.created_to— ISO 8601. Filtra created_at <= created_to. Se compara tal cual; sin offset se asume UTC.branch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/balance_transactions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"8 endpoints
/api/v1/payment_intentsListar payment intentsListar payment intents
permiso: payment_intents.readmódulo: mod_developerQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.statuscreated_fromcreated_toEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/payment_intents" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/payment_intentsCrear payment intentEl monto/moneda quedan FIJOS desde este momento — el body de confirm() nunca puede alterarlos. La respuesta incluye client_secret UNA sola vez: guárdalo de inmediato, no se puede recuperar después. currency solo admite MXN.
permiso: payment_intents.writemódulo: mod_developerEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/payment_intents" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount": 100,
"currency": "MXN",
"customer": {
"email": "cliente@email.com",
"name": "string",
"phone": "string"
},
"description": "string",
"metadata": {},
"expires_in_minutes": 60
}'/api/v1/payment_intents/{id}Obtener payment intentclient_secret NUNCA se incluye aquí (solo en la respuesta de creación).
permiso: payment_intents.readmódulo: mod_developerEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/payment_intents/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/payment_intents/{id}/cancelCancelar payment intentSolo permitido desde requires_payment_method/requires_confirmation/requires_action/failed. Un intent succeeded/canceled/processing responde 409. Un intent con una referencia OXXO/SPEI emitida (requires_action) o un failed con transaccion de efectivo/SPEI ligada tampoco se cancela (409 payment_intent_awaiting_payment): un pago tardio aun puede llegar.
permiso: payment_intents.writemódulo: mod_developerEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/payment_intents/SU_ID/cancel" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/payment_intents/{id}/confirmConfirmar (cobrar) un payment intentAutenticación exclusiva por client_secret en el body — NO requiere token dp_* ni el header Authorization. amount/currency/merchant ya están fijados desde la creación y no se pueden alterar aquí. Reusa internamente el mismo pipeline de cobro de /api/netpay/charge (3DS incluido): un status=succeeded confirma el cobro; requires_action expone next_action (redirect_to_url o use_netpay_3ds) para resolver 3D Secure — RECOMENDADO: redirige al pagador a `{appUrl}/pi/{id}/confirm#cs=<client_secret>` (F2b), que resuelve el challenge/frictionless y hace polling hasta el desenlace por su cuenta (ver public_status/resolve_3ds abajo); si prefieres implementar tu propio flujo 3DS, usa next_action.use_netpay_3ds.three_d_secure directamente. REINTENTOS DE TARJETA: un intent permite hasta max_card_attempts intentos de tarjeta (1-3, default 3). Si la tarjeta se rechaza y quedan intentos, el intent vuelve a requires_payment_method con last_error = motivo y se puede confirmar OTRA tarjeta con el mismo client_secret (cada confirm de tarjeta que llega a cobrar suma 1 a card_attempts; el siguiente confirm limpia last_error). Al agotar los intentos queda failed, terminal: confirm responde 409 payment_intent_failed y hay que crear un intent nuevo (también son terminales la expiración y los fallos de efectivo/SPEI). El tope de 3 acota el card-testing contra el mismo client_secret. La idempotencia de este endpoint la da el propio intent (claim atómico por status) — un header Idempotency-Key aquí NO tiene efecto y no debe usarse para deduplicar confirmaciones. El desenlace llega a tu backend por webhook (payment_intent.processing / requires_action / succeeded / payment_failed): confía en payment_intent.succeeded para entregar el pedido, no en el regreso del navegador.
sin token dp_* — JWT SupabaseEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/payment_intents/SU_ID/confirm" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"client_secret": "string",
"payment_method": {
"type": "card"
},
"token": "string",
"cardInfo": {},
"billing": {},
"installments": 10000
}'/api/v1/payment_intents/{id}/configConfig de tokenización para el SDK deonpay.jsEndpoint PÚBLICO consumido por `deonpay.js` (F2c), el SDK de tokenización embebible que corre en el dominio del comercio — NO requiere token dp_*, autenticación exclusiva por client_secret en el body (mismo modelo que public_status/confirm/resolve_3ds). Devuelve el procesador activo del comercio para el environment del intent y su llave PÚBLICA de tokenización (nunca una llave privada) — el SDK usa esto para decidir si monta el Component de Conekta o el formulario propio de NetPay. También devuelve `customer` (email/name/phone) SI el intent ya los trae fijados desde su creación — el SDK los usa para prellenar sus campos de contacto; nunca incluye datos sensibles de tarjeta. Responde con headers CORS abiertos (Access-Control-Allow-Origin: *) porque la autorización es el client_secret, no cookies — no hay CSRF que proteger y sin CORS el SDK no podría funcionar cross-origin.
sin token dp_* — JWT SupabaseEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/payment_intents/SU_ID/config" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"client_secret": "string"
}'/api/v1/payment_intents/{id}/public_statusConsultar estado público de un payment intent (para polling)Para confirmar el pago desde tu backend NO hagas polling: suscríbete al webhook payment_intent.succeeded (ver docs/api/webhooks.md). Este endpoint sirve para la página de espera del pagador. Endpoint PÚBLICO consumido por la página `/pi/{id}/confirm` (F2b) para hacer polling del desenlace de un intent — NO requiere token dp_*, autenticación exclusiva por client_secret en el body. Shape de respuesta deliberadamente acotado (PublicPaymentIntentStatus): nunca incluye merchant_id, processor_order_id ni ningún token. Si la transacción ligada usa Conekta y está atorada en `processing`, este endpoint intenta el mismo self-healing que `/api/transactions/{id}/status` (re-consulta la orden en Conekta) antes de responder. Recomendado hacer polling cada 2s durante los primeros ~60s y luego espaciar a 10s.
sin token dp_* — JWT SupabaseEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/payment_intents/SU_ID/public_status" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"client_secret": "string"
}'/api/v1/payment_intents/{id}/resolve_3dsCerrar el challenge/frictionless 3DS de un payment intentEndpoint PÚBLICO usado internamente por la página `/pi/{id}/confirm` (F2b) para completar la confirmación 3DS tras el callback del SDK de NetPay (Cardinal) o el flujo frictionless — NO requiere token dp_*, autenticación exclusiva por client_secret en el body. El `transactionTokenId` de NetPay NUNCA se expone al navegador: se resuelve server-side a partir de la transacción ligada al intent. Solo aplica a intents cuya transacción usa NetPay (processor='netpay') — si el `next_action` era `redirect_to_url` (Conekta), sigue esa URL directamente en vez de llamar aquí. Delega internamente en `/api/netpay/confirm` (mismo pipeline que el resto del sistema, incluida la sincronización del payment_intent).
sin token dp_* — JWT SupabaseEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/payment_intents/SU_ID/resolve_3ds" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"client_secret": "string",
"processor_transaction_id": "string",
"is_frictionless": false
}'5 endpoints
/api/v1/subscriptionsListar planes de suscripciónListar planes de suscripción
permiso: subscriptions.readmódulo: mod_subscriptionsQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.statussearch— Busca en name.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/subscriptions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/subscriptionsCrear plan de suscripciónRequiere llave On-Demand de NetPay configurada. En producción, sin llave propia del comercio → 400 ondemand_key_missing (sin fallback). En sandbox, si el comercio no tiene llave propia, se usa como fallback la env var compartida NETPAY_SANDBOX_ONDEMAND_KEY; solo falla si tampoco existe esa env var.
permiso: subscriptions.createmódulo: mod_subscriptionsEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/subscriptions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"amount": 10000,
"currency": "MXN",
"interval_type": null,
"interval_count": 1
}'/api/v1/subscriptions/{id}Obtener plan de suscripciónIncluye active_subscribers, total_subscribers y recent_charges (últimos 10).
permiso: subscriptions.readmódulo: mod_subscriptionsEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/subscriptions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/subscriptions/{id}Actualizar plan de suscripciónSolo campos no estructurales son editables. `interval_type`, `interval_count`, `currency` y `product_id` quedan CONGELADOS al crear el plan y se ignoran silenciosamente si se envían (Zod los descarta sin error, cambiarlos rompería los cobros recurrentes en curso).
permiso: subscriptions.updatemódulo: mod_subscriptionsEjemplo
curl -X PUT "https://uat.deonpay.mx/api/v1/subscriptions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"amount": 100,
"status": "active",
"metadata": {},
"allow_customer_cancel": true
}'/api/v1/subscriptions/{id}Actualizar plan de suscripción (alias de PUT)Alias de PUT /v1/subscriptions/{id}: mismo handler y comportamiento. A diferencia del resto de la API v1 (que documenta PUT como alias de PATCH), aquí el endpoint nativo es PUT y PATCH es el alias — se agregó por consistencia con el resto de la API.
permiso: subscriptions.updatemódulo: mod_subscriptionsEjemplo
curl -X PATCH "https://uat.deonpay.mx/api/v1/subscriptions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"description": "string",
"amount": 100,
"status": "active",
"metadata": {},
"allow_customer_cancel": true
}'3 endpoints
/api/v1/customer-subscriptionsListar suscriptoresListar suscriptores
permiso: subscriptions.readmódulo: mod_subscriptionsQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.subscription_idcustomer_email— Match exacto case-insensitive (no substring).statusEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/customer-subscriptions" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/customer-subscriptions/{id}Obtener suscriptorIncluye subscription{...,status,contract_terms} y recent_charges (últimos 20).
permiso: subscriptions.readmódulo: mod_subscriptionsEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/customer-subscriptions/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/customer-subscriptions/{id}/cancelCancelar suscripción de un clienteDos modos según cancel_at_period_end (default true): true = soft cancel (status no cambia hasta fin del periodo vigente, dispara customer.subscription.cancel_requested); false = hard cancel inmediato (status=cancelled ya, dispara subscription.cancelled). Body vacío = soft cancel (idempotente por diseño).
permiso: subscriptions.updatemódulo: mod_subscriptionsEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/customer-subscriptions/SU_ID/cancel" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"cancel_at_period_end": true,
"reason": "string",
"feedback": "string"
}'2 endpoints
/api/v1/customersListar clientesUn 'cliente' se deriva agregando transactions/saved_cards/customer_subscriptions — no existe tabla customers formal.
permiso: customers.readQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.searchsortlast_seen_since— ISO 8601. Solo clientes con última transacción >= last_seen_since (sync incremental). Se recorre el ranking por última transacción hasta salir del rango, con tope de 10 páginas x 500 clientes: si se alcanza, `meta.truncated` = true. Inválido → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/customers" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/customers/{email}Obtener cliente por emailEl email debe ir URL-encoded en la ruta. La búsqueda NO distingue mayúsculas/minúsculas y `data.email` devuelve el correo tal como está guardado (misma forma que en el listado). NUNCA expone vault_token de las tarjetas guardadas, solo metadata segura.
permiso: customers.readEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/customers/SU_EMAIL" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"3 endpoints
/api/v1/merchant/metricsMétricas del comercioSubset curado del dashboard interno. `period` afecta solo revenue/transactions/churn_rate. Los snapshots de suscripciones (active/trialing/past_due) son al momento; mrr es siempre run-rate de los últimos 30 días fijos, independiente de period. Con `branch_id=<uuid>` las cifras de suscripciones (ventas en línea) cuentan 0; con `branch_id=none` o sin filtro, como siempre. Si la lectura topa el límite interno de filas, la respuesta incluye `truncated: true` a nivel raíz (cifras parciales); si no, `truncated: false`. Definición única de ventas: `revenue.gross` = todo lo cobrado (completed+refunded+partially_refunded, sin propina) más el efectivo cobrado en terminal independiente; `revenue.refunded` = lo devuelto de esas ventas (acotado al bruto de cada una); `revenue.net` = gross − refunded (nunca negativo por transacción); `revenue.tips` = propinas, siempre aparte, nunca dentro de gross/net. `terminal_cash` desglosa aparte el efectivo de terminal ya incluido en gross/net. `transactions.successful` cuenta como cobro exitoso completed+refunded+partially_refunded (una venta reembolsada sí fue aprobada por el banco).
permiso: merchant.metrics.readQuery params
periodbranch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/merchant/metrics" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/merchant/metrics/seriesSerie temporal de revenueinterval es 'hour' cuando period=today, si no 'day'. Buckets sin ventas se pre-pueblan en 0. Si el caller es un vendedor (JWT operator), filtra a sus transacciones distribuidas. revenue_cents suma lo cobrado (completed+refunded+partially_refunded, definición única de ventas) usando el monto realmente capturado (sin restar reembolsos ni propina, y sin el efectivo de terminal). Si la lectura topa el límite interno de filas, `truncated: true` a nivel raíz indica que la serie es parcial. Con `branch_id=<uuid>` las ventas en línea (suscripciones) cuentan 0; con `branch_id=none` o sin filtro, como siempre.
permiso: merchant.metrics.readQuery params
periodbranch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/merchant/metrics/series" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/merchant/payment-rulesReglas de pago del comercioMSI, tarjetas permitidas, límites de monto y métodos de pago soportados. Respuesta cacheable (Cache-Control: private, max-age=300).
permiso: merchant.metrics.readEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/merchant/payment-rules" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"1 endpoint
/api/v1/branchesListar sucursalesSucursales del comercio, de la más antigua a la más reciente. Sin paginación (tope duro de 500). Por defecto solo las activas; `include_inactive=true` incluye las cerradas (útil para reportes históricos). Los ids sirven como `branch_id` en los GET de transacciones, ledger, autorizaciones, métricas, reportes y POS. Las ventas sin sucursal (en línea, terminales sin sucursal) no aparecen aquí: se filtran con `branch_id=none`. No tiene un permiso propio: basta con que el token tenga AL MENOS UNO de `transactions.read`, `balance_transactions.read`, `authorizations.read`, `reports.read`, `merchant.metrics.read` o cualquier permiso de lectura POS (`pos.terminals.read`, `pos.sales.read`, `pos.orders.read`, `pos.shifts.read`, `pos.reports.read`, `pos.customers.read`). Sin ninguno → 401 `unauthorized`.
permiso: transactions.read | balance_transactions.read | authorizations.read | reports.read | merchant.metrics.read | pos.*.read (basta uno)Query params
include_inactive— `true` incluye sucursales inactivas. Default: solo activas.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/branches" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"3 endpoints
/api/v1/reports/summaryResumen de ventas (rango libre)Las mismas cifras que Reportes del dashboard, con rango de fechas libre (máx. 366 días). Montos en centavos; `from`/`to` son días calendario locales del comercio, inclusivos. El ambiente lo fija el token. Una venta reembolsada cuenta en `gross` y se resta en `refunds`; las propinas van aparte (`tips`), nunca dentro de `gross`/`net_sales`. `fees` y `net` son null cuando el comercio tiene oculto el desglose de comisiones (`commissions_visible` = false). `series` es por hora (claves `00`..`23`) cuando from = to, y por día (`YYYY-MM-DD`) en otro caso. Con `compare=true`, `previous` trae el periodo anterior de igual duración (termina el día previo a `from`). `truncated` = true indica cifras incompletas por el tope de lectura.
permiso: reports.readQuery params
from— Fecha local inicial `YYYY-MM-DD` (inclusiva, zona horaria del comercio). Default: día 1 del mes de `to`. Inválida → 400 `validation_error`.to— Fecha local final `YYYY-MM-DD` (inclusiva). Default: hoy en la zona horaria del comercio. `from` no puede ser posterior a `to` y el rango máximo es de 366 días (400 `validation_error`).branch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.compare— `true` para incluir el periodo anterior en `previous` (cualquier otro valor lo omite). Con `compare=true` el rango máximo es de 92 días (`400 validation_error`).Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/reports/summary" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/reports/daily-closeCierre de díaEl 'Cierre de día' del dashboard para un día calendario en la zona horaria del comercio. Montos en centavos. `pos_cash` y `shifts` son informativos y van separados de `totals`; son null para credenciales con alcance de vendedor. `hourly` siempre trae las 24 horas (0-23, hora local).
permiso: reports.readQuery params
date— Día `YYYY-MM-DD` (zona horaria del comercio). Default: hoy. Inválido → 400 `validation_error`.branch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/reports/daily-close" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/reports/sellersRanking de vendedoresMismo ranking que el dashboard (Reportes › Vendedores) en un rango libre (máx. 366 días). Restringido: una credencial con alcance de vendedor (operator/developer) o con rol fuera de admin/manager/finance recibe 403 `forbidden`; un token `dp_*` del comercio con `reports.read` pasa.
permiso: reports.readQuery params
from— Fecha local inicial `YYYY-MM-DD` (inclusiva, zona horaria del comercio). Default: día 1 del mes de `to`. Inválida → 400 `validation_error`.to— Fecha local final `YYYY-MM-DD` (inclusiva). Default: hoy en la zona horaria del comercio. `from` no puede ser posterior a `to` y el rango máximo es de 366 días (400 `validation_error`).branch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/reports/sellers" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"6 endpoints
/api/v1/webhook_endpointsListar webhook endpointsEndpoints del ambiente del token (hasta 500, más recientes primero). Nunca devuelve el secret completo: solo `secret_last4`.
permiso: webhooks.managemódulo: mod_webhooksEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/webhook_endpoints" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/webhook_endpointsCrear webhook endpointRegistra un endpoint en el ambiente del token. La respuesta incluye `secret` (para verificar `X-DeonPay-Signature`, HMAC-SHA256) **una sola vez**: guárdalo; después solo se ve `secret_last4` (usa `rotate_secret` si lo pierdes). `url` debe ser https:// y pasa la protección SSRF; `events` se valida contra el catálogo de eventos disponible para el comercio. `description` se guarda como alias del endpoint (máx. 100 caracteres). Acepta `Idempotency-Key`: un reintento con la misma clave y el mismo cuerpo devuelve la misma respuesta (`Idempotency-Replayed: true`) pero con `secret: null` y `secret_redacted: true` (el secret no se guarda en caché; si lo perdiste, usa `rotate_secret`); la misma clave con otro cuerpo se procesa como petición nueva. Máximo 50 endpoints por ambiente (`400 limit_reached`).
permiso: webhooks.managemódulo: mod_webhooksEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/webhook_endpoints" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "string",
"events": [
"link.payment.completed",
"checkout.payment.completed"
],
"description": "string",
"enabled": true
}'/api/v1/webhook_endpoints/{id}Obtener webhook endpointObtener webhook endpoint
permiso: webhooks.managemódulo: mod_webhooksEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/webhook_endpoints/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/webhook_endpoints/{id}Actualizar webhook endpointActualización parcial: envía solo los campos que cambian (`url`, `events`, `description`, `enabled`). Mismas validaciones que la creación. `enabled: false` pausa las entregas sin borrar el endpoint. Sin campos → 400 `validation_error`. No modifica el secret (usa `rotate_secret`).
permiso: webhooks.managemódulo: mod_webhooksEjemplo
curl -X PATCH "https://uat.deonpay.mx/api/v1/webhook_endpoints/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "string",
"events": [
"string"
],
"description": "string",
"enabled": true
}'/api/v1/webhook_endpoints/{id}Eliminar webhook endpointEliminar webhook endpoint
permiso: webhooks.managemódulo: mod_webhooksEjemplo
curl -X DELETE "https://uat.deonpay.mx/api/v1/webhook_endpoints/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/webhook_endpoints/{id}/rotate_secretRotar el secret de un webhook endpointGenera un secret nuevo y lo devuelve **una sola vez**. El anterior deja de valer de inmediato: actualiza tu verificador a la par. Con `Idempotency-Key`, un reintento devuelve la misma respuesta con `secret: null` (`secret_redacted: true`): el secret solo se entrega en la primera respuesta.
permiso: webhooks.managemódulo: mod_webhooksEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/webhook_endpoints/SU_ID/rotate_secret" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"6 endpoints
/api/v1/authorizationsListar retenciones (pre-autorizaciones)Requiere módulo mod_preauth. Sin ?status, solo devuelve retenciones VIGENTES (status=authorized). status=all expone también el histórico (partially_captured/expired/voided/completed/failed). Modelo de captura única: partially_captured YA está CERRADO, no admite otra captura/incremento/void. No existe POST /v1/authorizations: una retención nace cuando el cliente paga la página hospedada de un link (POST /v1/links) o checkout session (POST /v1/checkout/sessions) creados con capture_method=manual; Payment Intents no soportan retenciones. El webhook authorization.created entrega data.transaction.id, que es el id de estos endpoints.
permiso: authorizations.readmódulo: mod_preauthQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.statusbranch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/authorizations" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/authorizations/{id}Obtener retenciónIncluye el ledger completo de operaciones (authorize/increment/capture/void/expire).
permiso: authorizations.readmódulo: mod_preauthEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/authorizations/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/authorizations/{id}Fijar/quitar auto-renovación de una retenciónNo llama a NetPay ni mueve dinero — solo persiste auth_renew_until, que consume el cron /api/cron/preauth-renewal para hacer incrementos mínimos (+1 centavo) antes de cada vencimiento. Solo aplica sobre retenciones status=authorized.
permiso: authorizations.incrementmódulo: mod_preauthEjemplo
curl -X PATCH "https://uat.deonpay.mx/api/v1/authorizations/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"renew_until": "2026-01-15T10:00:00Z"
}'/api/v1/authorizations/{id}/captureCapturar (cobrar) una retenciónCobra el monto realmente consumido. Si amount se omite, captura el remanente completo (authorized_amount - captured_amount). Puede exceder lo autorizado hasta 20% (tolerancia de red). Modelo de captura única de NetPay PostAuth: una vez capturado (total o parcial), la retención queda CERRADA — no admite otra captura; una captura menor a lo autorizado deja status=partially_captured. 500 no_netpay_keys si el comercio no tiene llaves de NetPay para el ambiente del token.
permiso: authorizations.capturemódulo: mod_preauthEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/authorizations/SU_ID/capture" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount": 1,
"stop_renewal": false
}'/api/v1/authorizations/{id}/incrementIncrementar (ReAuth) una retenciónamount es el TOTAL ABSOLUTO nuevo en centavos, NO un delta. Reinicia auth_expires_at. Solo opera sobre retenciones status=authorized. 500 no_netpay_keys si el comercio no tiene llaves de NetPay para el ambiente del token.
permiso: authorizations.incrementmódulo: mod_preauthEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/authorizations/SU_ID/increment" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount": 1
}'/api/v1/authorizations/{id}/voidLiberar una retención sin cobrarLibera la retención completa sin cobrar. Solo opera sobre status=authorized (una retención partially_captured ya está cerrada). Verificado contra NetPay en producción. 500 no_netpay_keys si el comercio no tiene llaves de NetPay para el ambiente del token.
permiso: authorizations.voidmódulo: mod_preauthEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/authorizations/SU_ID/void" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"6 endpoints
/api/v1/invoicesListar facturas CFDISolo devuelve las facturas del ambiente del token (sandbox o production).
permiso: invoices.readmódulo: mod_billing_cfdiQuery params
page— Número de página (1-based).limit— Elementos por página. Máximo 100.statusrfc— Match exacto, normalizado a mayúsculas.fromtoEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/invoices" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/invoicesEmitir factura CFDI manualTimbrado SÍNCRONO: la respuesta HTTP ya trae el resultado (stamped o failed), no hay polling. Requiere el módulo mod_billing_cfdi activo (si está apagado, falla con 422 invoice_failed, no con 403). Idempotente por transaction_id: si ya tiene una factura pending/stamped, la devuelve sin re-emitir.
permiso: invoices.createmódulo: mod_billing_cfdiEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/invoices" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"rfc": "string",
"legal_name": "string",
"email": "cliente@email.com",
"use": "string",
"tax_system": "string",
"zip": "string"
},
"items": [
{
"name": "string",
"amount_centavos": 1,
"quantity": 1,
"product_key": "string"
}
],
"payment_form": "99",
"transaction_id": "uuid",
"currency": "MXN"
}'/api/v1/invoices/{id}Obtener factura CFDIObtener factura CFDI
permiso: invoices.readmódulo: mod_billing_cfdiEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/invoices/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/invoices/{id}/cancelCancelar factura CFDI ante el SATSolo se cancelan facturas status=stamped. motive=01 (sustitución) requiere el campo substitution con el UUID del CFDI que la sustituye.
permiso: invoices.cancelmódulo: mod_billing_cfdiEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/invoices/SU_ID/cancel" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"motive": "01",
"substitution": "string"
}'/api/v1/invoices/{id}/pdfDescargar PDF de la facturaDevuelve el archivo binario directamente (Content-Type: application/pdf, Content-Disposition: attachment), no un JSON ni base64. Solo disponible para facturas stamped o cancelled.
permiso: invoices.readmódulo: mod_billing_cfdiEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/invoices/SU_ID/pdf" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/invoices/{id}/xmlDescargar XML (CFDI) de la facturaDevuelve el archivo binario directamente (Content-Type: application/xml, Content-Disposition: attachment). Solo disponible para facturas stamped o cancelled.
permiso: invoices.readmódulo: mod_billing_cfdiEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/invoices/SU_ID/xml" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"1 endpoint
/api/v1/catalog/feedFeed de catálogo ACP (Agentic Commerce)Feed del catálogo del comercio en formato ACP (protocolo de OpenAI) para descubrimiento por agentes de IA. Requiere el módulo mod_acp_catalog (apagado por defecto). Único endpoint de la API v1 donde el módulo deshabilitado responde 503 en vez de 403 — el resto de la API usa 403 module_disabled uniformemente.
permiso: catalog.feed.readmódulo: mod_acp_catalogQuery params
format— 'ndjson'/'jsonl' devuelve texto NDJSON (un JSON por línea) en vez del sobre {data,meta}.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/catalog/feed" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"3 endpoints
/api/v1/mePerfil del usuario autenticado (app móvil)Requiere EXCLUSIVAMENTE JWT Supabase Bearer (no acepta tokens dp_*). Pensado para la app móvil del vendedor. Devuelve las membresías del usuario donde su rol es 'operator' en comercios no suspendidos.
sin token dp_* — JWT SupabaseEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/me" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/devices/register-tokenRegistrar push token del dispositivo (app móvil)Requiere JWT Supabase Bearer (no tokens dp_*). Si el token ya pertenece a otro usuario (reasignación de dispositivo), se reasigna al usuario actual.
sin token dp_* — JWT SupabaseEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/devices/register-token" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"token": "string",
"platform": "android",
"app_version": "string"
}'/api/v1/devices/register-tokenEliminar push token del dispositivo (app móvil)Requiere JWT Supabase Bearer (no tokens dp_*).
sin token dp_* — JWT SupabaseEjemplo
curl -X DELETE "https://uat.deonpay.mx/api/v1/devices/register-token" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"token": "string"
}'2 endpoints
/api/v1/pos/terminalsListar terminales POSListar terminales POS
permiso: pos.terminals.readmódulo: mod_posQuery params
include_inactivebranch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.environment— Por defecto el ambiente del token. Si se envía y difiere del token: 409 environment_mismatch.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/terminals" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/terminalsRegistrar terminal POSRegistrar terminal POS
permiso: pos.terminals.managemódulo: mod_posEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/terminals" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"serial_number": "string",
"store_id": "string",
"environment": "sandbox",
"affiliation": "string",
"nickname": "string",
"alias": "string"
}'5 endpoints
/api/v1/pos/ordersListar órdenes/cuentas POSListar órdenes/cuentas POS
permiso: pos.orders.readmódulo: mod_posQuery params
statuslimitbranch_id— Filtra por sucursal: `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; ausente → todas. A diferencia de otros endpoints, `branch_id=none` NO está disponible aquí (400 `invalid_parameter`). El criterio es la sucursal de la ORDEN o del turno (la del POS o cajero que la abrió), no la del pago: una orden sin sucursal (cajero que rota) cobrada en una terminal de la sucursal no aparece aquí, aunque su venta sí aparece en `/v1/transactions?branch_id=`. Lista los ids con `GET /v1/branches`. Un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/orders" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/ordersCrear orden/cuenta POSSi items[].product_id viene, precio y modificadores se fuerzan desde el catálogo (ignora lo enviado). Rechaza productos marcados is_86 (agotados).
permiso: pos.orders.managemódulo: mod_posEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/orders" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"terminal_id": "string",
"shift_id": "string",
"cashier_id": "string",
"customer_email": "string",
"customer_name": "string",
"customer_phone": "string"
}'/api/v1/pos/orders/{id}Obtener orden/cuenta POSIncluye items, payments (con terminal embebida en pagos con tarjeta) y active_intent (si status=tendered).
permiso: pos.orders.readmódulo: mod_posEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/orders/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/orders/{id}Anular orden/cuenta POSEs un 'void' (anulación), no un borrado físico. Solo permitido si la orden está open o tendered sin pagos completed.
permiso: pos.orders.managemódulo: mod_posEjemplo
curl -X DELETE "https://uat.deonpay.mx/api/v1/pos/orders/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"reason": "string"
}'/api/v1/pos/orders/{id}/chargeCobrar una orden/cuenta POS (split de pagos)Cobra el saldo restante completo con uno o más pagos (split mixto: cash/card/transfer/other). No admite cobro parcial vía este endpoint. Pagos con terminal SDK encolan un pos_intent async; terminales API despachan directo a NetPay.
permiso: pos.orders.managemódulo: mod_posEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/orders/SU_ID/charge" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"cashier_id": "string",
"payments": [
{
"method": "cash",
"amount_centavos": 10000,
"tip_centavos": 10000,
"cash_received_centavos": 10000,
"terminal_id": "string",
"reference": "string"
}
]
}'4 endpoints
/api/v1/pos/shiftsListar turnos de cajaFiltra por el ambiente del token.
permiso: pos.shifts.readmódulo: mod_posQuery params
statuscashier_idlimit— Máximo 100 (default 50). Valores inválidos o <= 0 caen al default; mayores a 100 se acotan a 100.branch_id— Filtra por sucursal: `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; ausente → todas. A diferencia de otros endpoints, `branch_id=none` NO está disponible aquí (400 `invalid_parameter`). El criterio es la sucursal de la ORDEN o del turno (la del POS o cajero que la abrió), no la del pago: una orden sin sucursal (cajero que rota) cobrada en una terminal de la sucursal no aparece aquí, aunque su venta sí aparece en `/v1/transactions?branch_id=`. Lista los ids con `GET /v1/branches`. Un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/shifts" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/shiftsAbrir turno de cajaAbrir turno de caja
permiso: pos.shifts.managemódulo: mod_posEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/shifts" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"cashier_id": "string",
"opening_cash_centavos": 0,
"opening_notes": "string",
"environment": "sandbox"
}'/api/v1/pos/shifts/{id}Obtener turno de cajaObtener turno de caja
permiso: pos.shifts.readmódulo: mod_posEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/shifts/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/shifts/{id}/closeCerrar turno de caja (corte)Calcula expected_* desde las órdenes del turno y persiste counted_*/difference_cash_centavos. Bloquea el cierre si hay órdenes tendered (cobros en curso) pendientes.
permiso: pos.shifts.managemódulo: mod_posEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/shifts/SU_ID/close" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"counted_cash_centavos": 0,
"counted_card_centavos": 10000,
"counted_transfer_centavos": 10000,
"counted_other_centavos": 10000,
"closing_notes": "string"
}'5 endpoints
/api/v1/pos/customersListar clientes del programa POSListar clientes del programa POS
permiso: pos.customers.readmódulo: mod_posQuery params
search— Busca en name/email/phone.limitrecent— '1' ordena por last_visit_at desc; si no, por name asc.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/customers" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/customersCrear cliente del programa POSCrear cliente del programa POS
permiso: pos.customers.managemódulo: mod_posEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/customers" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"email": "string",
"phone": "string",
"notes": "string"
}'/api/v1/pos/customers/{id}Obtener cliente del programa POSObtener cliente del programa POS
permiso: pos.customers.readmódulo: mod_posEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/customers/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/customers/{id}Actualizar cliente del programa POSActualizar cliente del programa POS
permiso: pos.customers.managemódulo: mod_posEjemplo
curl -X PATCH "https://uat.deonpay.mx/api/v1/pos/customers/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"email": "string",
"phone": "string",
"notes": "string",
"is_active": true
}'/api/v1/pos/customers/{id}/ordersHistorial de órdenes de un cliente POSHistorial de órdenes de un cliente POS
permiso: pos.customers.readmódulo: mod_posQuery params
limitbranch_id— Filtra por sucursal: `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; ausente → todas. A diferencia de otros endpoints, `branch_id=none` NO está disponible aquí (400 `invalid_parameter`). El criterio es la sucursal de la ORDEN o del turno (la del POS o cajero que la abrió), no la del pago: una orden sin sucursal (cajero que rota) cobrada en una terminal de la sucursal no aparece aquí, aunque su venta sí aparece en `/v1/transactions?branch_id=`. Lista los ids con `GET /v1/branches`. Un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/customers/SU_ID/orders" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"1 endpoint
/api/v1/pos/transactions/{id}/refundReembolsar/cancelar una venta POS (card-present)Si amount_centavos se omite, reembolsa el restante completo. Si la transacción POS nace de un cobro e-commerce/QR (checkout_session_id no nulo), se rechaza con 400 — usa /v1/transactions/{id} en su lugar.
permiso: pos.sales.refundmódulo: mod_posEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/transactions/SU_ID/refund" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount_centavos": 10000,
"reason": "string"
}'1 endpoint
/api/v1/pos/reportsReportes agregados POSfrom/to: YYYY-MM-DD se interpreta como 00:00 / 23:59:59.999 en UTC-6 fijo; un ISO con hora se respeta tal cual. Default últimos 30 días, máximo 1 año de rango. Solo cuenta órdenes paid/partially_refunded/refunded por closed_at.
permiso: pos.reports.readmódulo: mod_posQuery params
fromtoshift_idcashier_idbranch_id— Filtra por sucursal: `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; ausente → todas. A diferencia de otros endpoints, `branch_id=none` NO está disponible aquí (400 `invalid_parameter`). El criterio es la sucursal de la ORDEN o del turno (la del POS o cajero que la abrió), no la del pago: una orden sin sucursal (cajero que rota) cobrada en una terminal de la sucursal no aparece aquí, aunque su venta sí aparece en `/v1/transactions?branch_id=`. Lista los ids con `GET /v1/branches`. Un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/reports" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"4 endpoints
/api/v1/pos/intentsListar intents de cobro (terminales SDK)Permite reconciliar sin depender del webhook pos.intent.completed. Acepta permiso pos.terminals.read o pos.sdk.charge. Filtra por el ambiente del token (el de la terminal del intent).
permiso: pos.terminals.readmódulo: mod_posQuery params
terminal_idstatuslimitoffsetbranch_id— Filtra por sucursal. `<uuid>` de una sucursal del comercio (activa o inactiva) → solo filas de esa sucursal; `none` → solo filas sin sucursal (ventas en línea y terminales sin sucursal); ausente → todas. Lista los ids con `GET /v1/branches`. Otro valor, o un uuid que no es de una sucursal de este comercio → 400 `invalid_parameter`.Ejemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/intents" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/intentsCrear intent de cobro para terminal SDKEncola un cobro para una terminal SDK (PAX) suscrita vía Supabase Realtime; se procesa en sub-segundo. El resultado llega por webhook pos.intent.completed o por GET de polling. Gateado por la palanca GLOBAL de la plataforma (no solo por módulo del comercio) — a diferencia del resto de POS, aquí sí aplica 503 cuando la plataforma tiene el módulo POS apagado globalmente.
permiso: pos.sdk.chargemódulo: mod_pos_sdkEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/intents" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"terminal_id": "string",
"amount": 100,
"tip": 0,
"msi": 0,
"external_reference": "string",
"callback_url": "string"
}'/api/v1/pos/intents/{id}Obtener intent de cobroMismo shape que el listado, incluye bloque result cuando el intent llegó a estado terminal (approved/declined).
permiso: pos.terminals.readmódulo: mod_pos_sdkEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/intents/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/intents/{id}Cancelar intent de cobro pendienteSolo cancela intents status=pending. Si ya está in_progress (la terminal ya está cobrando), devuelve 409. Si ya está en un estado terminal, responde 200 idempotente con already_terminal=true.
permiso: pos.sdk.chargemódulo: mod_pos_sdkEjemplo
curl -X DELETE "https://uat.deonpay.mx/api/v1/pos/intents/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"5 endpoints
/api/v1/pos/print-jobsEncolar un ticket de impresión en una terminal SDKEncola un ticket APARTE para la impresora de una terminal SDK. El comprobante de pago NetPay no se puede modificar: cada print job es un ticket separado. Plantillas: `custom` (blocks: text, pair, separator, qr, barcode, image, feed; máx. 100 bloques, 64 KB), `promotion` (promotion.title/body/valid_until/code/code_format/image_asset_id/terms), `invoice` (invoice_id de una factura timbrada, con QR de verificación SAT) y `payment_receipt_copy` (transaction_id de una venta POS con tarjeta; la terminal reimprime el comprobante certificado marcado DUPLICADO y solo en la terminal que la cobró). `header: merchant` (default) antepone logo y nombre del comercio. Las imágenes se referencian con `asset_id` (de POST /v1/pos/print-assets) o `asset: merchant_logo`; nunca URL externa. La terminal se entera por el aviso `pending_print_jobs`. Acepta Idempotency-Key.
permiso: pos.print.createmódulo: mod_pos_sdkEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/print-jobs" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"terminal_id": "string",
"template": "custom",
"copies": 1,
"expires_in_seconds": 600,
"external_reference": "string",
"metadata": {}
}'/api/v1/pos/print-jobsListar trabajos de impresiónFiltros: terminal_id, status, external_reference. Paginado con limit/offset.
permiso: pos.print.readmódulo: mod_pos_sdkQuery params
terminal_idstatusexternal_referencelimitoffsetEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/print-jobs" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/print-jobs/{id}Obtener un trabajo de impresiónObtener un trabajo de impresión
permiso: pos.print.readmódulo: mod_pos_sdkEjemplo
curl -X GET "https://uat.deonpay.mx/api/v1/pos/print-jobs/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/print-jobs/{id}Cancelar un trabajo de impresión en colaSolo cancela trabajos en estado queued. Si la terminal ya lo reclamó o terminó, responde 409 print_job_not_cancellable con el status actual.
permiso: pos.print.createmódulo: mod_pos_sdkEjemplo
curl -X DELETE "https://uat.deonpay.mx/api/v1/pos/print-jobs/SU_ID" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx"/api/v1/pos/print-assetsSubir una imagen para ticketsSube una imagen PNG o JPEG (máx. 2 MB decodificada) en base64. Se convierte a monocromo térmico y se guarda en el almacenamiento de DeonPay; usa el `id` devuelto como `asset_id` en bloques image o promotion.image_asset_id.
permiso: pos.print.createmódulo: mod_pos_sdkEjemplo
curl -X POST "https://uat.deonpay.mx/api/v1/pos/print-assets" \
-H "Authorization: Bearer dp_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"image_base64": "string",
"content_type": "image/png"
}'