DOCUMENTACIÓN PARA DESARROLLADORES
Documentación de la API
Integra facturas, pago y notificaciones de pago.
Resultados de búsqueda
Sin resultados. Prueba un nombre de endpoint, campo o guía.
Inicio rápido
Crea tu primera factura.
- Prepara una tienda
Activa sus métodos de pago, configura los proveedores y guarda una copia de seguridad de las wallets del proyecto.
- Crea una credencial API
En Ajustes → Acceso API de tu consola, elige lectura/escritura y asigna el proyecto.
- Envía la solicitud
Usa tu host API y copia tus IDs de proyecto y tienda. Envía los importes decimales como cadenas.
- Abre la página de pago
Redirige a
links.checkoutde la respuesta. Verifica la liquidación antes de entregar el pedido.
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Los ejemplos usan marcadores y no envían solicitudes desde esta página. Ver todos los campos de factura y el formato de respuesta →
IDs de proyecto y tienda
Dónde encontrar YOUR_PROJECT_ID y YOUR_STORE_ID.
Usa los UUID de tu consola, no los nombres de proyectos o tiendas ni sus identificadores legibles.
| Marcador | Dónde encontrarlo | Para qué se usa |
|---|---|---|
| YOUR_PROJECT_ID | Proyecto → Ajustes → IDs API → ID API de proyecto → Copiar. También se muestra en la pestaña Básico de la tienda. | Solicitudes a nivel de proyecto y tienda. |
| YOUR_STORE_ID | Proyecto → Tiendas → selecciona una tienda → Básico → IDs API → ID API de tienda → Copiar. | Creación de facturas y solicitudes de métodos de pago de la tienda. |
- Crear una factura requiere ambos IDs, incluso para la tienda predeterminada. La tienda debe pertenecer a ese proyecto y la credencial API debe tener acceso al proyecto.
- La creación, lista, detalle y página de pago de facturas devuelven invoice_id: el mismo UUID enviado en IPN/webhooks. Úsalo en las rutas de facturas, no el id interno ni order_id. Desde merchant 4.0.0 se elimina el antiguo campo de respuesta public_id; actualiza las integraciones antes de actualizar el software.
- La API REST no ofrece rutas para listar proyectos o tiendas. Copia los IDs en la consola o usa las herramientas MCP limitadas por ámbito list_projects y list_stores de merchant 5.0.0+.
- Tienda → Básico → Dominios de tienda selecciona nombres de host activos de comercio, pago y API. Los enlaces de pago devueltos y los nuevos enlaces de notificación priorizan esa tienda, después la predeterminada y después los valores del sistema. Nunca se eligen nombres retirados o sin activar. Configura tu SDK con el host API preferido; cambiar una preferencia no redirige otros alias activos.
Autenticación y alcance
Mantén las credenciales en tu servidor y concede solo el acceso necesario.
| Host predeterminado | Propósito |
|---|---|
| merchant.example.com | Consola de comercio y Ajustes |
| pay.example.com | Página de pago del cliente |
| api.example.com | Solicitudes API del comercio |
Sustituye example.com por tu dominio. Las instalaciones existentes conservan sus nombres configurados; gestiona alias en Ajustes → Sistema.
Authorization: Bearer YOUR_MERCHANT_API_TOKEN| Ajuste | Cómo funciona |
|---|---|
| Nivel de acceso | Las credenciales de solo lectura pueden listar y consultar. Las de lectura/escritura también pueden crear facturas y actualizar las políticas de activos documentadas. |
| Proyectos | Asigna los proyectos a los que puede acceder la credencial. Los IDs de tienda y factura deben pertenecer a un proyecto asignado. |
| Restricciones de IP | Opcionalmente permite direcciones públicas de salida IPv4 o IPv6 exactas en Ajustes → Acceso API. |
| Almacenamiento de credenciales | Guarda los tokens en la configuración de tu backend. Nunca incluyas una credencial bearer en un navegador ni en un enlace de pago. |
Las rutas públicas de pago usan el ID público de la factura y solo exponen datos seguros para el pago. Las sesiones de consola y controles administrativos son independientes de las credenciales API de comercio.
Activos y wallets
Elige los métodos de pago de cada tienda por separado.
- Consulta los activos de pago del proyecto y su disponibilidad.
- Activa la red nativa y configura su wallet y proveedores.
- Explora tokens candidatos y verifica el contrato o mint antes de activar un token.
- Selecciona para la tienda la lista ordenada de métodos de pago. Las nuevas facturas usan las selecciones disponibles.
Los tokens comparten la wallet de su red nativa. Los saldos de wallets devuelven importes atómicos exactos y valores fiat orientativos. Usa los campos de disponibilidad devueltos para determinar qué métodos pueden recibir pagos.
Los tokens ERC-20 verificados usan las redes EVM compatibles; los SPL verificados usan Solana. Los métodos de pago nativos están disponibles en las 30 redes integradas. Monero usa una conexión externa de wallet de solo lectura vinculada al proyecto.
API de recepción y cobertura nativa/de tokens
| Vía de pago | Compatibilidad | Evidencia | Requisitos |
|---|---|---|---|
| Vías de pago nativas | compatible | Escaneo de transacciones | BTC, SOL, ETH (Ethereum/Base/Arbitrum/OP), BNB, HYPE, AVAX y POL; las salidas Bitcoin, transacciones/recibos EVM canónicos y transferencias Solana analizadas aportan evidencia para las facturas. |
| Vías de tokens ERC-20 | compatible | Escaneo de transacciones | Ethereum, Base, BNB Chain, HyperEVM, Avalanche, Polygon, Arbitrum y Optimism requieren verificación en la blockchain; los logs Transfer indexados permiten atribuir los pagos. |
| Vías de tokens SPL | compatible | Escaneo de transacciones | Los candidatos Solana requieren verificación de red principal y mint; las diferencias exactas de saldo de tokens en transacciones analizadas permiten atribuir los pagos. |
| Vías nativas UTXO adicionales | compatible | Escaneo de transacciones | BCH/LTC/DOGE usan Esplora; BCH/DOGE también aceptan Bitcore, LTC/DOGE/DASH aceptan BlockCypher, Dash acepta Insight y ZEC transparente acepta zcash-explorer. Todos aceptan también bloques completos conservados de node-rpc compatible con Core. El modo directo requiere 1–48 confirmaciones, no detección en mempool. Zcash blindado no es compatible. |
| Vías nativas de cuentas indexadas | compatible | Escaneo de transacciones | TRON usa tron-indexer o node-rpc solidificado; XRP usa xrpl-jsonrpc; Stellar usa stellar-horizon o registros conservados de node-rpc de Stellar; Cosmos Hub usa cometbft-jsonrpc; Algorand usa algorand-indexer o node-rpc de algod; Hedera requiere hedera-mirror, no un relay EVM. |
| Vías de pago nativas por registro | compatible | Escaneo de transacciones | Aptos usa aptos-rest; Sui usa sui-graphql; NEAR usa near-jsonrpc; Kaspa usa kaspa-rest. Polkadot Asset Hub acepta substrate-rest o node-rpc finalizado que interprete metadatos; Tezos acepta tezos-tzkt u operaciones completas de node-rpc de Octez. Solo recibos nativos; las facturas antiguas requieren conservación del historial. |
| Vías nativas Cardano y TON | compatible | Escaneo de transacciones | Cardano requiere cardano-koios y TON requiere toncenter-v3. Las tags XRP, los ID memo Stellar y los comentarios de factura TON se devuelven como destination_tag y deben enviarse exactamente. |
| Integridad de la liquidación | compatible | Verificación independiente | Por defecto, la liquidación final requiere que dos proveedores independientes coincidan en la transacción/evento exactos, el importe, el bloque o slot canónico y la finalidad. Las ventanas directas y compartidas EVM también verifican la cobertura completa. Un administrador puede elegir expresamente un proveedor de confianza para una red; esto elimina la comprobación independiente, no las de identidad, integridad ni finalidad. |
| Vía nativa Monero | compatible | RPC de wallet de solo lectura vinculada al proyecto | Una wallet-RPC externa dedicada de solo observación, detrás de una pasarela HTTPS con lista de métodos permitidos, crea subdirecciones de la cuenta 0. El umbral configurado de daemons de red principal (por defecto 2 fuentes independientes, opcionalmente 1) aporta la evidencia de liquidación. El --restricted-rpc nativo es incompatible con create_address; el operador declara expresamente la copia de seguridad de la wallet y la ausencia de clave de gasto, y no se envía ningún material de claves a Wholly Crypto. |
Los saldos de exchanges y la elección de wallet o exchange por activo para envíos están disponibles en la consola, no en la API pública v1. Ver configuración de exchanges.
Ciclo de vida de una factura
Evidencia de pago, liquidación y entrega de pedidos.
| Estado | Significado |
|---|---|
| new | Esperando un pago |
| processing | Pago observado; importe aceptado o finalidad pendientes |
| settled | Aceptado según la política de liquidación de la factura o manualmente |
| expired | Plazo vencido; puede continuar el seguimiento tardío |
| invalid | El pago no puede aceptarse automáticamente |
| cancelled | Cancelada; solo la conciliación explícita puede reabrirla |
amount_status registros none, partial, paid o overpaid. timing_status distingue entre pagos a tiempo y tardíos. Las reglas de la tienda deciden las confirmaciones necesarias y la tolerancia aceptada para pagos inferiores.
Usa el valor de la factura invoice_id con la ruta de detalle de factura. Una redirección de pago por sí sola no demuestra la liquidación. Revisa excepciones mediante conciliación.
Reintentos seguros
Crear una factura requiere Idempotency-Key. Tras agotar el tiempo de espera, reintenta con la misma credencial, clave y cuerpo exacto de solicitud. Usa una clave nueva solo para una factura nueva.
Escaneo de pagos EVM
La detección compartida de bloques nativos y ERC-20 agrupa las facturas recientes por separado del trabajo de recuperación de historial antiguo. Cada factura conserva su cursor de historial persistente. Las consultas de tokens usan como máximo 100 bloques por solicitud y reducen el rango si el proveedor impone límites más estrictos. Dos proveedores independientes verifican cada ventana por defecto. Ajustes → Conexiones de redes → Detalles permite elegir una sola fuente de confianza para una red, sin comprobación independiente; se mantienen las verificaciones de transacción canónica, importe y confirmaciones. Los detalles de conexión distinguen retrasos del escáner, restricciones de historial y pausas por cuota del estado básico del nodo. La capacidad RPC pública no está garantizada.
IPN y webhooks
Recibe y verifica eventos de pago.
IPN recibe cada evento de factura generado en la ipn_url efectiva de la factura. Los webhooks solo reciben los eventos elegidos para cada endpoint habilitado de la tienda. Ambos envían por POST la misma instantánea JSON; son independientes, por lo que activar ambos puede notificar dos veces a tu aplicación.
Define ipn_url al crear una factura, o hereda el valor de la tienda. IPN usa el secreto de Tienda → IPN ; cada endpoint de Tienda → Webhooks tiene su propio secreto. Ninguno es tu clave API.
¿Cuándo debo entregar un pedido?
Para procesar por eventos, usa event_type = invoice.settled junto con status = settled para activar la comprobación del pedido. Verifica la factura actual y entrega cada pedido una sola vez.
status es el estado de la factura al crear el evento. event_type indica qué ocurrió. payment.received puede llevar processing o settled; no significa un segundo pago ni es una señal independiente para entregar.
¿Qué eventos y estados se envían?
| Evento en ajustes/historial | Estado en el cuerpo | Significado |
|---|---|---|
| invoice.created | new | Factura creada y esperando pago. También se usa cuando una reapertura controlada devuelve una factura a new. |
| payment.received | Resulting invoice status | Se registró un pago o aumentó el importe recibido. Normalmente processing o settled; este evento por sí solo no demuestra la liquidación. |
| invoice.processing | processing | Pago detectado, pero aún no se alcanza el importe aceptado o la finalidad requerida. Incluye pagos parciales. |
| invoice.settled | settled | Política de liquidación cumplida o aceptación manual. Comprueba resolution y tu pedido antes de entregar. |
| invoice.expired | expired | Venció el plazo de pago. Un pago tardío aún puede cambiar el estado mientras continúe el seguimiento. |
| invoice.invalid | invalid | No puede aceptarse automáticamente, se perdió la evidencia de pago o un comercio lo rechazó. Revisa la factura. |
| invoice.cancelled | cancelled | Factura cancelada. No entregues; cancelar no reembolsa un pago en la blockchain. |
Por qué Ethereum y Solana pueden enviar flujos de eventos diferentes
Las confirmaciones llegan después (ejemplo de Ethereum)
| Secuencia | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | processing |
| 2 | invoice.processing | processing |
| 3 | invoice.settled | settled |
Ya es definitivo al detectarse (ejemplo de Solana)
| Secuencia | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | settled |
| 2 | invoice.settled | settled |
Esto muestra la creación de eventos, no un orden de entrega garantizado. Cualquiera de los flujos puede ocurrir en otras redes según el momento de detección y la política de liquidación. No exijas un evento processing antes de settled.
Entregar una sola vez: ejemplo de receptor y protección contra duplicados
| Enfoque | Cómo gestionarlo |
|---|---|
| Receptor por eventos | Conserva eventos distintos por su event_id firmado y después selecciona invoice.settled con status = settled. No descartes este evento porque haya llegado primero payment.received con la misma sequence. |
| Bandeja de estados de pedido del SDK | Los ejemplos de receptor PHP, Python y Node proporcionados agrupan project + invoice_id + sequence. Procesa el estado guardado sin importar event_type, consulta la factura actual y entrega una vez si está settled. No añadas un filtro exclusivo invoice.settled después de agrupar. |
Un reintento conserva event_id y el cuerpo original. Distintos eventos pueden compartir sequence, pero tener valores event_id diferentes. Elimina entregas duplicadas por event_id firmado al procesar por eventos; protege por separado la entrega mediante instalación/proyecto configurados + invoice_id y tu pedido. Una liquidación posterior no debe acreditar dos veces el pedido.
HTTP receiver:
Verify raw-body signature, timestamp and configured project/store scope.
Save to a durable inbox; deduplicate the signed event_id.
Return HTTP 2xx only after persistence succeeds.
Event-based background worker:
Other events go to status/reconciliation handling, not fulfilment.
Continue here only for event_type = invoice.settled and status = settled.
Fetch the current invoice from your configured API origin.
Check settled status, project/store, order, amount, currency and review policy.
In one database transaction:
Lock the order and check the scoped invoice has not been fulfilled.
Credit/complete once and save the fulfilment record.
Queue any external fulfilment with the same business idempotency key.
SDK order-state worker:
Use the same current-invoice checks and fulfil-once transaction.
Do not filter event_type after collapsing events by invoice revision.Pseudocódigo, no un receptor listo para usar.
Todos los estados de factura y excepciones de pago
| Campo | Valores | Significado |
|---|---|---|
| status | new, processing, settled, expired, invalid, cancelled | Estado de la factura al crear el evento; no necesariamente su estado actual al entregarlo. |
| amount_status | none, partial, paid, overpaid | Importe recibido, incluida la tolerancia aceptada. paid no significa finalidad de confirmaciones. |
| timing_status | on_time, late | Si el pago cumplió el plazo de la factura. |
| resolution | automatic, manually_settled, manually_invalidated | Si el resultado lo determinaron las reglas normales o una aceptación/rechazo manual. |
| requires_review | false, true | Aviso de excepción, no otro estado de factura ni permiso automático para entregar o reembolsar. |
| Situación | Gestión |
|---|---|
| Pago insuficiente / tolerancia | Con reglas automáticas, partial no liquida. paid puede incluir una diferencia aceptada, pero sigue siendo necesaria la finalidad. Usa el estado de factura, no solo una comparación de importes. |
| Pago excesivo | overpaid puede coexistir con settled y requires_review = true. Aplica tu política para pagos excesivos; nunca acredites dos veces el pedido ni reembolses automáticamente una dirección sin verificar. |
| Pago tardío | expired puede cambiar más adelante mientras continúe el seguimiento. timing_status = late indica que hay que revisar; no reabras ni envíes automáticamente un pedido cancelado. |
| Aceptación manual | invoice.settled puede tener resolution = manually_settled sin fondos aptos en la blockchain. Decide si tu integración acepta esta anulación de las reglas; los campos de resumen de pago pueden ser null. |
| Reorganización / invalidación | Una revisión más nueva puede invalidar la evidencia de pago anterior. Vuelve a consultar el estado actual y gestiona la reversión mediante conciliación. No la ignores solo porque el pedido estuvo liquidado. |
| Cero confirmaciones / importe cero | La liquidación con cero confirmaciones puede ocurrir al detectar el pago y conlleva riesgo de reorganización. Una factura de importe cero permitida expresamente se liquida sin pago. Ninguna requiere primero un evento payment.received. |
Usa status = settled para entregar, no amount_status = paid ni una redirección del pago. Con cero confirmaciones requeridas, la liquidación puede ocurrir al detectar el pago; esto conlleva riesgo de reorganización.
Pago insuficiente es amount_status = partial; pago excesivo es overpaid. paid significa que llegó el mínimo aceptado, incluida la tolerancia de pago insuficiente de la factura. Son estados de importe, no de factura. late es un timing_status, no un evento separado.
Un flujo habitual es new → processing → settled, pero pueden omitirse estados intermedios. Una factura de importe cero permitida expresamente se liquida sin pago y conserva amount_status = none. La aceptación manual se marca como manually_settled.
Las notificaciones son instantáneas inmutables, no respuestas de estado en vivo. Pueden llegar tarde, desordenadas o más de una vez. Los eventos de pago y estado pueden compartir una sequence de factura y los mismos campos de estado, pero tener valores firmados event_id y event_type distintos. El número de confirmaciones no genera una notificación garantizada por bloque.
Qué recibes
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"amount": "49.9",
"currency": "EUR",
"order_id": "order-1042",
"payload_version": 2,
"event_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"event_type": "invoice.settled",
"occurred_at": "2026-09-14T12:05:00Z",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"description": "Annual plan",
"email": "ada@example.test",
"customer": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB"
},
"metadata": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB",
"cart_id": "cart-681"
},
"created_at": "2026-09-14T12:00:00Z",
"updated_at": "2026-09-14T12:05:00Z",
"expires_at": "2026-09-14T12:15:00Z",
"monitoring_expires_at": "2026-09-21T12:15:00Z",
"settled_at": "2026-09-14T12:05:00Z",
"paid_chain": "ethereum",
"paid_asset": "USDC",
"paid_asset_amount": "58.17342",
"paid_asset_amount_received": "58.17342",
"paid_payment_method_id": "33333333-3333-4333-8333-333333333333",
"settlement_exchange_rate": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"as_of": "2026-09-14T12:04:30Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"cancelled_at": null,
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"reason_code": "payment_confirmed",
"requires_review": false,
"links": {
"checkout": "https://pay.example.com/invoice/11111111-2222-4333-8444-555555555555",
"invoice": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555",
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments"
},
"payment_info": {
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"method_count": 1,
"methods_truncated": false,
"methods": [
{
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"asset_name": "USD Coin",
"symbol": "USDC",
"asset_kind": "token",
"asset_decimals": 6,
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"destination_address": "0x1111111111111111111111111111111111111111",
"destination_tag": null,
"status": "paid",
"amounts": {
"expected_amount": "58.17342",
"expected_amount_atomic": "58173420",
"received_amount": "58.17342",
"received_amount_atomic": "58173420",
"confirmed_amount": "58.17342",
"confirmed_amount_atomic": "58173420",
"unconfirmed_amount": "0",
"unconfirmed_amount_atomic": "0",
"minimum_payment_amount": "57.591686",
"minimum_payment_amount_atomic": "57591686",
"remaining_amount": "0",
"remaining_amount_atomic": "0",
"remaining_to_full_amount": "0",
"remaining_to_full_amount_atomic": "0",
"overpaid_amount": "0",
"overpaid_amount_atomic": "0"
},
"acceptance": {
"finality_mode": "confirmations",
"required_confirmations": 2,
"observed_confirmations": 2,
"underpayment_tolerance_percent": "1"
},
"quote": {
"effective_rate": "1.1658",
"reference_rate": "1.16",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"exchange_rate_spread_percent": "0.5",
"quote_expires_at": "2026-09-14T12:15:00Z",
"provenance_available": true,
"rounding": "up",
"unrounded_payment_amount": "58.17342",
"rounding_adjustment": "0",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T11:59:30Z",
"asset_fetched_at": "2026-09-14T11:59:30Z"
},
"market_rate_at_event": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"as_of": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"payment_count": 1,
"payments_truncated": false,
"payments": [
{
"payment_id": "55555555-5555-4555-8555-555555555555",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 0,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 26000000,
"observed_at": "2026-09-14T12:04:30Z",
"chain_time": "2026-09-14T12:04:20Z",
"finalized_at": "2026-09-14T12:05:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"links": {
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments?payment_method_id=33333333-3333-4333-8333-333333333333"
}
}
]
}
}amount es el total original de la factura. payment_info describe transferencias cripto observadas, importes aún pendientes y tipos de cambio fijados. La versión 2 también firma el nombre e ID del evento y el ámbito de proyecto y tienda.
Todos los campos de notificación y datos adicionales de factura
| Campo | Tipo | Significado |
|---|---|---|
| invoice_id | UUID | UUID público de factura, usado por la ruta autenticada de detalle de factura |
| status | string | Estado de factura en la instantánea: new, processing, settled, expired, invalid, cancelled |
| amount_status | string | none, partial, paid u overpaid; paid incluye la tolerancia aceptada de pago insuficiente, no la finalidad de confirmaciones |
| timing_status | string | on_time o late |
| resolution | string | automatic, manually_settled o manually_invalidated |
| sequence | integer | Revisión creciente de la factura; varios eventos pueden compartir revisión. Compara sin perder precisión de enteros |
| amount | decimal string | Total original de factura, no importe cripto recibido; conserva la precisión decimal |
| currency | string | Moneda de amount, p. ej., EUR para una factura EUR pagada con USDC |
| order_id | string | null | Referencia del pedido del comercio |
| payload_version | integer | 2 para eventos nuevos generados en 4.1.0+; ausente en eventos antiguos conservados |
| event_id | UUID | Identidad firmada del evento, sin cambios en reintentos y reenvíos manuales |
| event_type | string | Uno de los siete eventos de suscripción |
| occurred_at | timestamp | Cuándo se creó este evento inmutable, no la hora de entrega |
| project_id | UUID | Ámbito del proyecto de comercio; debe coincidir con el receptor configurado |
| store_id | UUID | Ámbito de la tienda de comercio; debe coincidir con el receptor configurado |
| description | string | null | Descripción original de factura |
| string | null | Email opcional del cliente al crear el evento | |
| customer | object | Campos opcionales reconocidos de metadatos de cliente; sin datos personales supuestos ni enriquecidos |
| metadata | object | Metadatos originales del comercio tal como existían al crear el evento |
| created_at | timestamp | Hora de creación de factura |
| updated_at | timestamp | Hora de actualización del estado de factura |
| expires_at | timestamp | Fecha límite de pago de factura |
| monitoring_expires_at | timestamp | Fecha límite de seguimiento de pagos tardíos |
| settled_at | timestamp | null | Hora de liquidación |
| paid_chain | string | null | 4.1.2+: slug de red del método de liquidación demostrado, p. ej., ethereum; null sin una liquidación apta guardada |
| paid_asset | string | null | 4.1.2+: símbolo de moneda nativa o token, p. ej., BTC, ETH o USDC; etiqueta visual, no identidad única de activo |
| paid_asset_amount | decimal string | null | 5.0.1+: importe solicitado total fijado en unidades paid_asset, antes de restar la tolerancia; guardado al liquidar |
| paid_asset_amount_received | decimal string | null | 5.0.1+: total válido recibido por el método ganador al liquidar, incluidas diferencias aceptadas por defecto o exceso; fijo, no un saldo en vivo |
| paid_payment_method_id | UUID | null | 4.1.2+: ID de intención que liquida; coincide con payment_info.methods[].payment_method_id y su red/contrato exactos |
| settlement_exchange_rate | object | null | 4.1.2+: instantánea de mercado previa al margen guardada al liquidar, con unidades, moneda, fechas de fuentes y marcas de calidad explícitas; nunca se recalcula al entregar |
| cancelled_at | timestamp | null | Hora de cancelación |
| exchange_rate_spread_percent | decimal string | Margen fijado, no el predeterminado actual de la tienda |
| underpayment_tolerance_percent | decimal string | Tolerancia de factura fijada; cada método también informa de su tolerancia efectiva |
| reason_code | string | null | Motivo de transición de estado legible por máquina |
| requires_review | boolean | Aviso de excepción de pago; no autoriza entregar ni reembolsar automáticamente |
| links | object | URL de pago, factura autenticada y pagos al crear el evento. Se aplican las preferencias de dominio de Tienda → Básico, después la tienda predeterminada y después el principal global; solo se usan dominios activos del rol adecuado. Los reintentos conservan los enlaces firmados originales; null si no hay registro de host activo. |
| payment_info | object | Métodos realmente observados, importes exactos, cotización fijada, instantánea orientativa de mercado y observaciones de pago limitadas; consulta los grupos de campos de abajo |
Resumen de liquidación: settlement_exchange_rate
| Campo | Tipo | Significado |
|---|---|---|
| rate / units / currency / symbol | strings | Unidades de activo antes del margen por una unidad de moneda de factura. Cadena decimal, no importe de pago ni operación ejecutada. |
| observed_at / as_of | timestamps | Hora de captura de liquidación / fecha de fuente anterior. No trates datos en caché como una cotización en vivo. |
| pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | strings / timestamps | Fuentes de precios fiat y de activos y sus horas de consulta, guardadas al liquidar. |
| stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | Las mismas marcas de calidad que market_rate_at_event. Los precios fijos de proyecto se etiquetan; la moneda de referencia es USD. |
| Missing snapshot or price | null | Sin tipos históricos supuestos. Antes de liquidar, todos los campos de resumen son null; si solo faltan precios, los identificadores paid_* demostrados siguen disponibles. |
Métodos de pago: payment_info
| Campo | Tipo | Significado |
|---|---|---|
| active_payment_method_id | UUID | null | Método observado ganador o seleccionado. Null antes de detectar o después de invalidar; no se supone un método predeterminado. |
| method_count / methods_truncated | integer / boolean | Total de métodos observados y si la lista integrada de métodos está incompleta. |
| methods[] | object[] | Como máximo ocho métodos observados, primero el activo. Sin totales entre activos diferentes. |
| payment_method_id / payment_rail | UUID / string | Identidad de intención de factura y transporte onchain o lightning. |
| chain_slug / network / caip_network_id | string | Identidad de red. Vincula siempre la identidad del token con su red. |
| asset_id / asset_key / caip_asset_id | UUID / string / nullable string | Identidad verificada del registro; los símbolos por sí solos no son únicos. |
| asset_name / symbol / asset_kind | string | Nombre visible del activo, símbolo y tipo native o token. |
| contract_address / token_standard | string | null | Contrato o mint del token y estándar; null para activos nativos. |
| asset_decimals | integer | Precisión atómica; Lightning BTC usa 11. |
| destination_address / destination_tag | string | null | Dirección pública receptora y memo/tag obligatorio. La dirección es null para Lightning; nunca una clave privada. |
| status | string | Estado del método: pending, partial, paid, overpaid, expired o invalid. Paid no es por sí solo la liquidación de la factura. |
| payment_count / payments_truncated / payments[] | integer / boolean / object[] | Total de observaciones y las últimas cinco o menos. Cada observación se describe abajo. |
| links.payments | HTTPS URL | null | Historial autenticado y paginado de este método en el origen API configurado. |
Importes exactos: methods[].amounts
| Campo | Tipo | Significado |
|---|---|---|
| expected_amount | decimal string | Cotización total fijada, después del margen y redondeo hacia arriba. |
| received_amount / confirmed_amount | decimal strings | Fondos válidos detectados / fondos que cumplen la política de confirmaciones o finalidad de este método. |
| unconfirmed_amount | decimal string | max(received - confirmed, 0). No es un importe adicional para enviar. |
| minimum_payment_amount | decimal string | Umbral aceptado tras la tolerancia. Puede ser inferior a la cotización total. |
| remaining_amount | decimal string | max(minimum accepted - received, 0). Fondos adicionales necesarios para alcanzar el umbral aceptado, no el progreso de confirmaciones. |
| remaining_to_full_amount | decimal string | max(full quote - received, 0), ignorando la tolerancia. |
| overpaid_amount | decimal string | max(received - full quote, 0). No autoriza un reembolso automático. |
| Every amount's *_atomic companion | integer string | Representación exacta en la unidad mínima. Usa bibliotecas decimales o de enteros; nunca float ni Number de JavaScript para dinero. |
Política de confirmaciones: methods[].acceptance
| Campo | Tipo | Significado |
|---|---|---|
| finality_mode / required_confirmations | string / integer | Confirmaciones fijadas o política finalized. La política del comercio permite expresamente cero confirmaciones; no significa finalidad universal de la red. |
| observed_confirmations | integer | null | Mínimo entre observaciones válidas, no solo de la transferencia más reciente. Null para Lightning o si no hay observaciones válidas. |
| underpayment_tolerance_percent | decimal string | Tolerancia efectiva del método. Lightning usa cero aunque la factura tenga tolerancia en blockchain distinta de cero. |
Tipos de cambio: methods[].quote y market_rate_at_event
| Campo | Tipo | Significado |
|---|---|---|
| quote.effective_rate / units / currency / symbol | strings | Tipo asset_per_invoice_currency fijado, incluido el margen; moneda y símbolo indican explícitamente la dirección. |
| quote.exchange_rate_spread_percent / quote_expires_at | decimal string / timestamp | Margen y plazo de cotización fijados. Nunca se sustituyen por los ajustes actuales de la tienda. |
| quote.reference_rate / unrounded_payment_amount / rounding_adjustment | decimal string | null | Referencia anterior al margen, importe de pago antes del redondeo y ajuste al alza en unidades del activo. |
| quote.pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | string or timestamp | null | Fuentes y fechas originales de precios de moneda y activo. Sin claves API ni credenciales de proveedores. |
| quote.provenance_available / rounding | boolean / string | False para facturas antiguas sin instantánea guardada de fuentes; el redondeo es hacia arriba. |
| market_rate_at_event | object | null | Instantánea orientativa del mercado en caché al crear este evento. Los datos ausentes permanecen null; nunca cambia importes de factura ni espera una consulta de red. |
| market_rate_at_event.rate / units / currency / symbol | strings | Tipo de mercado antes del margen, con la misma dirección explícita que quote. |
| market_rate_at_event.observed_at / as_of / pricing_fetched_at / asset_fetched_at | timestamps | Hora de instantánea del evento / la más antigua de las dos fuentes / hora de cada fuente. |
| market_rate_at_event.pricing_provider / asset_provider | strings | Fuentes de moneda y activo en caché, incluidos precios configurados de tokens personalizados. |
| market_rate_at_event.stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | Si la caché está desactualizada, el precio del token es fijo o la referencia USD usa una stablecoin como sustituto. La moneda de referencia es USD. Desactualizado es un aviso, nunca una cotización nueva. |
Registros de transferencias: methods[].payments[] y GET …/payments
| Campo | Tipo | Significado |
|---|---|---|
| payment_id / payment_method_id | UUID | Identidad de observación / identidad de intención principal. Usa payment_id para eliminar duplicados del historial. |
| transaction_id / payment_hash / event_index | string | null / integer | Hash en la blockchain e índice de transferencia/log/salida, o hash Lightning. Lightning no tiene transacción ni enlace a explorador. |
| payment_rail / chain_slug / network / asset_id / asset_key / caip_asset_id / symbol / asset_decimals | strings / UUID / integer | Los mismos identificadores de activo y red que el método que lo contiene. |
| amount / amount_atomic | decimal / integer strings | Valor exacto de esta transferencia, nunca una conversión fiat. |
| status / counts_towards_received | string / boolean | detected, confirming y final cuentan; reorged, replaced e invalid no. Conserva el historial invalidado para conciliación. |
| confirmations / block_height | integer | null | Datos de bloque de la observación; confirmaciones null para Lightning. |
| observed_at / chain_time / finalized_at | timestamp | null | Primera detección local, hora confiable de la red si está disponible y hora de finalidad según la política si se alcanzó. |
| explorer_name / explorer_url | string | null | Referencia validada a explorador público de bloques, cuando se admite. |
Merchant 5.13.3 excluye las transferencias internas verificadas de provisión de gas de los totales de pago de clientes, payment_info, la API de pagos de facturas, los límites de reembolso y los eventos payment.received. Sus registros de red y tesorería siguen disponibles para la contabilidad de wallets. Las transferencias normales y los pagos excesivos auténticos siguen contando. Nunca se reescriben cuerpos de notificación firmados existentes. Si una liquidación histórica dependía de fondos internos y no del cliente, la conciliación emite invoice.invalid con reason_code internal_gas_funding_excluded; revísala en lugar de volver a entregar.
Merchant 4.1.0 añade payload_version 2 sin mover ni cambiar los nueve campos originales. Los eventos ya en cola conservan el cuerpo original y pueden no tener payload_version. event_id, event_type y los IDs de proyecto y tienda están ahora en el cuerpo firmado; los encabezados de transporte de evento y entrega siguen sin firmar.
payment_info describe pagos observados, no todas las opciones de pago ofrecidas. Antes de detectar, active_payment_method_id es null y methods está vacío. Las observaciones reorged/invalid pueden permanecer en methods incluso cuando el método activo pasa a null. Nunca sumes importes de activos o redes diferentes.
Todos los importes, enteros atómicos, tipos de cambio y porcentajes son cadenas. received_amount incluye fondos válidos pendientes de confirmación; confirmed_amount cumple la política de finalidad del método. remaining_amount es max(minimum_payment_amount menos received_amount, 0); remaining_to_full_amount es max(expected_amount menos received_amount, 0). Ejemplo: 100 USDC esperados, 99 recibidos y 1% de tolerancia da remaining_amount 0 y remaining_to_full_amount 1. La finalidad sigue siendo obligatoria.
quote es el cálculo fijado de la factura: unidades de activo por una unidad de moneda de factura. El margen se aplica antes de redondear hacia arriba. Usa expected_amount_atomic para comparar el pago exacto; un tipo mostrado por sí solo quizá no reproduzca el redondeo al alza. Las facturas antiguas sin procedencia de fuentes guardada muestran campos de fuente/referencia/redondeo null y provenance_available false, nunca datos de hoy presentados como una cotización histórica.
market_rate_at_event son datos orientativos en caché antes del margen, fijados al crear el evento. Incluye fechas de fuentes y marcas de datos desactualizados y referencia sustituta; es null si no existe un par utilizable en caché. Ninguna consulta de precio en vivo bloquea una notificación y esta observación de mercado nunca cambia el importe adeudado. Los tokens personalizados fijos se etiquetan is_fixed; los tokens DEX usan la fuente específica de su proyecto, no un token con el mismo símbolo.
Los campos superiores paid_chain, paid_asset, paid_payment_method_id y settlement_exchange_rate (4.1.2+) identifican el método ganador demostrado tras liquidar, no una opción de pago seleccionada ni una suma de métodos. Antes de liquidar, tras invalidar, en liquidaciones antiguas sin instantánea o en aceptación manual sin fondos que cumplan la finalidad requerida, los campos de resumen son null. Los símbolos son etiquetas visuales: sigue el ID del método para la identidad exacta de red/activo/contrato.
Merchant 5.0.1 añade paid_asset_amount y paid_asset_amount_received como cadenas decimales exactas en unidades paid_asset; payload_version sigue siendo 2. paid_asset_amount es la cotización total fijada, incluido margen y redondeo al alza, nunca el umbral de tolerancia ni un saldo restante. paid_asset_amount_received es el total de recibos válidos del método ganador al liquidar, incluidos fondos pendientes de confirmación y diferencias aceptadas por defecto o exceso. Ejemplo: 100 USDC cotizados, 99 recibidos y aceptados con tolerancia da 100 y 99, no 99 y 99. Ambos se fijan con la instantánea de liquidación; usa payment_info.methods[].amounts para recibos en cada evento o la API de pagos para registros actuales. Son null sin una instantánea apta y para instantáneas anteriores a 5.0.1; los cuerpos de eventos antiguos en cola no cambian. Nunca conviertas cadenas decimales exactas a coma flotante para contabilidad.
settlement_exchange_rate es la observación de mercado en caché anterior al margen capturada al liquidar, no la cotización fijada de la factura ni una operación ejecutada en un exchange. Su estructura coincide con market_rate_at_event; 1.17 asset_per_invoice_currency con EUR/USDC significa 1 EUR = 1.17 USDC. Las fechas de las fuentes y las marcas de datos desactualizados/fijos/sustitutos describen su calidad. Si falta un par, el tipo queda null, pero un método demostrado conserva los campos paid_*. Nunca cambia el importe adeudado ni espera una llamada a un proveedor en vivo. Pagos posteriores con el mismo método, reintentos y reenvíos no pueden sustituir la instantánea guardada, incluido un tipo null guardado. Una liquidación nueva real o un cambio del método que liquida captura otra instantánea; observed_at identifica esa captura, mientras settled_at puede conservar la hora de la primera liquidación. Los cuerpos de eventos antiguos no cambian.
Se incluyen como máximo ocho métodos observados y las cinco observaciones de pago más recientes por método, con recuentos y marcas de truncamiento. El límite de tamaño puede reducir más esos arrays. Una observación de pago es una transferencia/log/salida UTXO, no necesariamente un hash de transacción único. Usa GET /v1/projects/YOUR_PROJECT_ID/invoices/{invoice_id}/payments con payment_method_id, limit y offset para obtener todo el historial actual. El detalle de factura conserva cada método cotizado y su quote_details. Los enlaces API requieren tu host y credenciales configurados; nunca reenvíes un token bearer a una URL arbitraria proporcionada por una notificación.
Lightning usa payment_hash en lugar de transaction_id; dirección receptora, explorador y confirmaciones observadas son null. Su importe BTC exacto usa 11 decimales (millisatoshis) y la tolerancia efectiva es cero. No incluye preimagen de pago BOLT11, clave de wallet, secreto de firma ni credencial del proveedor. Los campos de cliente/metadatos solo pertenecen a respuestas del comercio y notificaciones firmadas, nunca al pago público; no pongas credenciales en metadatos.
Recibe con seguridad
- Verifica el cuerpo original exacto con el secreto correspondiente antes de analizarlo. Tienda → IPN proporciona el secreto IPN, incluidas entregas a ipn_url personalizadas. Cada endpoint de Tienda → Webhooks tiene su propio secreto. Ninguno es tu token API; rotar uno no rota los demás.
- Comprueba la marca de tiempo firmada (predeterminado del SDK: cinco minutos en cualquier dirección) y, si están presentes, compara los IDs firmados de proyecto y tienda con la configuración de tu receptor. Guarda en una cola duradera antes de devolver HTTP 2xx. Para procesar por evento, event_id de v2 está firmado; los IDs de encabezado por sí solos no protegen contra repeticiones porque esos encabezados no están firmados. Para bandejas de estado de pedido, elimina duplicados de invoice_id y sequence y compara los campos originales de estado de factura, no todo el cuerpo v2: distintos tipos e IDs de evento pueden compartir revisión.
- En un worker, consulta la factura actual desde tu origen API configurado, no desde un enlace arbitrario de notificación. Comprueba pedido guardado, proyecto/tienda, importe y moneda, exige el estado actual settled y aplica tu política de aceptación manual y excepciones. Bloquea el pedido y entrega una sola vez dentro de una transacción de base de datos, independientemente de la eliminación de eventos duplicados.
- Nunca apliques una sequence anterior sobre una más reciente. Varios eventos pueden compartir revisión; no combines eliminación de duplicados por revisión con un filtro exclusivo invoice.settled. La reapertura/conciliación puede cambiar el estado; sequence, no una jerarquía fija de estados, ordena las actualizaciones. Registra reversiones para revisión en lugar de entregar de nuevo.
Ejemplos de receptor: PHP · Python · Node.js / TypeScript.
Verificación de firma y reglas de entrega
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWhollySignature(rawBody, header, signingSecret, toleranceSeconds = 300) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!match) return false;
const timestamp = Number(match[1]);
if (!Number.isSafeInteger(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > toleranceSeconds) return false;
// rawBody must be the exact request Buffer, before JSON parsing.
const expected = createHmac("sha256", signingSecret)
.update(String(timestamp))
.update(".")
.update(rawBody)
.digest();
const presented = Buffer.from(match[2], "hex");
return timingSafeEqual(expected, presented);
}| Regla de entrega | Detalles |
|---|---|
| Encabezados | Wholly-Signature, Wholly-Event-Id y Wholly-Delivery-Id; Content-Type es application/json. |
| Firma | HMAC-SHA256 sobre <unix timestamp>.<exact raw body>; formato de encabezado t=<timestamp>,v1=<64 lowercase hex>. |
| Éxito | Cualquier respuesta HTTP 2xx. No se siguen redirecciones; las respuestas no 2xx son fallos. |
| Tiempos de espera | 5 segundos para conectar y 10 segundos totales por solicitud. |
| Calendario de reintentos | Hasta 8 intentos para fallos reintentables: inmediatamente y después con retrasos de 10s, 1m, 5m, 15m, 1h, 6h y 24h tras terminar el intento anterior. IPN reintenta automáticamente; el reintento automático de webhooks puede desactivarse por endpoint. |
| Seguridad del destino | Solo HTTPS público. El DNS se revalida y fija para la entrega; se rechazan destinos locales, privados o reservados. |
| Retención de eventos | Los datos de eventos de notificación y las entregas se conservan durante 90 días según la programación; los detalles retenidos se purgan en lotes limitados. |
| Eliminación de duplicados | Guarda los valores firmados invoice_id y sequence en el ámbito del proyecto configurado. Wholly-Event-Id identifica un evento; Wholly-Delivery-Id identifica un registro de entrega (los reintentos lo reutilizan; un reenvío manual crea otro). Ninguno de los encabezados ID está firmado. |
| Nombres de eventos | La versión 2 firma event_id y event_type en el cuerpo. Los eventos antiguos en cola no tienen ninguno. Distintos tipos de evento pueden compartir una sequence de factura; concilia el estado por revisión o elimina eventos individuales duplicados por event_id firmado. |
| Rotación de secretos | La rotación no tiene solapamiento ni encabezado de versión y cambia inmediatamente las firmas de entregas en cola, reintentadas y manuales. |
| Entregas en pausa | Los créditos de procesamiento insuficientes pausan IPN/webhooks, incluidos reintentos. Los pagos entrantes continúan; las notificaciones en cola se reanudan tras recargar dentro de su plazo de retención de datos. |
Asistentes de IA · MCP
Conecta un asistente a tu instalación de comercio.
Merchant 5.0.0 incluye un servidor MCP opcional en tu dominio API configurado. Se ejecuta dentro de tu instalación, no mediante un relay compartido de Wholly Crypto.
- Abre Ajustes → Acceso API. Crea una credencial dedicada, asigna solo los proyectos que necesita el asistente y empieza con acceso de solo lectura. En una cuenta alojada por un operador, este activa primero el servicio MCP de la instalación; tú solo gestionas tus propias credenciales y autorizaciones.
- En Conexiones de IA · MCP, activa MCP, selecciona la credencial y guarda su acceso MCP. Las credenciales existentes no tienen acceso MCP hasta que se active expresamente.
- Copia la URL del servidor MCP en los ajustes de servidor HTTP remoto de tu cliente. Con OAuth, accede a tu consola de comercio, revisa el nombre del cliente y la dirección de retorno, elige una credencial y aprueba. Tus protecciones Basic Auth y TOTP existentes siguen aplicándose.
- Crear facturas requiere además una credencial de lectura/escritura, Leer y crear facturas en su política MCP, el ámbito OAuth mcp:invoice:create y aprobación explícita. Una conexión OAuth nunca obtiene proyectos añadidos a una credencial después de la aprobación.
{
"mcpServers": {
"whollycrypto": {
"url": "https://api.example.com/mcp"
}
}
}| Herramienta | Acceso | Propósito |
|---|---|---|
| list_projects | Consulta los | Proyectos habilitados asignados a la conexión; paginación limit/offset. |
| list_stores | Consulta los | Tiendas, IDs y estado habilitado dentro de project_id; paginación limit/offset. |
| list_payment_methods | Consulta los | Métodos de redes, tokens y Lightning configurados para project_id + store_id. |
| get_wallet_balances | Consulta los | Direcciones receptoras y saldos en caché, con campos de actualidad/disponibilidad; nunca secretos de wallets. |
| list_invoices | Consulta los | Facturas del proyecto filtradas por tienda, estado o búsqueda; paginación limit/offset. |
| get_invoice | Consulta los | Detalles completos de factura y enlace de pago usando project_id + invoice_id. |
| get_delivery_history | Consulta los | Estados, intentos y resultados HTTP de IPN/webhooks de la tienda. Filtros opcionales invoice_id/kind; sin secretos ni cuerpos de notificación. |
| convert_amount | Consulta los | Conversión de referencia en caché usando from, to y un amount como cadena decimal; no es una cotización de factura. |
| create_invoice | Escritura explícita | project_id, store_id, idempotency_key e invoice (el cuerpo existente de creación de facturas). invoice.payment_methods filtra los métodos habilitados de la tienda; 5.4.0+ ignora opciones inactivas/no aceptadas y vuelve a los valores de la tienda si ninguna coincide. Filtrar solo por red selecciona todos los activos aceptados activos. Los asset_tickers limitados por red se admiten desde 5.3.0. Devuelve la respuesta normal de factura. |
Protocolo, OAuth y seguridad
Usa Streamable HTTP por HTTPS. Negocia una versión de protocolo anunciada e incluye MCP-Protocol-Version en los POST posteriores. Envía Content-Type: application/json y Accept: application/json, text/event-stream. Las respuestas son JSON finito; las reconexiones no necesitan un ID de sesión MCP.
OAuth usa tokens de acceso breves (15 minutos), códigos S256 PKCE de un solo uso (5 minutos) y tokens de actualización rotatorios (duración de conexión de 30 días). Reutilizar un token de actualización ya usado revoca esa conexión. Vuelve a conectar tras la caducidad, rotación de credenciales, cambios de política o de dominio API canónico.
El descubrimiento OAuth solo es público cuando MCP está activado. El parámetro resource debe coincidir con la URL canónica devuelta por el descubrimiento, incluido /mcp. Se admite registro dinámico; no se admiten documentos remotos de metadatos de ID de cliente ni secretos de cliente.
Los clientes que admiten encabezados Authorization personalizados pueden usar en su lugar un token API de comercio habilitado para MCP como Bearer. Conserva sus permisos REST separados; es preferible OAuth para una conexión limitada a MCP. Nunca pegues credenciales en chats, URL, argumentos de herramientas ni control de versiones.
MCP comparte la cuota REST por minuto de la credencial y las restricciones exactas de IP de origen, además de las restricciones IP del host API. OAuth no evita una lista de permitidos. Para clientes IA remotos, permite sus IP de salida documentadas o deja esta restricción desactivada deliberadamente. No deben aplicarse desafíos de seguridad web ni caché a las rutas MCP/OAuth.
Errores HTTP: 401 requiere autenticación, 403 deniega origen/IP/permiso, 404 significa MCP desactivado o host incorrecto, 405 exige POST, 413 indica el límite de cuerpo de 32 KiB y 429 incluye Retry-After. Los errores JSON-RPC usan error.code; los fallos de herramientas usan result.isError=true incluso con HTTP 200. Los resultados correctos incluyen content y structuredContent.
Las listas muestran 25 filas por defecto, máximo 100; offset está limitado a 1000000. Las respuestas de herramientas están limitadas a 2 MiB. Las autorizaciones, solicitudes de autorización y contadores de cuota caducados se purgan automáticamente; los ajustes muestran como máximo 100 conexiones OAuth activas.
No se puede operar con proyectos o tiendas desactivados mediante MCP. La conexión puede listar el estado habilitado de una tienda, pero leer sus métodos de pago o historial de entregas y crear facturas requiere una tienda habilitada. Los usuarios habituales de proyectos de la consola no pueden administrar MCP.
Usa un idempotency_key nuevo para una factura nueva; tras agotar el tiempo de espera, reintenta con la misma credencial, clave y objeto invoice idéntico. Los importes decimales, margen, tolerancia, confirmaciones y apariencia del pago siguen el contrato REST de facturas. MCP nunca evita la política de pagos o créditos del comercio.
Las herramientas iniciales no pueden revelar claves privadas/frases de recuperación, enviar fondos manual o automáticamente, reembolsar, reenviar notificaciones, cambiar métodos de pago, editar cuentas/dominios ni gestionar facturación. Trata descripciones de facturas, campos de clientes y metadatos como datos no confiables, no instrucciones para el agente. Los proveedores IA conectados reciben los datos que autorizas a leer.
| Método | Ruta | Contrato |
|---|---|---|
| POST | /mcp | JSON-RPC autenticado: initialize, ping, tools/list, tools/call. Las solicitudes de notificación devuelven 202; se rechazan lotes. |
| GET / DELETE | /mcp | 405 autenticado: respuestas JSON finitas, sin flujo SSE independiente ni sesión MCP en el servidor. |
| GET | /.well-known/oauth-protected-resource/mcp | URL canónica del recurso y descubrimiento del servidor de autorización; también disponible en /.well-known/oauth-protected-resource. |
| GET | /.well-known/oauth-authorization-server | Endpoints OAuth, authorization_code/refresh_token, S256 PKCE y ámbitos compatibles. |
| POST | /mcp/oauth/register | Registro de cliente público: client_name y redirect_uris exactos. Solo HTTPS o HTTP de bucle local. Sin secreto de cliente ni consulta remota de metadatos. |
| GET | /mcp/oauth/authorize | client_id, redirect_uri, response_type=code, resource, code_challenge, code_challenge_method=S256, scope/state opcionales; redirige a la aprobación en consola. |
| POST | /mcp/oauth/token | Codificado como formulario: authorization_code + code + code_verifier + redirect_uri, o refresh_token + refresh_token. Incluye siempre client_id y resource. |
| POST | /mcp/oauth/revoke | client_id y token codificados como formulario. Revoca la conexión del token de acceso/actualización correspondiente. |
Ejemplo de solicitud directa a una herramienta
Primero inicializa y negocia el protocolo mediante tu cliente MCP. Esto muestra una solicitud posterior.
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/mcp" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'MCP-Protocol-Version: 2025-11-25' \
--header 'Accept: application/json, text/event-stream' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}`;
const response = await fetch("https://api.example.com/mcp", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}
JSON;
$ch = curl_init("https://api.example.com/mcp");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "MCP-Protocol-Version: 2025-11-25", "Accept: application/json, text/event-stream", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/mcp",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))API de operador
Crea comercios alojados con claves de servidor separadas y de alcance limitado.
Aloja varios negocios y automatiza su configuración mediante api.example.com/v1/operator. Disponible desde 7.4.0 solo en modo operador. La API de comercio habitual no cambia.
- Abre Operador → Ajustes → API de operador y actívala (desactivada por defecto). Crea una credencial separada con solo los permisos y comercios alojados necesarios.
- Guarda la clave wc_operator_ en tu servidor. Usa el host API, no el host del panel de operador ni una clave de comercio.
- Guarda un Idempotency-Key y el cuerpo exacto de la solicitud antes de cada POST de operador. Vuelve a leer la cuenta tras un resultado incierto; nunca sustituyas la clave solo para reintentar.
- Crea un comercio con onboarding: direct y contraseña, o onboarding: invitation y sin contraseña. Después crea proyectos/tiendas y emite una clave de comercio limitada al proyecto para su integración de pago.
| Ámbito | Acceso |
|---|---|
| merchants.read / merchants.write | Listar/leer y crear/actualizar comercios alojados. |
| users.read / users.write / users.security | Leer/crear/actualizar usuarios; cambiar contraseñas o revocar sesiones por separado. Nunca crea un administrador de operador. |
| invitations.read / invitations.write | Listar/leer, crear, sustituir y revocar enlaces de invitación/restablecimiento de un solo uso. Los usuarios nuevos también requieren users.write; los restablecimientos también requieren users.security. |
| credits.read / credits.write / fees.write | Leer saldos/libro mayor; conceder o corregir crédito local; definir comisiones futuras. Los créditos iniciales distintos de cero requieren credits.write. |
| topups.read / topups.write | Leer o crear solicitudes de pago para créditos de comercios alojados. Ninguna acción API puede marcarlas como pagadas. |
| projects.read / projects.write / reports.read | Crear proyectos, tiendas, apariencia y ajustes de pago de comercios; leer facturas, saldos de wallets e informes financieros. |
| merchant_credentials.read / merchant_credentials.write | Gestionar claves habituales de comercio con ámbito limitado. Permiso potente: esas claves actúan independientemente tras emitirse. |
| events.read / webhooks.write / audit.read / health.read | Leer historial del ciclo de vida; configurar notificaciones firmadas del ciclo de vida; leer auditoría/capacidades/estado de nodos. |
Incorporación, créditos, permisos y reintentos seguros
| Tema | Regla |
|---|---|
| Credenciales | Caducidad opcional y lista exacta de IPv4/IPv6 permitidas; 60 solicitudes/minuto por defecto, configurable entre 1–600. Cada solicitud comprueba el administrador emisor y los ámbitos actuales. HTTP 429 incluye Retry-After. |
| Aislamiento | Las claves solo acceden a sus comercios alojados asignados. Crear comercios y ver informes de toda la instalación requiere acceso a todos los comercios. El negocio del propio operador queda excluido. |
| Primer acceso | Las cuentas directas reconocen que quien aloja puede acceder a sus claves de wallets. require_password_change añade un cambio de contraseña en el primer acceso. Aceptar una invitación exige reconocer expresamente la custodia y después acceder normalmente. Basic Auth y TOTP existentes siguen vigentes. |
| Invitaciones | Los enlaces de usuarios nuevos duran 48 horas; los de restablecer contraseña, una hora. Los tokens son de un solo uso. Reemitir revoca el enlace anterior. La aceptación SMTP no garantiza entrega en la bandeja de entrada; revisa email_delivery. |
| Reintentos seguros | Cada POST de operador requiere una clave de 16–128 caracteres (letras, dígitos, -, _ o .). La misma clave con URL/cuerpo exactos devuelve el resultado guardado. Bytes diferentes devuelven 409. Se omiten secretos/enlaces al repetir; rota o reemite mediante una nueva operación explícita cuando haga falta. |
| Resultados inciertos | operator_request_in_progress significa que una operación está en curso o se interrumpió antes de registrar su recibo. Inspecciona el recurso y la auditoría; no envíes a ciegas una clave nueva. Los recibos completos se compactan tras 30 días; las claves antiguas siguen sin poder ejecutarse otra vez. |
| Créditos y comisiones | Cadenas decimales, máximo seis decimales. starting_credit es una concesión local única. Los ajustes necesitan importe con signo, nota y request_id, además de la clave HTTP de reintento. fee_bps=100 significa 1%; los cambios afectan a facturas futuras. Las concesiones no recargan el saldo prepago propio de la instalación. |
| Pausar | enabled=false desactiva una cuenta alojada y revoca las sesiones de consola. payments_paused=true detiene nuevas facturas. El seguimiento de pagos existentes continúa. La creación de proyectos/tiendas y la automatización mantienen la política de crédito de la instalación. |
| No expuesto | Sin secretos de wallets, firma, envíos, reembolsos, borrado permanente, restablecimiento TOTP, cambios de dominio ni configuración del servidor. Las solicitudes habituales de facturas siguen usando una clave de comercio y la API de comercio. |
Webhooks del ciclo de vida de operador
| Evento | Datos |
|---|---|
| merchant.created / merchant.updated | merchant_id, enabled, payments_paused, fee_bps. |
| user.created / user.updated | merchant_id, user_id, enabled. El evento de actualización cubre cambios de email, estado habilitado y rol de administrador. |
| invitation.accepted / password_reset.completed | merchant_id, user_id, invitation_id. |
| topup.settled / credit.balance_changed | merchant_id, ledger_id, kind, amount y balance. Lee la moneda de crédito del comercio o el detalle del libro mayor al conciliar. |
Los eventos del ciclo de vida de operador son independientes de IPN de facturas y webhooks de tienda. Una suscripción pertenece a la credencial de operador que la creó, con hasta 10 endpoints por clave. Solo se encolan eventos futuros coincidentes; usa GET /events para el historial conservado.
El cuerpo contiene event_id, event_type, merchant_id, occurred_at y data. Verifica Wholly-Signature sobre el cuerpo original exacto usando el signing_secret mostrado una vez del endpoint: HMAC-SHA256(secret, timestamp + '.' + raw_body), encabezado t=...,v1=.... Aplica una tolerancia corta de tiempo.
Usa el verificador genérico de firmas del SDK, no su analizador de notificaciones de facturas. Después valida merchant_id y event_type, guarda y elimina duplicados de event_id de forma transaccional y devuelve 2xx solo tras una aceptación duradera. Wholly-Event-Id debe coincidir con el cuerpo firmado. No trates los encabezados sin firma como datos de negocio.
La entrega es al menos una vez, puede llegar desordenada y se intenta hasta 8 veces. Lee los recursos actuales para conciliar; occurred_at no es una secuencia monótona. El ámbito y los ajustes de habilitación/caducidad se vuelven a comprobar antes de entregar. Las suscripciones desactivadas pausan el trabajo ya en cola, pero no encolan nuevos eventos mientras están desactivadas.
Los eventos e historial de entregas se conservan 30 días. La política de automatización de la instalación puede pausar la entrega. GET /webhooks/{id}/deliveries muestra resultado y datos inmutables; la API pública no fuerza la entrega de un registro caducado.
{
"event_id": "55555555-5555-4555-8555-555555555555",
"event_type": "merchant.created",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"occurred_at": "2026-10-01T12:00:00Z",
"data": {
"merchant_id": "11111111-1111-4111-8111-111111111111",
"enabled": true,
"payments_paused": false,
"fee_bps": 300
}
}Errores y límites
Gestiona validaciones, cuotas y reintentos de forma predecible.
Comprueba el estado HTTP y Content-Type antes de analizar una respuesta. Para un 429, espera al menos el tiempo de Retry-After antes de reintentar.
| Límite | Detalles |
|---|---|
| Frecuencia de solicitudes | Cuota por credencial: 120 solicitudes por minuto UTC por defecto, configurable entre 1 y 6000 en Ajustes → API. Todas las lecturas y escrituras v1 autenticadas, incluidos reintentos idempotentes y fallos de autorización/validación posteriores a la autenticación, comparten el cupo entre dominios, proyectos y procesos. Las credenciales inválidas, rutas de consola y pago público no lo consumen. |
| Encabezados de límite de solicitudes | Las respuestas v1 autenticadas incluyen X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (segundos Unix al comienzo del siguiente minuto UTC). El exceso de solicitudes devuelve JSON 429 rate_limit_exceeded y Retry-After en segundos enteros. Espera al menos ese tiempo y añade variación aleatoria al reintentar. Las ventanas fijas permiten ráfagas en los límites de minuto; no garantizan solicitudes por segundo. |
| Cuerpo del comercio | Máximo de 32 KiB en el router de la aplicación. La capa de entrada puede rechazar una solicitud demasiado grande antes de generar un error JSON. |
| Lista de facturas | limit es 50 por defecto y acepta 1–100; offset acepta 0–1,000,000. La búsqueda admite como máximo 100 caracteres. Los resultados van de más reciente a más antiguo e incluyen metadatos total/has_more. |
| Métodos de tienda | Máximo 64 selecciones de activos por tienda, suficiente para las 30 redes nativas y el catálogo limitado de tokens verificados. La política del proyecto, la capacidad del escáner y una wallet de red lista y con copia de seguridad siguen condicionando la creación de facturas. |
| Descubrimiento de tokens | El límite de candidatos es 50 por defecto y acepta 1–100. Los resultados del descubrimiento no son activos de pago hasta superar la verificación en la blockchain. |
| Tokens registrados del proyecto | Máximo 20 activos de token persistentes por proyecto. Los activos ya registrados pueden reutilizarse sin consumir otra plaza. |
| Idempotencia | Obligatoria para crear facturas. 1–128 caracteres ASCII visibles sin espacios; las claves son únicas por tienda y una repetición debe usar la credencial original y el cuerpo original exacto. |
| Metadatos | Solo objeto JSON, máximo 4,096 bytes codificados y cinco niveles de anidamiento. |
| Notificaciones | URL HTTPS pública de hasta 2,048 bytes. Los cuerpos de solicitudes de notificación se limitan a 256 KiB; los datos de eventos de factura conservados se limitan a 64 KiB con historiales de pago acotados. |
| Recursos de la página de pago | Las respuestas QR SVG son privadas y no-store porque un pago insuficiente cambia el resto exacto. Los logos PNG con versión se almacenan en caché pública un año y son inmutables. |
| Capa de entrada API | Las solicitudes al backend de la API gestionada tienen un tiempo de lectura de 30 segundos. Diseña clientes con tiempos de espera explícitos inferiores al tiempo disponible de su tarea. |
| Fallos sin JSON | UUID o parámetros de consulta mal formados, métodos incorrectos y el límite de 32 KiB pueden devolver texto del framework o respuestas vacías. Las rutas /v1 desconocidas devuelven actualmente HTML de consola con 404; valida estado y Content-Type antes de analizar. |
Referencia de errores
| HTTP | Código de error | Significado |
|---|---|---|
| 400 | invalid_reconciliation_action | Un filtro de estado de excepción, motivo, búsqueda o página de historial no es válido. |
| 500 | reconciliation_unavailable | No se pudo cargar la cola de excepciones o la evidencia. Reintenta la lectura con espera progresiva. |
| 402 | billing_required | Cada factura nueva requiere una cuenta de créditos vinculada y verificada y autorización vigente. Los créditos prepagos insuficientes no bloquean la creación ni los pagos entrantes: se pausan IPN, webhooks y Envío de fondos, mientras las comisiones siguen acumulándose. La creación sigue bloqueada para cuentas suspendidas, verificación de facturación vencida/inválida, servicio de créditos inaccesible o base fiat de factura no autorizada. Las comisiones usan el importe fiat original de la factura, no la cripto recibida, el margen, el exceso de pago ni las comisiones de red. Ese importe y la conversión independiente se registran antes de crear la página de pago. El seguimiento existente y la consulta de facturas continúan durante interrupciones. Tras recargar, las notificaciones en cola se reanudan dentro de su plazo normal de retención y se reanudan las reglas de envío activadas. Comprueba Ajustes → Comisiones y reintenta la creación fallida con el mismo Idempotency-Key. |
| 400 | invalid_json | JSON mal formado, campo desconocido o cuerpo que no coincide con la solicitud documentada. |
| 400 | idempotency_key_required | La creación de factura omitió Idempotency-Key. |
| 400 | invalid_idempotency_key | La clave está vacía, supera 128 bytes, no es ASCII o contiene espacios o un byte de control. |
| 400 | invalid_payment_request | Falló un campo validado o un método activo seleccionado. Lee error.message y error.details.payment_methods (PaymentMethodIssue[]) para saber qué lo bloquea exactamente. SDK 2.4.0+ añade resúmenes seguros de excepciones con acciones y funciones para problemas; los SDK de PHP anteriores exponen getApiMessage(). |
| 400 | invalid_invoice_status | El estado de la lista no pertenece a los seis estados de factura documentados. |
| 400 | invalid_callback_url | El destino IPN efectivo falló la validación de HTTPS, dirección pública, DNS o SSRF. |
| 400 | invalid_wallet_request | Un dato de preparación de wallet/dirección no es válido. |
| 400 | invalid_token_asset | La red del token, consulta de candidatos, identidad CoinGecko, metadatos de catálogo o entrada de contrato/mint no es válida. |
| 401 | authentication_required | El token Bearer falta, está mal formado, desactivado, rotado o es desconocido. |
| 403 | source_ip_denied | La restricción IP de la credencial no incluye la dirección pública exacta de origen de la solicitud. |
| 403 | source_ip_not_allowed | La restricción IP de origen del host excluye a este cliente. Un administrador puede gestionar las listas de permitidos de hosts activos en Ajustes → Sistema; se aplican además de las restricciones IP de la credencial. |
| 503 | source_access_unavailable | La verificación de acceso al host no está disponible temporalmente. Reintenta más tarde; si falla, las restricciones bloquean el acceso. |
| 403 / 409 / 500 | merchant_api_access_denied | Falló la autorización: permiso/ámbito de proyecto puede dar 403, proyecto/tienda desactivado puede dar 409 y un fallo del backend de autorización puede dar 500. Las wallets receptoras del operador están reservadas para el panel de operador, no para credenciales API de comercio ni MCP, incluso con una autorización explícita antigua del proyecto. |
| 403 | project_access_denied | Una nueva comprobación transaccional al crear detectó que la credencial ya no tiene acceso al proyecto. |
| 404 | invoice_not_found | No existe una factura con ese ID público en el proyecto autorizado o la página de pago no puede exponerla. |
| 404 | payment_resource_not_found | Ya no existe un proyecto, tienda, activo o wallet necesario al preparar la factura. |
| 404 | token_candidate_not_found | El proyecto no está disponible o el token ya no está en el catálogo de descubrimiento coincidente actual. |
| 409 | idempotency_conflict | La clave limitada a la tienda ya existe y difieren la credencial o los bytes exactos del cuerpo original. |
| 409 | store_unavailable | El proyecto o la tienda está desactivado o no disponible. |
| 409 | no_ready_payment_methods | No hay métodos de tienda listos. Lee error.message y error.details.payment_methods para ver chain_slug, asset_ticker y reason_code. La copia/activación de wallet, el adaptador instalado y los precios deben ser válidos. Desde 6.0.6, las pausas del escáner, las comprobaciones de estado fallidas o antiguas y la falta de cuórum de proveedores no bloquean la creación. |
| 409 | payment_method_unavailable | Un método seleccionado dejó de estar disponible durante la nueva comprobación atómica al crear. |
| 409 | store_payment_method_not_selected | Se pidió una excepción de confirmaciones de tienda para un activo que esa tienda no tiene seleccionado. |
| 409 | wallet_unavailable | Una wallet de pago dejó de estar disponible durante la nueva comprobación atómica al crear. |
| 409 | ipn_secret_required | Existe una URL IPN efectiva, pero la tienda no tiene secreto de firma IPN. |
| 409 | payment_resource_not_ready | Un activo de pago o wallet requerido está desactivado, sin copia de seguridad, esperando prueba de activación de cuenta compartida, agotado o no listo por otro motivo. |
| 409 | account_activation_unverified | No se pudo demostrar la activación de cuenta XRP Ledger o Stellar con el número configurado de endpoints sanos de red principal (2 por defecto, 1 opcional); aporta fondos a la cuenta exacta y reintenta la verificación. |
| 400 | invalid_monero_wallet_rpc | No es válido el endpoint HTTPS, la dirección principal exacta de red principal, la etiqueta o los datos completos de autenticación Digest/Basic/encabezado. |
| 404 | monero_wallet_rpc_not_found | No existe el vínculo de wallet-RPC Monero limitado al proyecto. |
| 409 | monero_wallet_rpc_not_ready | El activo Monero, cuórum de dos daemons, vínculo inmutable o declaración explícita de copia/solo lectura no está listo. |
| 409 | monero_wallet_rpc_unavailable | Crear facturas requiere un vínculo wallet-RPC Monero del proyecto activo, verificado y declarado, con credencial válida en el servidor. |
| 503 | lightning_unavailable | El único método listo de la tienda es Lightning y no se pudo verificar su wallet o cotización. Reintenta con la misma clave de idempotencia. Si existe otro método listo en blockchain, se omite el método Lightning no disponible. |
| 422 | monero_wallet_rpc_verification_failed | Falló la verificación de wallet exacta, fijación HTTPS, sincronización, cuórum de daemons de red principal o prueba de rechazo de métodos de la pasarela. |
| 503 | monero_wallet_rpc_failed | La wallet-RPC externa de solo observación no pudo crear y releer de forma segura la subdirección de factura; no se inventa una dirección alternativa. |
| 409 | token_chain_not_ready | El activo nativo de la red está desactivado, la correspondencia de descubrimiento cambió durante la verificación o el proyecto ya alcanzó el máximo actual de 20 activos de token registrados. |
| 503 | dex_price_unavailable | Proveedor DEX no disponible, ocupado, limitado por cuota, con respuesta antigua o datos mal formados. Reintenta después de un minuto; los precios fijos siguen disponibles. |
| 422 | invalid_dex_price | Combinación de modo de precio inválida o el pool elegido no puede dar un precio apto para el contrato exacto. Elige otro pool o precio fijo en USD. |
| 422 | token_verification_failed | Todos los nodos aptos fallaron la verificación de identidad de red, código de contrato, decimales, consulta de saldo o mint. |
| 422 | invalid_store_confirmation_policy | La excepción de tienda no está disponible para este modo de finalidad, queda fuera de los límites devueltos para la red o solicita aceptar cero confirmaciones sin soporte. |
| 409 | invoice_not_payable | La factura de pago está en estado terminal o venció su plazo de pago. |
| 409 | invoice_payment_method_locked | Un pago válido ya seleccionó otro activo; continúa con active_payment_method_id. |
| 409 | payment_method_not_payable | El método seleccionado está completo o ya no acepta otro pago. |
| 422 | payment_qr_unavailable | La solicitud de pago es demasiado grande para codificarla como imagen QR SVG. |
| 503 | payment_rates_unavailable | No hay una cotización reciente y fiable para ningún método de pago listo. |
| 500 | authentication_unavailable | La autenticación Bearer no pudo leer o validar su credencial almacenada de forma segura. |
| 429 | rate_limit_exceeded | Esta credencial agotó su cupo del minuto UTC actual. Espera al menos Retry-After segundos; reintenta crear la factura con la misma clave de idempotencia. |
| 500 | database_error / internal_error | Fallo temporal del servidor; reintenta de forma segura con la misma clave de idempotencia. |
Descripción general de la API
Elige un endpoint para ver sus campos, ejemplos y respuesta.
Facturas
POSTCrear factura/v1/projects/{project_id}/stores/{store_id}/invoicesGETListar facturas/v1/projects/{project_id}/invoicesGETObtener factura/v1/projects/{project_id}/invoices/{invoice_id}GETListar pagos de una factura/v1/projects/{project_id}/invoices/{invoice_id}/paymentsMétodos de pago
GETListar activos de pago del proyecto/v1/projects/{project_id}/payment-assetsPUTActualizar política de activos del proyecto/v1/projects/{project_id}/payment-assets/{asset_id}GETExplorar tokens candidatos para pagos/v1/projects/{project_id}/payment-token-candidatesPOSTVerificar y registrar token/v1/projects/{project_id}/payment-token-assetsGETBuscar pools DEX de tokens personalizados/v1/projects/{project_id}/payment-token-dex-poolsPOSTAñadir o cambiar precio de token personalizado/v1/projects/{project_id}/payment-token-assets/customGETListar métodos de pago de la tienda/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTSustituir métodos de pago de la tienda/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTDefinir política de confirmaciones de una tienda/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policyWallets
GETListar wallets y saldos del proyecto/v1/projects/{project_id}/walletsConciliación
GETListar excepciones de pago/v1/projects/{project_id}/reconciliationGETLeer evidencia de conciliación/v1/projects/{project_id}/reconciliation/{invoice_id}API de operador
GETCapacidades/v1/operator/capabilitiesGETEstado del servicio/v1/operator/healthGETListar comercios/v1/operator/merchantsPOSTCrear comercio/v1/operator/merchantsGETObtener comercio/v1/operator/merchants/{merchant_id}POSTActualizar comercio/v1/operator/merchants/{merchant_id}GETListar usuarios/v1/operator/merchants/{merchant_id}/usersPOSTCrear usuario/v1/operator/merchants/{merchant_id}/usersGETObtener usuario/v1/operator/merchants/{merchant_id}/users/{user_id}POSTActualizar usuario/v1/operator/merchants/{merchant_id}/users/{user_id}POSTDefinir contraseña de usuario/v1/operator/merchants/{merchant_id}/users/{user_id}/passwordPOSTRevocar sesiones de usuario/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessionsGETListar invitaciones/v1/operator/merchants/{merchant_id}/invitationsPOSTCrear invitación/v1/operator/merchants/{merchant_id}/invitationsGETObtener invitación/v1/operator/invitations/{invitation_id}POSTReenviar invitación/v1/operator/invitations/{invitation_id}/resendPOSTRevocar invitación/v1/operator/invitations/{invitation_id}/revokeGETObtener créditos/v1/operator/merchants/{merchant_id}/creditsGETListar libro mayor de créditos/v1/operator/merchants/{merchant_id}/credits/ledgerPOSTAjustar créditos/v1/operator/merchants/{merchant_id}/credits/adjustmentsGETListar recargas/v1/operator/merchants/{merchant_id}/topupsPOSTCrear recarga/v1/operator/merchants/{merchant_id}/topupsGETObtener recarga/v1/operator/merchants/{merchant_id}/topups/{topup_id}GETInformes/v1/operator/reportsGETListar auditoría/v1/operator/auditGETListar eventos/v1/operator/eventsGETListar webhooks/v1/operator/webhooksPOSTCrear webhook/v1/operator/webhooksPOSTActualizar webhook/v1/operator/webhooks/{webhook_id}POSTRotar secreto de webhook/v1/operator/webhooks/{webhook_id}/rotateGETListar entregas de webhooks/v1/operator/webhooks/{webhook_id}/deliveriesGETListar proyectos/v1/operator/merchants/{merchant_id}/projectsPOSTCrear proyecto/v1/operator/merchants/{merchant_id}/projectsGETObtener proyecto/v1/operator/merchants/{merchant_id}/projects/{project_id}POSTActualizar proyecto/v1/operator/merchants/{merchant_id}/projects/{project_id}GETListar tiendas/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesPOSTCrear tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesGETObtener tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}POSTActualizar tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}GETObtener apariencia de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearancePOSTActualizar apariencia de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceGETListar activos de pago de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsPOSTActualizar activos de pago de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsGETListar webhooks de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOSTCrear webhook de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOSTActualizar webhook de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}GETListar facturas/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoicesGETObtener factura/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}GETListar wallets/v1/operator/merchants/{merchant_id}/projects/{project_id}/walletsGETListar direcciones de wallets/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addressesGETListar credenciales de comercio/v1/operator/merchants/{merchant_id}/api-credentialsPOSTCrear credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentialsPOSTActualizar credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}POSTRotar credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotatePOSTRevocar credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revokePOSTComprobar token de invitación/v1/onboarding/invitations/checkPOSTAceptar invitación o restablecimiento de contraseña/v1/onboarding/invitations/acceptPágina de pago
GETEstructura de la página de pago/GETPágina de pago alojada/invoice/{invoice_id}GETFactura segura para la página de pago/checkout-api/invoices/{invoice_id}GETVista previa del pago de la tienda/invoice/preview/{project_id}GETDatos de vista previa del pago/checkout-api/previews/{project_id}GETImagen de pago de la tienda/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngGETImagen de vista previa de la tienda/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngGETLogo de vista previa con versión/checkout-api/previews/{project_id}/logo/{revision}/image.pngGETImagen QR de pago/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgGETLogo de pago con versión/checkout-api/invoices/{invoice_id}/logo/{revision}/image.pngServicio
GETDescubrimiento del servicio API/GETEstado del servicio/healthzGETCapacidades/v1/operator/capabilitiesSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere health.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/capabilities" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/capabilities", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/capabilities");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/capabilities",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"api_version": "v1",
"operator_version": "7.4.0",
"scopes": [
"health.read"
],
"all_merchants": false,
"merchant_ids": [
"11111111-1111-4111-8111-111111111111"
],
"onboarding": [
"direct",
"invitation"
],
"write_methods": [
"POST"
],
"idempotency_required": true
}GETEstado del servicio/v1/operator/healthSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere health.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/health" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/health", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/health");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/health",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"version": "7.4.0",
"nodes": []
}GETListar comercios/v1/operator/merchantsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchants.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear comercio/v1/operator/merchantsLectura y escritura
Crea atómicamente un comercio alojado y su primer administrador, directamente con contraseña o por invitación.
- Requiere merchants.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Requiere acceso global a comercios. Las comisiones personalizadas explícitas también necesitan fees.write; starting_credit distinto de cero necesita credits.write. La incorporación por invitación también necesita invitations.write. Sin inicio automático de sesión, sin evitar Basic Auth y sin crédito retroactivo al reintentar.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| name, email | string · required | Nombre del comercio y email globalmente único del primer administrador. |
| onboarding | direct | invitation · required | direct requiere password y no envía email de invitación. invitation omite password. |
| password | string · direct only | 12–128 caracteres (máximo 512 bytes UTF-8); nunca se devuelve ni se envía por email. Usa require_password_change para contraseñas temporales. |
| require_password_change | boolean · default false | Exige una contraseña nueva al primer acceso. Cada cuenta creada directamente debe reconocer la custodia de wallets alojadas. |
| currency | fiat code · optional | Moneda de la cuenta prepaga; por defecto usa la moneda regional y no puede cambiar después. |
| fee_bps | integer · optional | 0–10000; 100 significa 1%. Usa el valor de operador por defecto si se omite. Requiere fees.write. |
| starting_credit | decimal string · default 0 | Concesión local exacta de una sola vez. Un valor distinto de cero requiere credits.write. No recarga el saldo de instalación del operador. |
| external_id | string · optional | Referencia única de integración, 1–120 caracteres. |
| default_timezone | IANA timezone · optional | Por defecto usa la zona horaria regional de la instalación. |
| send_invitation_email | boolean · default false | Solo invitación. Requiere SMTP configurado; la respuesta distingue aceptación del relay de creación de cuenta. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"merchant_id": "11111111-1111-4111-8111-111111111111",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant": {
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "0"
},
"onboarding": "direct",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"custody_acceptance_required": true
}GETObtener comercio/v1/operator/merchants/{merchant_id}Solo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchants.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}POSTActualizar comercio/v1/operator/merchants/{merchant_id}Lectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchants.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name, enabled, payments_paused, fee_bps, external_id | optional fields | Desactivar revoca sesiones. payments_paused bloquea nuevas facturas, no el escaneo de pagos existentes. Cambiar comisiones requiere fees.write y afecta a facturas futuras; la moneda de cuenta no puede cambiar. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"payments_paused": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"payments_paused": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"payments_paused": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payments_paused": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false
}GETListar usuarios/v1/operator/merchants/{merchant_id}/usersSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere users.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear usuario/v1/operator/merchants/{merchant_id}/usersLectura y escritura
Añade un administrador de comercio o un usuario limitado a proyectos seleccionados.
- Requiere users.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| email, display_name | strings · required | El email es único en la instalación. |
| onboarding, password, require_password_change, send_invitation_email | same as merchant creation | Crear invitaciones requiere además invitations.write. |
| access_level | admin | projects · default admin | admin solo es administrador de este comercio, nunca de la instalación u operador. |
| project_ids | UUID[] | Solo proyectos propiedad del comercio. Selecciones obligatorias para acceso limitado a proyectos; nunca entre comercios. |
| default_timezone | IANA timezone · optional | Valor regional predeterminado si se omite. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"user": {
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
},
"user_id": "22222222-2222-4222-8222-222222222222",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"merchant_id": "11111111-1111-4111-8111-111111111111"
}GETObtener usuario/v1/operator/merchants/{merchant_id}/users/{user_id}Solo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere users.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| user_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POSTActualizar usuario/v1/operator/merchants/{merchant_id}/users/{user_id}Lectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere users.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| user_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| email, display_name, enabled, access_level, project_ids, default_timezone | optional fields | Actualiza los campos enviados; se mantiene la protección del último administrador. Las contraseñas tienen una operación users.security separada. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"display_name": "Store manager"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"display_name": "Store manager"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"display_name": "Store manager"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"display_name": "Store manager"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POSTDefinir contraseña de usuario/v1/operator/merchants/{merchant_id}/users/{user_id}/passwordLectura y escritura
Define la contraseña de una cuenta alojada y revoca sesiones. Conserva el TOTP existente.
- Requiere users.security; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| user_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| password | string · required | Cambia la contraseña y revoca sesiones, conservando TOTP. Requiere users.security. |
| require_password_change | boolean · default true | El usuario debe definir su propia contraseña en el siguiente acceso correcto. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}POSTRevocar sesiones de usuario/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessionsLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere users.security; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| user_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}GETListar invitaciones/v1/operator/merchants/{merchant_id}/invitationsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere invitations.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear invitación/v1/operator/merchants/{merchant_id}/invitationsLectura y escritura
Crea o sustituye un enlace de invitación o restablecimiento de contraseña de un solo uso.
- Requiere invitations.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| user_id, send_email | UUID, boolean | Emite o sustituye un enlace de un solo uso para una cuenta existente. Los usuarios activados reciben un enlace de restablecimiento de una hora y requieren users.security. |
| new user fields | alternative to user_id | Usa email, display_name, access_level y project_ids para crear un usuario invitado; requiere users.write. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}GETObtener invitación/v1/operator/invitations/{invitation_id}Solo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere invitations.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invitation_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"kind": "invitation",
"status": "pending",
"created_at": "2026-10-01T12:00:00Z",
"expires_at": "2026-10-03T12:00:00Z"
}POSTReenviar invitación/v1/operator/invitations/{invitation_id}/resendLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere invitations.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invitation_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| send_email | boolean · default false | Sustituye el token anterior, nunca añade crédito. Devuelve un enlace recién generado una sola vez. Una cuenta ya activada necesita users.security. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}POSTRevocar invitación/v1/operator/invitations/{invitation_id}/revokeLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere invitations.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invitation_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"revoked": true,
"invitation_id": "44444444-4444-4444-8444-444444444444"
}GETObtener créditos/v1/operator/merchants/{merchant_id}/creditsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere credits.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}GETListar libro mayor de créditos/v1/operator/merchants/{merchant_id}/credits/ledgerSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere credits.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, q | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTAjustar créditos/v1/operator/merchants/{merchant_id}/credits/adjustmentsLectura y escritura
Añade una concesión o corrección con motivo al libro mayor prepago de este comercio.
- Requiere credits.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| amount | signed decimal string · required | Concesión positiva o corrección negativa, hasta seis decimales en la moneda de crédito del comercio. No es una transferencia en blockchain. |
| note | string · required | Motivo conservado en el libro mayor de solo anexado. |
| request_id | UUID · required | Guarda junto con el importe y el motivo, además del Idempotency-Key HTTP. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"balance": "25"
}GETListar recargas/v1/operator/merchants/{merchant_id}/topupsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere topups.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear recarga/v1/operator/merchants/{merchant_id}/topupsLectura y escritura
Crea una página de pago para crédito prepago; nunca la marques como pagada manualmente.
- Requiere topups.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| amount | decimal string · required | Al menos una unidad de la moneda de crédito del comercio. Requiere una tienda receptora de operador lista. |
| request_id | UUID · required | Conserva entre reintentos. Devuelve la factura existente si ya fue creada. El crédito se aplica solo tras observar la liquidación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GETObtener recarga/v1/operator/merchants/{merchant_id}/topups/{topup_id}Solo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere topups.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| topup_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "pending",
"invoice_status": "new",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GETInformes/v1/operator/reportsSolo lectura
Lee el resumen financiero de operador. Requiere acceso a todos los comercios alojados.
- Requiere reports.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| period, start, end, currency, timezone, merchant_id | query · optional | Filtros financieros. period usa last30 por defecto; usa custom con start/end en YYYY-MM-DD. Solo credenciales para todos los comercios. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/reports" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/reports", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/reports");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/reports",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"summary": {
"fees": "10",
"costs": "3",
"margin": "7",
"credits": "25",
"pending": 0,
"missing_rates": 0
},
"merchants": [],
"filters": {
"period": "last30",
"currency": "EUR",
"timezone": "UTC"
},
"basis": "first_settlement_latest_net_fees"
}GETListar auditoría/v1/operator/auditSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere audit.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id, event_type / search | query · optional | Filtra por comercio permitido, tipo exacto de evento (events) o texto de acción (audit). Retención de eventos: 30 días. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/audit" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/audit", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/audit");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/audit",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETListar eventos/v1/operator/eventsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere events.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id, event_type / search | query · optional | Filtra por comercio permitido, tipo exacto de evento (events) o texto de acción (audit). Retención de eventos: 30 días. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/events" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/events", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/events");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/events",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETListar webhooks/v1/operator/webhooksSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere events.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear webhook/v1/operator/webhooksLectura y escritura
Suscríbete a eventos futuros del ciclo de vida de operador. No es un webhook de pagos de tienda.
- Requiere webhooks.write + events.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| url | public HTTPS URL · required | Sin credenciales, IP privadas ni redirecciones. DNS/IP se comprueban otra vez al entregar. |
| events | string[] · required | Elige eventos del ciclo de vida de la guía de operador, no notificaciones de facturas. |
| merchant_ids | UUID[] · optional | Vacío significa todos los comercios permitidos por esta credencial. Se vuelven a comprobar las restricciones de ámbito vigentes. |
| enabled | boolean · default true | Los endpoints en pausa conservan entregas en cola; reactivarlos reanuda el trabajo conservado válido. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}POSTActualizar webhook/v1/operator/webhooks/{webhook_id}Lectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere webhooks.write + events.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| webhook_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| url | public HTTPS URL · required | Sin credenciales, IP privadas ni redirecciones. DNS/IP se comprueban otra vez al entregar. |
| events | string[] · required | Elige eventos del ciclo de vida de la guía de operador, no notificaciones de facturas. |
| merchant_ids | UUID[] · optional | Vacío significa todos los comercios permitidos por esta credencial. Se vuelven a comprobar las restricciones de ámbito vigentes. |
| enabled | boolean · default true | Los endpoints en pausa conservan entregas en cola; reactivarlos reanuda el trabajo conservado válido. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"signing_secret": null
}POSTRotar secreto de webhook/v1/operator/webhooks/{webhook_id}/rotateLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere webhooks.write + events.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| webhook_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}GETListar entregas de webhooks/v1/operator/webhooks/{webhook_id}/deliveriesSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere events.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| webhook_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETListar proyectos/v1/operator/merchants/{merchant_id}/projectsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear proyecto/v1/operator/merchants/{merchant_id}/projectsLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name, slug | strings · required | Nombre e identificador de proyecto único y estable. Crea wallets locales con la inicialización existente del proyecto; nunca transfiere fondos. |
| enabled, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | enabled usa true por defecto; se recomienda crear en pausa y configurar primero una tienda. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GETObtener proyecto/v1/operator/merchants/{merchant_id}/projects/{project_id}Solo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}POSTActualizar proyecto/v1/operator/merchants/{merchant_id}/projects/{project_id}Lectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name, enabled, reporting_currency, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | Actualización parcial. No se pueden cambiar el identificador ni el comercio propietario. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GETListar tiendas/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name, slug | strings · required | Nombre de tienda e identificador estable. |
| default_currency, invoice_expiry_minutes, exchange_rate_spread_percent, underpayment_tolerance_percent | optional | Usa cadenas decimales para porcentajes. Las tiendas nuevas heredan la apariencia de la tienda predeterminada del proyecto. |
| enabled, is_default, allow_zero_amount_invoices, allow_underpayments, allow_overpayments, allowed_chain_slugs | optional | Configura los activos aceptados con payment-assets; las facturas de importe cero están desactivadas por defecto. |
| ipn_enabled, default_ipn_url, default_redirect_url, default_cancel_url, redirect_automatically | optional | Las URL IPN y de retorno siguen la validación URL existente. Sin HTML/JavaScript arbitrario. |
| checkout_language, embed_enabled, allowed_embed_origins, domains | optional | Usa un idioma compatible y dominios de rol activos; configura explícitamente los orígenes de integración. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GETObtener tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}Solo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}POSTActualizar tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}Lectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store fields | optional | Los mismos ajustes modificables que al crear la tienda, excepto slug. Solo cambian los campos enviados. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GETObtener apariencia de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"revision": 1,
"settings": {
"inherit_default_store": true
},
"effective": {
"title": "Pay securely"
}
}POSTActualizar apariencia de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceLectura y escritura
Guarda un diseño de tienda validado y protegido por revisión.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| revision | integer · required | Lee primero la revisión actual con GET. Una revisión antigua falla sin sobrescribir a otro editor. |
| settings | appearance object · required | Apariencia de pago validada, incluidos inherit_default_store, marca, intro/outro, tamaños de letra y visibilidad. Sin HTML/JavaScript arbitrario. Subir bytes de imágenes solo está disponible en consola. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
},
"revision": 2
}GETListar activos de pago de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": []
}POSTActualizar activos de pago de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| assets | array · required | Sustitución completa de métodos en blockchain: UUID asset_id y display_order. [] borra los activos en blockchain aceptados. Solo activos verificados del proyecto; no configura Lightning. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": []
}GETListar webhooks de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear webhook de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name, url, event_types | strings / array · required | Receptor HTTPS público y nombres de eventos de factura de la documentación IPN y webhooks. |
| enabled, automatic_redelivery | booleans · default true | La creación devuelve el secreto de firma una sola vez. Son notificaciones de pago de tienda, no eventos del ciclo de vida de operador. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"signing_secret": "EXAMPLE_ONLY_SAVE_ONCE",
"endpoint": {
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
},
"secret_visible_once": true
}POSTActualizar webhook de la tienda/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}Lectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere projects.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| store_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| webhook_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name, url, event_types | strings / array · required | Receptor HTTPS público y nombres de eventos de factura de la documentación IPN y webhooks. |
| enabled, automatic_redelivery | booleans · default true | La creación devuelve el secreto de firma una sola vez. Son notificaciones de pago de tienda, no eventos del ciclo de vida de operador. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
}GETListar facturas/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoicesSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere reports.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| limit, offset, search, status, store_id | query · optional | Paginación y filtros de facturas, igual que en la lista de facturas del proyecto. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"pagination": {
"limit": 25,
"offset": 0,
"total": 0,
"has_more": false
}
}GETObtener factura/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}Solo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere reports.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
- invoice_id es el ID público de factura devuelto al crearla y en notificaciones, no el id interno.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| invoice_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"invoice_id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "new",
"metadata": {},
"payment_intents": []
}GETListar wallets/v1/operator/merchants/{merchant_id}/projects/{project_id}/walletsSolo lectura
Lee saldos públicos de wallets en caché, nunca claves privadas ni frases de recuperación.
- Requiere reports.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los saldos son observaciones en caché con campos de actualización, no una garantía de saldo gastable. El envío y la exportación de claves no están disponibles mediante esta API.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETListar direcciones de wallets/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addressesSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere reports.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| project_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| wallet_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| limit, before, search, has_balance, hide_small_balances | query · optional | Límite 1–50, predeterminado 25. Pasa next_cursor como before para la página siguiente. Omite before para la página 1. has_balance=false y hide_small_balances=false incluyen saldos vacíos/pequeños. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"wallet": {
"id": "22222222-2222-4222-8222-222222222222",
"chain_slug": "ethereum"
},
"items": [],
"total": 0,
"total_pages": 1,
"next_cursor": null,
"reporting_currency": "EUR",
"has_balance": true,
"hide_small_balances": true,
"small_balance_threshold": {
"amount": "0.20",
"currency": "EUR"
}
}GETListar credenciales de comercio/v1/operator/merchants/{merchant_id}/api-credentialsSolo lectura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchant_credentials.read; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| page, search | query · optional | Páginas desde 1, 25 elementos por página. Se admite búsqueda en comercios, usuarios, proyectos, tiendas, wallets, credenciales y webhooks; las listas nativas de eventos usan sus filtros específicos. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCrear credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentialsLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchant_credentials.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name | string · required | Etiqueta de una clave de comercio habitual nueva, no de operador. |
| access_level | read_only | read_write · default read_only | Lectura/escritura habilita el contrato existente de la API de comercio. |
| project_ids | UUID[] | Solo proyectos propiedad del comercio seleccionado; una lista vacía sigue la política existente de todos los proyectos del comercio. |
| enabled, ip_restriction_enabled, allowed_ips, requests_per_minute | optional | Controles existentes de claves de comercio. El secreto se devuelve una sola vez; requiere merchant_credentials.write. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 o 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POSTActualizar credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}Lectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchant_credentials.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| credential_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| name, access_level, enabled, ip_restriction_enabled, allowed_ips | required fields | Envía la configuración completa actual de la credencial con los cambios. project_ids usa [] por defecto; requests_per_minute usa la cuota de la API de comercio. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
}POSTRotar credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotateLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchant_credentials.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| credential_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POSTRevocar credencial de comercio/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revokeLectura y escritura
Gestiona o inspecciona el recurso indicado del comercio alojado usando una credencial de operador separada.
- Requiere merchant_credentials.write; solo comercios alojados permitidos. Las claves de operador no acceden al espacio del negocio del propietario.
- Guarda un Idempotency-Key único y el cuerpo exacto antes de enviar. Los reintentos nunca repiten una acción confirmada. Los campos secretos se omiten al repetir; si se perdió una respuesta con secretos, inspecciona el recurso creado y rota/reemite explícitamente. Un 409 operator_request_in_progress puede significar una solicitud interrumpida con resultado desconocido: inspecciona recurso/auditoría; no reintentes a ciegas con una clave nueva.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatorio | 16–128 letras, dígitos, -, _ o .; guardada para esta operación |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| merchant_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
| credential_id | path UUID | UUID canónico del recurso en minúsculas; debe pertenecer al ámbito de comercios de la credencial. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"revoked": true
}POSTComprobar token de invitación/v1/onboarding/invitations/checkPúblico
Incorporación solo por token. No acepta una clave de operador ni inicia sesión automáticamente. El acceso a consola sigue requiriendo Basic Auth del sitio y el TOTP existente.
- Invitación de 48 horas; enlace de restablecimiento de contraseña de una hora. Tokens de un solo uso almacenados como hash. Reemitir revoca el enlace anterior. Aceptar conserva TOTP y revoca sesiones antiguas.
- Sin reintentos automáticos. Si se agota el tiempo al aceptar, comprueba el estado del enlace e intenta acceder; no supongas que falló. Limitado por la IP de origen observada. El destinatario debe dar su propio reconocimiento de custodia.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| token | string · required | Secreto del fragmento de la URL de invitación. Nunca lo registres en logs. |
Solicitud
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/check" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/check", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/check");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/check",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"kind": "invitation",
"email": "admin@example.test",
"merchant_name": "Example shop"
}POSTAceptar invitación o restablecimiento de contraseña/v1/onboarding/invitations/acceptPúblico
Incorporación solo por token. No acepta una clave de operador ni inicia sesión automáticamente. El acceso a consola sigue requiriendo Basic Auth del sitio y el TOTP existente.
- Invitación de 48 horas; enlace de restablecimiento de contraseña de una hora. Tokens de un solo uso almacenados como hash. Reemitir revoca el enlace anterior. Aceptar conserva TOTP y revoca sesiones antiguas.
- Sin reintentos automáticos. Si se agota el tiempo al aceptar, comprueba el estado del enlace e intenta acceder; no supongas que falló. Limitado por la IP de origen observada. El destinatario debe dar su propio reconocimiento de custodia.
- Los ejemplos de respuesta muestran campos seleccionados. Trata los campos adicionales de respuesta como adiciones compatibles.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| token | string · required | Secreto del fragmento de la URL de invitación. Nunca lo registres en logs. |
| password | string · required | Contraseña nueva, 12–128 caracteres (máximo 512 bytes UTF-8). |
| custody_acknowledged | boolean | Debe ser true al aceptar una invitación nueva de wallet alojada. |
Solicitud
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/accept" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/accept", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/accept");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/accept",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"password_set": true
}GETListar excepciones de pago/v1/projects/{project_id}/reconciliationSolo lectura
Una cola de revisión paginada para pagos insuficientes, excesivos, tardíos, reorganizados o ambiguos, entregas fallidas y métodos desactivados/vencidos. Los casos reconocidos por un operador se reabren al llegar evidencia nueva.
- Solo lectura, limitado al proyecto y cubierto por la cuota de la credencial. Las decisiones financieras y reembolsos siguen siendo exclusivos de la consola.
- Las filas contienen id (UUID interno), invoice_id (UUID público, igual que en notificaciones), información de tienda, importe/moneda fiat originales, invoice_status, estado del caso, motivos, revisión y updated_at. Usa invoice_id en el endpoint de detalle de comercio.
- La detección automática sigue la ventana original de seguimiento de la factura; Volver a escanear amplía la observación una hora sin habilitar el pago. Los métodos liquidados/cancelados continúan bajo seguimiento dentro de esa ventana.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado a esta credencial. |
| status | query string | open (predeterminado), resolved o all. |
| reason | query string | underpaid, overpaid, late, reorged, ambiguous, delivery_failed, disabled_method o expired_method. |
| search | query string | Hasta 100 caracteres: ID de factura, pedido, cliente o tienda. |
| store_id | query UUID | Filtro opcional de tienda. |
| page | query integer | 1–40001. 25 casos fijos por página. |
Respuesta de la cola de excepciones
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| data | ExceptionRow[] | siempre | Primero los casos actualizados más recientemente. Usa invoice_id, no el id interno, en las URL de detalle de comercio. |
| pagination | object | siempre | page (1–40001), per_page (25), total de filas coincidentes, has_more. |
| counts | object | siempre | Totales open y resolved de todo el proyecto, independientes de los filtros actuales. |
ExceptionRow
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id / invoice_id | UUID | siempre | ID interno del registro / UUID de factura visible para el cliente. invoice_id coincide con los datos de notificación. |
| store_id / store_name | UUID / string | siempre | Tienda propietaria. |
| order_id / email | string | null | siempre | Referencia privada del pedido del comercio y email del cliente. |
| amount / currency | decimal string / string | siempre | Importe y moneda fiat originales de factura. |
| invoice_status | invoice status | siempre | Estado actual del ciclo de vida del pago. |
| status / reasons | open|resolved / string[] | siempre | Estado del caso y tipos de excepción enumerados en el filtro reason. |
| revision / updated_at | integer / timestamp | siempre | Revisión actual y hora de actualización de la revisión. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{"data":[],"pagination":{"page":1,"per_page":25,"total":0,"has_more":false},"counts":{"open":0,"resolved":0}}GETLeer evidencia de conciliación/v1/projects/{project_id}/reconciliation/{invoice_id}Solo lectura
Devuelve factura, caso, totales exactos por método e importes disponibles para reembolso, transacciones observadas, historial de entregas, decisiones del comercio y transferencias de reembolso vinculadas. Nunca expone claves de firma ni secretos de notificación.
- case es null cuando la factura no ha generado una excepción. Se devuelven las 100 observaciones y 50 entregas más recientes; el historial de decisiones está paginado.
- refundable_atomic requiere al menos una confirmación de red, excluye reservas de reembolso existentes y no promete fondos gastables en la wallet. Una cotización en vivo valida además la disponibilidad de la wallet, saldos de origen y comisiones.
- Un reembolso transmitido significa enviado a un endpoint de red, no recepción del cliente confirmada independientemente. Las comisiones son adicionales y las de procesamiento fiat no se acreditan automáticamente al reembolsar.
- Menú del proyecto en consola → Requiere atención ofrece cancelar, aceptar, rechazar, reabrir, revisar, notas, Volver a escanear, reintento de entrega y reembolsos en redes compatibles. Las decisiones usan sesiones protegidas por CSRF, un request_id único, la revisión actual del caso, nota obligatoria y confirmación explícita; los tokens bearer no pueden invocar esas modificaciones.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado. |
| invoice_id | path UUID | UUID público de factura, no id interno. |
| page | query integer | Página de historial de decisiones, desde 1; 25 decisiones por página. |
Respuesta de conciliación
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| invoice | InvoiceDetail | siempre | Factura completa de comercio: campos de resumen, metadatos privados y payment_intents. Sin envoltorio data. |
| case | object | null | siempre | Caso actual con estado, motivos, revisión y marcas de tiempo; null sin excepción. Se excluye la evidencia interna. |
| methods | object[] | siempre | id, wallet_id, asset_id, symbol, chain, decimals, expected_atomic, received_atomic, confirmed_atomic, refundable_atomic, address, tag, monitor_error, last_checked_at, monitoring_expires_at y spending_supported. Los importes atómicos son cadenas. |
| history | object[] | siempre | Las 25 decisiones más recientes de esta página: id, action, note, actor, result, created_at. |
| history_pagination | object | siempre | page, per_page (25), total. Solo el historial de decisiones se pagina por page. |
| refunds | object[] | siempre | Los 100 reembolsos más recientes: id, payment_intent_id, amount_atomic, destination, status, request, treasury_intent_id, transfer_status, created_at y transactions (id/status). Enviar reembolsos solo está disponible en consola. |
| observations | object[] | siempre | Los 100 más recientes: payment_intent_id, transaction_id, event_index, amount, status, confirmations, observed_at, symbol, chain y disabled_at_detection. Se incluyen explorer_name/explorer_url cuando se admite. |
| deliveries | object[] | siempre | Las 50 más recientes: id, kind, status, attempts, response_status, error, next_attempt_at, event_type y created_at. Sin secretos de notificación. |
Resumen de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | UUID interno de factura. No lo uses en rutas de detalle de comercio ni de pago. |
| invoice_id | UUID | siempre | UUID público de factura usado por rutas de detalle de comercio y pago. |
| project_id | UUID | siempre | Proyecto propietario. |
| store_id | UUID | siempre | Tienda propietaria. |
| source | manual | api | siempre | Cómo se creó la factura. |
| order_id | string | null | siempre | Referencia de pedido del comercio. |
| string | null | siempre | Email de cliente solo para el comercio. Nunca se devuelve en el pago público. | |
| customer_name | string | null | siempre | Nombre visible derivado de metadatos privados firstname, lastname y company. |
| customer_address | string | null | siempre | Dirección en una línea para el comercio derivada de metadatos privados company, street, street2, zip, city, country, countryiso2 y vatid. |
| description | string | null | siempre | Descripción visible para el cliente. |
| amount | decimal string | siempre | Importe canónico de factura. |
| currency | string | siempre | Código normalizado de moneda/activo de factura. |
| exchange_rate_spread_percent | decimal string | siempre | Margen de cotización fijado: el valor personalizado al crear o el de la tienda si se omite. Se aplica antes del redondeo al alza; nunca cambia en esta factura. |
| underpayment_tolerance_percent | decimal string | siempre | Porcentaje inmutable de diferencia por defecto aceptada, capturado al crear la factura. |
| status | invoice status | siempre | new, processing, settled, expired, invalid o cancelled. |
| amount_status | amount status | siempre | none, partial, paid u overpaid. Una factura de importe cero permitida expresamente se liquida con none y sin métodos de pago. |
| timing_status | timing status | siempre | on_time o late. |
| resolution | resolution | siempre | automatic, manually_settled o manually_invalidated. |
| sequence | integer | siempre | Secuencia monótona del estado de factura, desde 1. |
| winning_payment_intent_id | UUID | null | siempre | Método de pago que resolvió la factura, si está seleccionado. |
| expires_at | RFC 3339 timestamp | siempre | Plazo de cotización/pago. |
| monitoring_expires_at | RFC 3339 timestamp | siempre | Último límite configurado de seguimiento tardío entre los métodos de pago. |
| settled_at | timestamp | null | siempre | Hora de liquidación cuando está liquidada. |
| cancelled_at | timestamp | null | siempre | Hora de cancelación cuando está cancelada. |
| archived_at | timestamp | null | siempre | Hora de archivo cuando está archivada. |
| created_at | RFC 3339 timestamp | siempre | Hora de creación. |
| updated_at | RFC 3339 timestamp | siempre | Hora de la última actualización de estado. |
Datos adicionales del detalle de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ipn_url | string | null | siempre | Destino IPN efectivo por factura. Solo en respuesta al comercio; se omite en el pago público. |
| redirect_url | string | null | siempre | URL efectiva de éxito usada tras liquidar. |
| cancel_url | string | null | siempre | URL efectiva de retorno cuando el pago termina sin éxito. |
| redirect_automatically | boolean | siempre | Si la página de pago debe redirigir automáticamente tras el éxito. |
| checkout_language | string | siempre | Etiqueta efectiva de idioma de la página de pago. |
| metadata | object | siempre | Metadatos del comercio. Nunca se devuelven en el pago público. |
| payment_intents | PaymentIntent[] | siempre | Métodos de pago cotizados y estado de seguimiento. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{"invoice":{"invoice_id":"YOUR_PUBLIC_INVOICE_ID","status":"processing"},"case":{"status":"open","reasons":["underpaid"],"revision":1},"methods":[],"history":[],"history_pagination":{"page":1,"per_page":25,"total":0},"refunds":[],"observations":[],"deliveries":[]}GETDescubrimiento del servicio API/Público
Respuesta de entrada del host API gestionado que confirma el rol de API pública v1. La produce el proxy gestionado, no el router Axum del comercio.
- No se necesita token bearer.
- Solo el host API gestionado garantiza esta respuesta exacta en la raíz.
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"service": "Wholly Crypto API",
"status": "ready",
"version": "v1"
}GETEstado del servicio/healthzPúblico
Comprueba acceso a la aplicación y un ping de dos segundos a la base de datos. Úsalo para supervisión, no como sustituto del estado de factura.
- No se necesita token bearer.
- El valor de versión es la versión del paquete en ejecución, no la versión de la ruta API.
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/healthz"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/healthz", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/healthz");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/healthz",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 en buen estado; 503 base de datos no disponible
{
"status": "ok",
"database": "ok",
"version": "0.1.0"
}GETListar activos de pago del proyecto/v1/projects/{project_id}/payment-assetsSolo lectura
Lista activos nativos y tokens verificados con política del proyecto, disponibilidad de wallets de red y capacidades instaladas de escaneo/saldos. scanner_ready es un requisito del adaptador compilado, no un resultado de cuórum de endpoints en vivo. Desde 6.0.6, la creación conserva los métodos configurados durante interrupciones del escáner. La verificación de recepción sigue necesitando el umbral configurado de proveedores sanos con rol exacto (2 por defecto, 1 opcional).
- Un token puede estar listado globalmente y no poder seleccionarse si scanner_ready o payment_supported es false.
- La matriz de capacidades del operador también requiere el rol exacto de endpoint del escáner; no cuenta un endpoint sano que sirva una API incompatible.
- Los tokens comparten la wallet de proyecto de su red nativa; no crean otra frase semilla.
- Los resúmenes de wallet incluidos solo indican disponibilidad y dejan saldos vacíos; usa GET /v1/projects/{project_id}/wallets para saldos enriquecidos.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto habilitado asignado a la credencial. |
PaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | Identificador persistente de activo de pago usado por rutas de política de proyecto y tienda. |
| asset_key | string | siempre | Identidad canónica de activo nativo o de contrato con estilo CAIP. |
| chain_slug / network | string | siempre | Identificador de cadena Wholly Crypto y red configurada. |
| caip_network_id / caip_asset_id | string / string|null | siempre | Identidades canónicas de red y activo. |
| asset_kind | native | token | siempre | Si la liquidación usa la moneda de red o un contrato/mint verificado. |
| payment_rail | string | siempre | Vía de ejecución: utxo, evm-native, solana-native, account-native, privacy-native o token-transfer. |
| symbol / name / decimals | string / string / integer | siempre | Identidad visual y precisión exacta de unidades atómicas. |
| contract_address | string | null | siempre | Contrato ERC-20 o mint SPL canónico para tokens; null para activos nativos. |
| coingecko_id | string | null | siempre | Identidad de descubrimiento/precios. Null para contratos personalizados; nunca deduzcas un precio de mercado de su símbolo. Los metadatos CoinGecko por sí solos nunca hacen seleccionable un token. |
| custom_token | boolean | siempre | Contrato personalizado verificado en blockchain con precio fijo USD o pool DEX seleccionado, limitado al proyecto. |
| icon_path | path | null | siempre | Icono del token en caché local cuando está disponible. |
| token_standard | erc20 | spl-token | null | siempre | Estándar de token verificado en ejecución; null para activos nativos. |
| metadata_verified_at | timestamp | null | siempre | Hora de verificación de metadatos en blockchain para tokens registrados. |
| payment_supported / scanner_ready / balance_ready | boolean | siempre | Condiciones del registro en compilación. scanner_ready significa que está instalado el escáner de pagos; confirmar pagos requiere el número configurado de proveedores sanos con rol exacto (2 por defecto, 1 opcional); la disponibilidad temporal del escáner no bloquea crear facturas desde 6.0.6. balance_ready solo es true para adaptadores de saldo implementados. |
| default_finality_mode | confirmations | finalized | siempre | Modelo predeterminado de finalidad que hereda una política nueva de proyecto. |
| default_required_confirmations / default_monitoring_minutes | integer | siempre | Política predeterminada de confirmaciones y seguimiento. |
ProjectPaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| asset | PaymentAsset | siempre | Activo nativo persistente o token verificado. |
| policy | ProjectAssetPolicy | null | siempre | Política de activación/finalidad del proyecto, o null sin configurar. Incluye custom_price_mode (fixed/dex), custom_price_usd (cadena decimal fija o null), custom_dex_pair (pool seleccionado o null) y custom_dex (dex_id, quote_symbol, price_usd actual o null, liquidity_usd, fetched_at, last_error). Las tiendas de este proyecto comparten los precios personalizados. |
| wallet | WalletSummary | null | siempre | Wallet de proyecto sin custodia de la red. Los tokens comparten la wallet nativa de su red. |
| wallet_readiness | readiness enum | siempre | unsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required o ready. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Evaluación compartida de configuración de recepción del proyecto. Incluye comprobaciones de wallet y proveedores independientes de escaneo, separadas de la actualidad de saldos y el gas para envíos. Null si no existe política de proyecto. La moneda y los tipos de cambio se comprueban al crear una factura. |
WalletSummary
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | siempre | Identificadores de wallet, proyecto propietario y activo nativo de la red. |
| chain_slug / network | string | siempre | Cadena y red de la wallet. |
| asset_symbol / asset_name | string | siempre | Identidad visual nativa de la red. |
| status | pending | active | disabled | error | siempre | Estado operativo de la wallet. |
| label | string | siempre | Etiqueta del operador. |
| public_key / primary_address | string | null | siempre | Identidad pública de wallet; no expone frase semilla ni clave privada. |
| derivation_scheme / address_format | string | null | siempre | Política y formato de direcciones. |
| backup_confirmed_at | timestamp | null | siempre | Distinto de null después de que el operador confirme la copia de recuperación. |
| activation_required / activation_verified_at | boolean / timestamp|null | siempre | Las cuentas compartidas XRP y Stellar siguen sin estar disponibles hasta que el operador aporte fondos a la dirección mostrada y los proveedores de escaneo configurados verifiquen esa cuenta exacta. La prueba persistente no caduca; el estado vivo del escáner se comprueba por separado para verificar pagos, no para crear facturas. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Incluido en listas de wallets: configuración receptora del proyecto y requisitos previos del escáner de red. Separado de saldos, gas de tokens y disponibilidad de envío. Otras respuestas de wallet pueden dejarlo null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | siempre | Estado del vínculo externo wallet-RPC de solo lectura de Monero, sin datos sensibles. Incluye endpoint, modo de autenticación, dirección principal de cuenta 0, indicadores/alturas de prueba técnica y fechas de declaración del operador; nunca se serializan credenciales, claves ni archivos de wallet. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | siempre | Metadatos de auditoría de revelación de secretos en consola. |
| next_receive_index | integer | siempre | Siguiente índice reservado de dirección derivada. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | siempre | Estado del escáner de wallet. |
| balances | WalletAssetBalance[] | siempre | Saldos en caché de cada una de las 30 vías nativas, además de activos ERC-20 y SPL verificados. Monero requiere una wallet-RPC externa de solo lectura configurada. |
| total_value_usd | decimal string | null | siempre | Suma orientativa de saldos con precio USD actual. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | siempre | Actualidad agregada de la caché; unknown es una alternativa defensiva y ninguno de estos estados demuestra liquidación de factura. |
| balance_checked_at | timestamp | null | siempre | Comprobación correcta de saldo relevante más antigua representada en el agregado. |
| recent_payments | WalletRecentPayment[] | siempre | Hasta las tres observaciones válidas detected, confirming o final más recientes atribuidas a esta wallet exacta. |
| created_at / updated_at | RFC 3339 timestamp | siempre | Hora de creación y última actualización de wallet. |
ReceiveReadiness
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ready | boolean | siempre | Las comprobaciones de configuración de recepción pasan. No describe disponibilidad de gasto, gas, actualización de saldos ni una cotización futura garantizada. |
| invoice_creatable | boolean | 6.0.6+ | La configuración permite un método de factura pese a avisos temporales del escáner. El precio de moneda se comprueba al crear. Esto no verifica pagos: ready puede ser false mientras invoice_creatable es true. Las wallets ausentes, políticas desactivadas y adaptadores no compatibles siguen fallando de forma segura. |
| checked_at | timestamp | siempre | Hora de evaluación. Listar no hace solicitudes de red ni asigna direcciones. |
| issues | PaymentMethodIssue[] | siempre | Vacío cuando está listo; en otro caso, aviso de recepción o bloqueo de configuración. Comprueba invoice_creatable para distinguir avisos temporales del escáner de fallos de configuración de factura. |
PaymentMethodIssue
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | cuando se conoce | Identifica la red y activo afectados. Lightning puede omitir asset_id. |
| reason_code | string | siempre | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled o asset_not_accepted. |
| message / action | string | cuando está disponible | Explicación para el comercio e identificador de acción: chain_connections, wallets, rates, payment_methods, project_settings o store_settings. Sin credenciales ni URL privadas de proveedores. |
| required_endpoint_role | string | null | en blockchain | Rol API preferido del escáner (campo heredado). Usa accepted_endpoint_roles para la lista completa de compatibilidad. El estado básico de un nodo no demuestra soporte de historial de pagos. |
| accepted_endpoint_roles | string[] | null | en blockchain | Dialectos API compatibles, no prueba de historial ni capacidad del endpoint. node-rpc directo admite BTC/BCH/LTC/DOGE/DASH y ZEC transparente (bloques completos decodificados, 1–48 confirmaciones), TRX nativo solidificado, ALGO nativo mediante algod, XTZ mediante Octez, DOT finalizado de Asset Hub mediante metadatos SCALE y XLM nativo mediante Stellar RPC con ID memo de factura. El historial podado o incompleto no sirve. Estos adaptadores directos no añaden vías de tokens. Las API indexadas siguen siendo alternativas; consulta la tabla de vías de abajo. Las fuentes directas/indexadas mixtas verifican ventanas limitadas de forma independiente; el valor predeterminado sigue siendo dos proveedores independientes, no alias de un mismo operador. La altura básica de nodo, información de red ORDnet y un relay EVM para una vía no EVM no son pruebas de recepción. Monero sigue necesitando una wallet-RPC de solo lectura vinculada al proyecto. |
| healthy_endpoints | integer | en blockchain | Endpoints sanos coincidentes, no el número de proveedores independientes. |
| usable_independent_providers / required_independent_providers | integer | en blockchain | Plazas de verificación utilizables, limitadas a dos. required_independent_providers es el ajuste de red: 2 por defecto o 1 tras elección expresa del administrador. En modo de dos proveedores se requieren claves de proveedor Y hosts diferentes. Las fuentes desactivadas, antiguas (más de diez minutos) o en pausa no ocupan una plaza. Lightning usa sus propias reglas de conexión. |
| last_checked_at | timestamp | null | en blockchain | Última comprobación de estado de endpoint coincidente, separada de la hora de evaluación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{
"asset": {
"id": "10000000-0000-4000-8000-000000000003",
"asset_key": "eip155:1/slip44:60",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"caip_asset_id": "eip155:1/slip44:60",
"asset_kind": "native",
"payment_rail": "evm-native",
"symbol": "ETH",
"name": "Ethereum",
"decimals": 18,
"contract_address": null,
"coingecko_id": "ethereum",
"icon_path": "/assets/coingecko/ethereum.png",
"token_standard": null,
"metadata_verified_at": null,
"payment_supported": true,
"scanner_ready": true,
"balance_ready": true,
"default_finality_mode": "confirmations",
"default_required_confirmations": 12,
"default_monitoring_minutes": 60
},
"policy": {
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 12,
"monitoring_minutes": 60,
"late_monitoring_days": 30
},
"wallet": null,
"wallet_readiness": "wallet_missing",
"receive_readiness": { "ready": false, "invoice_creatable": false, "checked_at": "2026-09-16T09:00:00Z", "issues": [{ "chain_slug": "ethereum", "asset_id": "10000000-0000-4000-8000-000000000003", "asset_ticker": "ETH", "reason_code": "wallet_missing", "message": "ethereum / ETH: Create a project wallet for this chain.", "action": "wallets" }] }
}
]
}PUTActualizar política de activos del proyecto/v1/projects/{project_id}/payment-assets/{asset_id}Lectura y escritura
Crea o sustituye la política de proyecto para un activo persistente y devuelve la lista actualizada de activos del proyecto. Desactivar una red nativa hace que su activo y tokens no estén disponibles para nuevas facturas, pero conserva políticas de tokens, wallets y selecciones de tienda para reanudarlas después.
- El cuerpo sustituye la política completa y rechaza campos desconocidos.
- Activar en el proyecto no selecciona por sí solo el activo para ninguna tienda.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatorio | application/json |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto habilitado asignado a la credencial. |
| asset_id | path UUID | ID de activo devuelto por la lista de activos del proyecto o el registro de tokens. |
Actualización de política de activos del proyecto
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| enabled | boolean | obligatorio | Activa o desactiva el activo para el proyecto. La red nativa debe activarse antes que cualquier token. |
| finality_mode | confirmations | finalized | obligatorio | Política de finalidad compatible con la vía del activo. finalized requiere required_confirmations=1. |
| required_confirmations | integer | obligatorio | Las vías Bitcoin y EVM aceptan cero; otras vías de confirmación requieren al menos una, las vías solo finalized exigen exactamente una y las EVM se limitan a 0–48 para mantener cada transferencia dentro de la ventana de repetición de transacciones. |
| monitoring_minutes | integer | obligatorio | Ventana de sondeo de 1–10,080 minutos mientras una factura está activa. |
| late_monitoring_days | integer | obligatorio | 0–3,650 días de seguimiento tras vencer la factura. |
PaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | Identificador persistente de activo de pago usado por rutas de política de proyecto y tienda. |
| asset_key | string | siempre | Identidad canónica de activo nativo o de contrato con estilo CAIP. |
| chain_slug / network | string | siempre | Identificador de cadena Wholly Crypto y red configurada. |
| caip_network_id / caip_asset_id | string / string|null | siempre | Identidades canónicas de red y activo. |
| asset_kind | native | token | siempre | Si la liquidación usa la moneda de red o un contrato/mint verificado. |
| payment_rail | string | siempre | Vía de ejecución: utxo, evm-native, solana-native, account-native, privacy-native o token-transfer. |
| symbol / name / decimals | string / string / integer | siempre | Identidad visual y precisión exacta de unidades atómicas. |
| contract_address | string | null | siempre | Contrato ERC-20 o mint SPL canónico para tokens; null para activos nativos. |
| coingecko_id | string | null | siempre | Identidad de descubrimiento/precios. Null para contratos personalizados; nunca deduzcas un precio de mercado de su símbolo. Los metadatos CoinGecko por sí solos nunca hacen seleccionable un token. |
| custom_token | boolean | siempre | Contrato personalizado verificado en blockchain con precio fijo USD o pool DEX seleccionado, limitado al proyecto. |
| icon_path | path | null | siempre | Icono del token en caché local cuando está disponible. |
| token_standard | erc20 | spl-token | null | siempre | Estándar de token verificado en ejecución; null para activos nativos. |
| metadata_verified_at | timestamp | null | siempre | Hora de verificación de metadatos en blockchain para tokens registrados. |
| payment_supported / scanner_ready / balance_ready | boolean | siempre | Condiciones del registro en compilación. scanner_ready significa que está instalado el escáner de pagos; confirmar pagos requiere el número configurado de proveedores sanos con rol exacto (2 por defecto, 1 opcional); la disponibilidad temporal del escáner no bloquea crear facturas desde 6.0.6. balance_ready solo es true para adaptadores de saldo implementados. |
| default_finality_mode | confirmations | finalized | siempre | Modelo predeterminado de finalidad que hereda una política nueva de proyecto. |
| default_required_confirmations / default_monitoring_minutes | integer | siempre | Política predeterminada de confirmaciones y seguimiento. |
ProjectPaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| asset | PaymentAsset | siempre | Activo nativo persistente o token verificado. |
| policy | ProjectAssetPolicy | null | siempre | Política de activación/finalidad del proyecto, o null sin configurar. Incluye custom_price_mode (fixed/dex), custom_price_usd (cadena decimal fija o null), custom_dex_pair (pool seleccionado o null) y custom_dex (dex_id, quote_symbol, price_usd actual o null, liquidity_usd, fetched_at, last_error). Las tiendas de este proyecto comparten los precios personalizados. |
| wallet | WalletSummary | null | siempre | Wallet de proyecto sin custodia de la red. Los tokens comparten la wallet nativa de su red. |
| wallet_readiness | readiness enum | siempre | unsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required o ready. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Evaluación compartida de configuración de recepción del proyecto. Incluye comprobaciones de wallet y proveedores independientes de escaneo, separadas de la actualidad de saldos y el gas para envíos. Null si no existe política de proyecto. La moneda y los tipos de cambio se comprueban al crear una factura. |
ReceiveReadiness
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ready | boolean | siempre | Las comprobaciones de configuración de recepción pasan. No describe disponibilidad de gasto, gas, actualización de saldos ni una cotización futura garantizada. |
| invoice_creatable | boolean | 6.0.6+ | La configuración permite un método de factura pese a avisos temporales del escáner. El precio de moneda se comprueba al crear. Esto no verifica pagos: ready puede ser false mientras invoice_creatable es true. Las wallets ausentes, políticas desactivadas y adaptadores no compatibles siguen fallando de forma segura. |
| checked_at | timestamp | siempre | Hora de evaluación. Listar no hace solicitudes de red ni asigna direcciones. |
| issues | PaymentMethodIssue[] | siempre | Vacío cuando está listo; en otro caso, aviso de recepción o bloqueo de configuración. Comprueba invoice_creatable para distinguir avisos temporales del escáner de fallos de configuración de factura. |
PaymentMethodIssue
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | cuando se conoce | Identifica la red y activo afectados. Lightning puede omitir asset_id. |
| reason_code | string | siempre | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled o asset_not_accepted. |
| message / action | string | cuando está disponible | Explicación para el comercio e identificador de acción: chain_connections, wallets, rates, payment_methods, project_settings o store_settings. Sin credenciales ni URL privadas de proveedores. |
| required_endpoint_role | string | null | en blockchain | Rol API preferido del escáner (campo heredado). Usa accepted_endpoint_roles para la lista completa de compatibilidad. El estado básico de un nodo no demuestra soporte de historial de pagos. |
| accepted_endpoint_roles | string[] | null | en blockchain | Dialectos API compatibles, no prueba de historial ni capacidad del endpoint. node-rpc directo admite BTC/BCH/LTC/DOGE/DASH y ZEC transparente (bloques completos decodificados, 1–48 confirmaciones), TRX nativo solidificado, ALGO nativo mediante algod, XTZ mediante Octez, DOT finalizado de Asset Hub mediante metadatos SCALE y XLM nativo mediante Stellar RPC con ID memo de factura. El historial podado o incompleto no sirve. Estos adaptadores directos no añaden vías de tokens. Las API indexadas siguen siendo alternativas; consulta la tabla de vías de abajo. Las fuentes directas/indexadas mixtas verifican ventanas limitadas de forma independiente; el valor predeterminado sigue siendo dos proveedores independientes, no alias de un mismo operador. La altura básica de nodo, información de red ORDnet y un relay EVM para una vía no EVM no son pruebas de recepción. Monero sigue necesitando una wallet-RPC de solo lectura vinculada al proyecto. |
| healthy_endpoints | integer | en blockchain | Endpoints sanos coincidentes, no el número de proveedores independientes. |
| usable_independent_providers / required_independent_providers | integer | en blockchain | Plazas de verificación utilizables, limitadas a dos. required_independent_providers es el ajuste de red: 2 por defecto o 1 tras elección expresa del administrador. En modo de dos proveedores se requieren claves de proveedor Y hosts diferentes. Las fuentes desactivadas, antiguas (más de diez minutos) o en pausa no ocupan una plaza. Lightning usa sus propias reglas de conexión. |
| last_checked_at | timestamp | null | en blockchain | Última comprobación de estado de endpoint coincidente, separada de la hora de evaluación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "symbol": "USDC", "asset_kind": "token", "scanner_ready": true }, "policy": { "enabled": true, "finality_mode": "confirmations", "required_confirmations": 2, "monitoring_minutes": 60, "late_monitoring_days": 30 }, "wallet_readiness": "ready" }
]
}GETExplorar tokens candidatos para pagos/v1/projects/{project_id}/payment-token-candidatesSolo lectura
Busca correspondencias de contratos CoinGecko en caché local solo en redes con escáner de facturas de tokens y adaptador de saldos implementados. Los resultados son candidatos de descubrimiento, no activos de pago de confianza.
- Adaptadores de tokens compatibles: ERC-20 en Ethereum, Base, BNB Chain, HyperEVM, Avalanche, Polygon, Arbitrum y Optimism; SPL en Solana.
- Las redes de catálogo no compatibles se rechazan en lugar de aparecer seleccionables.
- El ranking, icono y precio CoinGecko son datos orientativos de descubrimiento.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto habilitado asignado a la credencial. |
| chain_slug | query string | Slug obligatorio de red EVM compatible o solana. |
| q | query string | Subcadena opcional de nombre, símbolo, id CoinGecko, contrato o mint; máximo 80 caracteres. |
| limit | query integer | Opcional 1–100; predeterminado 50. |
TokenCandidate
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| coingecko_id | string | siempre | Identidad de descubrimiento CoinGecko usada por la solicitud de registro. |
| chain_slug | string | siempre | Red Wholly Crypto coincidente. |
| symbol / name | string | siempre | Identidad visual del catálogo. |
| contract_address | string | siempre | Contrato o mint coincidente; se verifica en blockchain antes de registrar. |
| market_cap_rank | integer | null | siempre | Ranking de descubrimiento, no señal de confianza ni disponibilidad para pagos. |
| icon_path | path | siempre | Ruta del icono CoinGecko en caché local. |
| current_price_usd | decimal string | null | siempre | Precio USD orientativo en caché. |
| token_standard | erc20 | spl-token | siempre | Estándar de token compatible con el adaptador de la red seleccionada. |
| scanner_ready | boolean | siempre | True solo para candidatos en una vía de tokens implementada en esta compilación. |
| registered_asset_id | UUID | null | siempre | Activo persistente existente si ya fue registrado. |
| project_enabled | boolean | siempre | Si el activo registrado está habilitado para este proyecto. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{
"coingecko_id": "usd-coin",
"chain_slug": "ethereum",
"symbol": "USDC",
"name": "USDC",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"market_cap_rank": 7,
"icon_path": "/assets/coingecko/usd-coin.png",
"current_price_usd": "1.0001",
"token_standard": "erc20",
"scanner_ready": true,
"registered_asset_id": null,
"project_enabled": false
}
]
}POSTVerificar y registrar token/v1/projects/{project_id}/payment-token-assetsLectura y escritura
Promueve un candidato actual al registro persistente de pagos solo tras verificar con los nodos configurados la identidad de red, identidad de contrato/mint, decimales y una consulta de saldo utilizable. El registro nunca confía solo en metadatos CoinGecko y cada proyecto está limitado a 20 activos de token registrados.
- Activa el activo nativo de red del proyecto antes de registrar sus tokens.
- Un proyecto puede registrar como máximo 20 activos de token; un candidato nuevo por encima devuelve token_chain_not_ready (409). Reutilizar un activo ya registrado no consume otra plaza.
- La verificación de nodos puede tardar más que leer el catálogo; usa un tiempo de espera explícito en el cliente.
- Después de registrar, selecciona el activo en cada tienda que deba ofrecerlo.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatorio | application/json |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto habilitado asignado a la credencial. |
Cuerpo de registro de token
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug | string | obligatorio | ethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism o solana. |
| coingecko_id | string | obligatorio | Identidad exacta del candidato devuelta por la búsqueda de tokens. Conserva guiones bajos o guiones iniciales, como _ o -6. No derives este ID del nombre ni símbolo del token. |
| enabled | boolean | opcional | Estado de la política del proyecto tras verificar; predeterminado true. |
RegisteredTokenAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| asset_id | UUID | siempre | Identificador persistente de activo de pago. |
| chain_slug / coingecko_id | string | siempre | Red verificada e identidad de descubrimiento/precios conservada. |
| contract_address | string | siempre | Contrato o mint canónico verificado. |
| token_standard | erc20 | spl-token | siempre | Estándar de token verificado en ejecución. |
| symbol / name / decimals | string / string / integer | siempre | Identidad visual registrada y precisión exacta. |
| enabled | boolean | siempre | Estado inicial de la política de proyecto. |
| metadata_verified_at | RFC 3339 timestamp | siempre | Hora de verificación en blockchain. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 application/json
{
"data": {
"asset_id": "44444444-4444-4444-8444-444444444444",
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"symbol": "USDC",
"name": "USDC",
"decimals": 6,
"enabled": true,
"metadata_verified_at": "2026-08-31T18:00:00Z"
}
}GETBuscar pools DEX de tokens personalizados/v1/projects/{project_id}/payment-token-dex-poolsSolo lectura
Encuentra hasta 12 pools aptos por red y contrato exacto del token base mediante DEX Screener, ordenados por liquidez. Esto no registra ni activa un token.
- Un array data vacío significa que no se encontró un pool apto. Solo se devuelven pools donde el contrato exacto solicitado es el token base; nunca se suponen precios USD del lado cotizado.
- Aparecer en DEX no es una auditoría de seguridad. La liquidez mínima y actividad reciente reducen cotizaciones inutilizables, pero no evitan la manipulación de mercado.
- Uniswap, PancakeSwap y otros DEX indexados son compatibles donde el escáner de red existente admite tokens. El acceso API sigue limitado al proyecto y por cuota. Las llamadas al proveedor también se serializan y limitan.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado. |
| chain_slug | query string | Red de tokens EVM compatible o solana. |
| contract_address | query string | Contrato ERC-20 o mint SPL clásico exacto. |
CustomDexPool
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| pair_address / dex_id / quote_symbol | string | siempre | Identificador exacto del pool, ID de exchange (p. ej., uniswap/pancakeswap) y símbolo emparejado solo visual. |
| price_usd / liquidity_usd | decimal string | siempre | Precio USD del token base solicitado y liquidez total del pool. Requiere al menos $10,000 de liquidez y una operación en la última hora. |
| fetched_at | RFC 3339 timestamp | siempre | Cuándo obtuvo el servidor la observación del proveedor, no la fecha de una operación en blockchain. |
| url | HTTPS URL | siempre | Enlace validado de DEX Screener a este pool. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{"data":[{"pair_address":"0x2222222222222222222222222222222222222222","dex_id":"uniswap","quote_symbol":"WETH","price_usd":"0.25","liquidity_usd":"250000.00","fetched_at":"2026-09-09T12:00:00Z","url":"https://dexscreener.com/ethereum/0x2222222222222222222222222222222222222222"}]}POSTAñadir o cambiar precio de token personalizado/v1/projects/{project_id}/payment-token-assets/customLectura y escritura
Verifica un contrato personalizado con los nodos de red configurados y lo registra sin exigir una entrada CoinGecko. El precio fijo USD o pool DEX automático seleccionado pertenece a este proyecto, no al símbolo ni a otros proyectos. Repetir la misma identidad actualiza su precio de proyecto sin cambiar una política existente de activación/desactivación.
- Después de registrar, selecciona asset_id en el endpoint payment-assets de la tienda; registrar por sí solo nunca activa un método de tienda.
- Los tokens personalizados y del catálogo comparten el límite de 20 por proyecto. El mismo contrato en redes diferentes es un activo de pago diferente.
- Los contratos existentes del catálogo devuelven 409: usa el registro de catálogo para conservar tipos de mercado automáticos. Un símbolo personalizado nunca toma el precio de un token homónimo.
- Los precios fijos son estimaciones del operador. Los precios automáticos DEX son observaciones al contado del pool elegido mediante DEX Screener, no un oráculo resistente a manipulación. Siguen aplicándose el margen de tienda y redondeo al alza, con tipos fiat recientes. Las cotizaciones ya emitidas no cambian.
- Para modo DEX, descubre primero un pool y envía price_mode: dex y dex_pair_address, omitiendo price_usd. Una tarea compartida en segundo plano actualiza los pools elegidos cada minuto. Los fallos de comprobación o precios de más de cinco minutos excluyen este token de nuevas cotizaciones; no hay alternativa silenciosa a precio fijo ni por símbolo.
- Solo se aceptan tokens ERC-20 estándar y SPL clásicos. Se rechazan Token-2022/extensiones y redes solo nativas. La verificación técnica no es una auditoría de seguridad del emisor/contrato; los tokens con comisión por transferencia, reajuste de saldo o listas de bloqueo pueden comportarse de forma incompatible.
- Usa un tiempo de espera del cliente de al menos 60 segundos. La verificación está acotada y puede probar nodos alternativos. Datos inválidos devuelven 400; fallos de verificación de red/contrato, 422; conflictos de identidad o límites, 409.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatorio | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado a esta credencial con escritura. |
Registro de token personalizado
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug | string | obligatorio | ethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism o solana. Fijo para este contrato. |
| contract_address | string | obligatorio | Contrato ERC-20 (0x y 40 caracteres hexadecimales) o mint SPL clásico. Los nodos verifican identidad de red y decimales exactos; se rechazan decimales y URL RPC proporcionados por el cliente. |
| name / symbol | string / string | obligatorio | Nombre visible (1–80 caracteres) y símbolo (1–16 letras/dígitos/puntos/guiones bajos/guiones, primer carácter alfanumérico). Este endpoint no puede renombrar identidades existentes. |
| price_mode | fixed | dex | opcional | Por defecto fixed por compatibilidad. DEX usa un pool específico descubierto para la red y contrato exactos. |
| price_usd | decimal string | modo fixed | Valor USD fijo de UN token, positivo, máximo 30 decimales, máximo 1000000000000000000000000. Sin exponente ni floats. Omite en modo dex. |
| dex_pair_address | string | modo dex | Dirección del pool de payment-token-dex-pools. Obligatoria en modo dex; omite en modo fixed. El servidor vuelve a comprobar identidad, precio, liquidez y actividad del pool cada vez que guardas. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{"data":{"asset_id":"44444444-4444-4444-8444-444444444444"}}GETListar métodos de pago de la tienda/v1/projects/{project_id}/stores/{store_id}/payment-assetsSolo lectura
Lista activos en blockchain en data y disponibilidad Lightning separada en lightning. Los métodos en blockchain requieren wallets de red listas. Lightning usa la conexión receptora externa verificada elegida en la tienda, independientemente de la wallet Bitcoin en blockchain.
- selected es la configuración en blockchain; wallet_readiness es su condición de elegibilidad actual.
- El miembro lightning de la respuesta contiene payment_rail: lightning, symbol: BTC, asset_decimals: 11, enabled y ready. Nunca contiene credenciales de nodo. Configura este método en la consola de la tienda; actualizar el array assets no cambia Lightning.
- confirmation_policy solo se aplica a métodos en blockchain. Lightning liquida sin confirmaciones de bloques y requiere el importe BOLT11 completo, sin tolerancia de pago parcial.
- Los métodos nativos y de tokens de una red usan el mismo destino de factura para la wallet de esa red.
- Los resúmenes de wallet incluidos solo indican disponibilidad y dejan saldos vacíos; usa la ruta específica de wallets del proyecto para valores actuales.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado a la credencial; puede estar en pausa. |
| store_id | path UUID | Tienda perteneciente a project_id; puede estar en pausa. |
PaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | Identificador persistente de activo de pago usado por rutas de política de proyecto y tienda. |
| asset_key | string | siempre | Identidad canónica de activo nativo o de contrato con estilo CAIP. |
| chain_slug / network | string | siempre | Identificador de cadena Wholly Crypto y red configurada. |
| caip_network_id / caip_asset_id | string / string|null | siempre | Identidades canónicas de red y activo. |
| asset_kind | native | token | siempre | Si la liquidación usa la moneda de red o un contrato/mint verificado. |
| payment_rail | string | siempre | Vía de ejecución: utxo, evm-native, solana-native, account-native, privacy-native o token-transfer. |
| symbol / name / decimals | string / string / integer | siempre | Identidad visual y precisión exacta de unidades atómicas. |
| contract_address | string | null | siempre | Contrato ERC-20 o mint SPL canónico para tokens; null para activos nativos. |
| coingecko_id | string | null | siempre | Identidad de descubrimiento/precios. Null para contratos personalizados; nunca deduzcas un precio de mercado de su símbolo. Los metadatos CoinGecko por sí solos nunca hacen seleccionable un token. |
| custom_token | boolean | siempre | Contrato personalizado verificado en blockchain con precio fijo USD o pool DEX seleccionado, limitado al proyecto. |
| icon_path | path | null | siempre | Icono del token en caché local cuando está disponible. |
| token_standard | erc20 | spl-token | null | siempre | Estándar de token verificado en ejecución; null para activos nativos. |
| metadata_verified_at | timestamp | null | siempre | Hora de verificación de metadatos en blockchain para tokens registrados. |
| payment_supported / scanner_ready / balance_ready | boolean | siempre | Condiciones del registro en compilación. scanner_ready significa que está instalado el escáner de pagos; confirmar pagos requiere el número configurado de proveedores sanos con rol exacto (2 por defecto, 1 opcional); la disponibilidad temporal del escáner no bloquea crear facturas desde 6.0.6. balance_ready solo es true para adaptadores de saldo implementados. |
| default_finality_mode | confirmations | finalized | siempre | Modelo predeterminado de finalidad que hereda una política nueva de proyecto. |
| default_required_confirmations / default_monitoring_minutes | integer | siempre | Política predeterminada de confirmaciones y seguimiento. |
StorePaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| asset | PaymentAsset | siempre | Activo nativo o token verificado visible para el proyecto. |
| project_policy | ProjectAssetPolicy | null | siempre | Política del proyecto principal. |
| selected | boolean | siempre | Si este método forma parte de la configuración deseada guardada de la tienda. Se ofrece si son válidos la política del proyecto, wallet, adaptador instalado y precios. Las interrupciones temporales del escáner no lo quitan de las facturas nuevas. |
| display_order | integer | null | siempre | Orden en el pago de la tienda si está seleccionado. |
| confirmation_policy | StoreConfirmationPolicy | null | siempre | Política efectiva de tienda para un activo configurado en el proyecto. Null si no existe política de proyecto. |
| wallet | WalletSummary | null | siempre | Wallet de red compartida por activos nativos y tokens. |
| wallet_readiness | readiness enum | siempre | Solo estado de wallet/política; usa receive_readiness para los requisitos del escáner. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Configuración compartida de recepción más aceptación de tienda. Usa observaciones en caché; no es reserva ni garantía. La creación vuelve a comprobar requisitos y el tipo de cambio real de la factura. |
StoreConfirmationPolicy
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| finality_mode | confirmations | finalized | siempre | Si la liquidación usa un número configurable de bloques o finalidad de red. |
| project_required_confirmations | integer | siempre | Valor predeterminado actual del proyecto usado por facturas futuras sin excepción de tienda. |
| override_required_confirmations | integer | null | siempre | Número específico de tienda, o null para heredar el predeterminado del proyecto. |
| effective_required_confirmations | integer | siempre | Número que guardarán las facturas nuevas de esta tienda y activo. |
| editable | boolean | siempre | False para redes finalized cuya política de finalidad no puede modificarse. |
| minimum_required_confirmations | integer | siempre | Límite inferior inclusivo según la red; 0 solo se expone en vías que admiten aceptar al detectar. |
| maximum_required_confirmations | integer | siempre | Límite superior inclusivo según la red. |
WalletSummary
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | siempre | Identificadores de wallet, proyecto propietario y activo nativo de la red. |
| chain_slug / network | string | siempre | Cadena y red de la wallet. |
| asset_symbol / asset_name | string | siempre | Identidad visual nativa de la red. |
| status | pending | active | disabled | error | siempre | Estado operativo de la wallet. |
| label | string | siempre | Etiqueta del operador. |
| public_key / primary_address | string | null | siempre | Identidad pública de wallet; no expone frase semilla ni clave privada. |
| derivation_scheme / address_format | string | null | siempre | Política y formato de direcciones. |
| backup_confirmed_at | timestamp | null | siempre | Distinto de null después de que el operador confirme la copia de recuperación. |
| activation_required / activation_verified_at | boolean / timestamp|null | siempre | Las cuentas compartidas XRP y Stellar siguen sin estar disponibles hasta que el operador aporte fondos a la dirección mostrada y los proveedores de escaneo configurados verifiquen esa cuenta exacta. La prueba persistente no caduca; el estado vivo del escáner se comprueba por separado para verificar pagos, no para crear facturas. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Incluido en listas de wallets: configuración receptora del proyecto y requisitos previos del escáner de red. Separado de saldos, gas de tokens y disponibilidad de envío. Otras respuestas de wallet pueden dejarlo null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | siempre | Estado del vínculo externo wallet-RPC de solo lectura de Monero, sin datos sensibles. Incluye endpoint, modo de autenticación, dirección principal de cuenta 0, indicadores/alturas de prueba técnica y fechas de declaración del operador; nunca se serializan credenciales, claves ni archivos de wallet. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | siempre | Metadatos de auditoría de revelación de secretos en consola. |
| next_receive_index | integer | siempre | Siguiente índice reservado de dirección derivada. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | siempre | Estado del escáner de wallet. |
| balances | WalletAssetBalance[] | siempre | Saldos en caché de cada una de las 30 vías nativas, además de activos ERC-20 y SPL verificados. Monero requiere una wallet-RPC externa de solo lectura configurada. |
| total_value_usd | decimal string | null | siempre | Suma orientativa de saldos con precio USD actual. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | siempre | Actualidad agregada de la caché; unknown es una alternativa defensiva y ninguno de estos estados demuestra liquidación de factura. |
| balance_checked_at | timestamp | null | siempre | Comprobación correcta de saldo relevante más antigua representada en el agregado. |
| recent_payments | WalletRecentPayment[] | siempre | Hasta las tres observaciones válidas detected, confirming o final más recientes atribuidas a esta wallet exacta. |
| created_at / updated_at | RFC 3339 timestamp | siempre | Hora de creación y última actualización de wallet. |
ReceiveReadiness
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ready | boolean | siempre | Las comprobaciones de configuración de recepción pasan. No describe disponibilidad de gasto, gas, actualización de saldos ni una cotización futura garantizada. |
| invoice_creatable | boolean | 6.0.6+ | La configuración permite un método de factura pese a avisos temporales del escáner. El precio de moneda se comprueba al crear. Esto no verifica pagos: ready puede ser false mientras invoice_creatable es true. Las wallets ausentes, políticas desactivadas y adaptadores no compatibles siguen fallando de forma segura. |
| checked_at | timestamp | siempre | Hora de evaluación. Listar no hace solicitudes de red ni asigna direcciones. |
| issues | PaymentMethodIssue[] | siempre | Vacío cuando está listo; en otro caso, aviso de recepción o bloqueo de configuración. Comprueba invoice_creatable para distinguir avisos temporales del escáner de fallos de configuración de factura. |
PaymentMethodIssue
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | cuando se conoce | Identifica la red y activo afectados. Lightning puede omitir asset_id. |
| reason_code | string | siempre | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled o asset_not_accepted. |
| message / action | string | cuando está disponible | Explicación para el comercio e identificador de acción: chain_connections, wallets, rates, payment_methods, project_settings o store_settings. Sin credenciales ni URL privadas de proveedores. |
| required_endpoint_role | string | null | en blockchain | Rol API preferido del escáner (campo heredado). Usa accepted_endpoint_roles para la lista completa de compatibilidad. El estado básico de un nodo no demuestra soporte de historial de pagos. |
| accepted_endpoint_roles | string[] | null | en blockchain | Dialectos API compatibles, no prueba de historial ni capacidad del endpoint. node-rpc directo admite BTC/BCH/LTC/DOGE/DASH y ZEC transparente (bloques completos decodificados, 1–48 confirmaciones), TRX nativo solidificado, ALGO nativo mediante algod, XTZ mediante Octez, DOT finalizado de Asset Hub mediante metadatos SCALE y XLM nativo mediante Stellar RPC con ID memo de factura. El historial podado o incompleto no sirve. Estos adaptadores directos no añaden vías de tokens. Las API indexadas siguen siendo alternativas; consulta la tabla de vías de abajo. Las fuentes directas/indexadas mixtas verifican ventanas limitadas de forma independiente; el valor predeterminado sigue siendo dos proveedores independientes, no alias de un mismo operador. La altura básica de nodo, información de red ORDnet y un relay EVM para una vía no EVM no son pruebas de recepción. Monero sigue necesitando una wallet-RPC de solo lectura vinculada al proyecto. |
| healthy_endpoints | integer | en blockchain | Endpoints sanos coincidentes, no el número de proveedores independientes. |
| usable_independent_providers / required_independent_providers | integer | en blockchain | Plazas de verificación utilizables, limitadas a dos. required_independent_providers es el ajuste de red: 2 por defecto o 1 tras elección expresa del administrador. En modo de dos proveedores se requieren claves de proveedor Y hosts diferentes. Las fuentes desactivadas, antiguas (más de diez minutos) o en pausa no ocupan una plaza. Lightning usa sus propias reglas de conexión. |
| last_checked_at | timestamp | null | en blockchain | Última comprobación de estado de endpoint coincidente, separada de la hora de evaluación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "chain_slug": "ethereum", "symbol": "USDC", "asset_kind": "token", "token_standard": "erc20", "scanner_ready": true }, "project_policy": { "enabled": true, "required_confirmations": 12 }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": 3, "effective_required_confirmations": 3, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet": { "id": "WALLET_UUID", "status": "active" }, "wallet_readiness": "ready" }
],
"lightning": { "payment_rail": "lightning", "symbol": "BTC", "asset_decimals": 11, "enabled": true, "ready": true }
}PUTSustituir métodos de pago de la tienda/v1/projects/{project_id}/stores/{store_id}/payment-assetsLectura y escritura
Sustituye atómicamente todo el subconjunto ordenado de activos de la tienda y devuelve la lista actualizada. Los activos omitidos quedan deseleccionados.
- El array acepta como máximo 64 activos y órdenes visuales únicos.
- Las selecciones son configuración deseada guardada y pueden prepararse antes de copiar una wallet o mientras una red está en pausa. Crear facturas sigue ofreciendo solo métodos con política de proyecto, política nativa principal, wallet y comprobaciones de ejecución listas.
- Envía un array assets vacío para configurar ningún método de pago.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatorio | application/json |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado a la credencial; puede estar en pausa. |
| store_id | path UUID | Tienda perteneciente a project_id; puede estar en pausa. |
Cuerpo de selección de activos de pago de tienda
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| assets | StoreAssetSelection[] | obligatorio | Lista de sustitución completa, máximo 64 entradas. Cada entrada contiene un asset_id único y display_order único entre 0 y 10,000. |
PaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | Identificador persistente de activo de pago usado por rutas de política de proyecto y tienda. |
| asset_key | string | siempre | Identidad canónica de activo nativo o de contrato con estilo CAIP. |
| chain_slug / network | string | siempre | Identificador de cadena Wholly Crypto y red configurada. |
| caip_network_id / caip_asset_id | string / string|null | siempre | Identidades canónicas de red y activo. |
| asset_kind | native | token | siempre | Si la liquidación usa la moneda de red o un contrato/mint verificado. |
| payment_rail | string | siempre | Vía de ejecución: utxo, evm-native, solana-native, account-native, privacy-native o token-transfer. |
| symbol / name / decimals | string / string / integer | siempre | Identidad visual y precisión exacta de unidades atómicas. |
| contract_address | string | null | siempre | Contrato ERC-20 o mint SPL canónico para tokens; null para activos nativos. |
| coingecko_id | string | null | siempre | Identidad de descubrimiento/precios. Null para contratos personalizados; nunca deduzcas un precio de mercado de su símbolo. Los metadatos CoinGecko por sí solos nunca hacen seleccionable un token. |
| custom_token | boolean | siempre | Contrato personalizado verificado en blockchain con precio fijo USD o pool DEX seleccionado, limitado al proyecto. |
| icon_path | path | null | siempre | Icono del token en caché local cuando está disponible. |
| token_standard | erc20 | spl-token | null | siempre | Estándar de token verificado en ejecución; null para activos nativos. |
| metadata_verified_at | timestamp | null | siempre | Hora de verificación de metadatos en blockchain para tokens registrados. |
| payment_supported / scanner_ready / balance_ready | boolean | siempre | Condiciones del registro en compilación. scanner_ready significa que está instalado el escáner de pagos; confirmar pagos requiere el número configurado de proveedores sanos con rol exacto (2 por defecto, 1 opcional); la disponibilidad temporal del escáner no bloquea crear facturas desde 6.0.6. balance_ready solo es true para adaptadores de saldo implementados. |
| default_finality_mode | confirmations | finalized | siempre | Modelo predeterminado de finalidad que hereda una política nueva de proyecto. |
| default_required_confirmations / default_monitoring_minutes | integer | siempre | Política predeterminada de confirmaciones y seguimiento. |
StorePaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| asset | PaymentAsset | siempre | Activo nativo o token verificado visible para el proyecto. |
| project_policy | ProjectAssetPolicy | null | siempre | Política del proyecto principal. |
| selected | boolean | siempre | Si este método forma parte de la configuración deseada guardada de la tienda. Se ofrece si son válidos la política del proyecto, wallet, adaptador instalado y precios. Las interrupciones temporales del escáner no lo quitan de las facturas nuevas. |
| display_order | integer | null | siempre | Orden en el pago de la tienda si está seleccionado. |
| confirmation_policy | StoreConfirmationPolicy | null | siempre | Política efectiva de tienda para un activo configurado en el proyecto. Null si no existe política de proyecto. |
| wallet | WalletSummary | null | siempre | Wallet de red compartida por activos nativos y tokens. |
| wallet_readiness | readiness enum | siempre | Solo estado de wallet/política; usa receive_readiness para los requisitos del escáner. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Configuración compartida de recepción más aceptación de tienda. Usa observaciones en caché; no es reserva ni garantía. La creación vuelve a comprobar requisitos y el tipo de cambio real de la factura. |
StoreConfirmationPolicy
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| finality_mode | confirmations | finalized | siempre | Si la liquidación usa un número configurable de bloques o finalidad de red. |
| project_required_confirmations | integer | siempre | Valor predeterminado actual del proyecto usado por facturas futuras sin excepción de tienda. |
| override_required_confirmations | integer | null | siempre | Número específico de tienda, o null para heredar el predeterminado del proyecto. |
| effective_required_confirmations | integer | siempre | Número que guardarán las facturas nuevas de esta tienda y activo. |
| editable | boolean | siempre | False para redes finalized cuya política de finalidad no puede modificarse. |
| minimum_required_confirmations | integer | siempre | Límite inferior inclusivo según la red; 0 solo se expone en vías que admiten aceptar al detectar. |
| maximum_required_confirmations | integer | siempre | Límite superior inclusivo según la red. |
ReceiveReadiness
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ready | boolean | siempre | Las comprobaciones de configuración de recepción pasan. No describe disponibilidad de gasto, gas, actualización de saldos ni una cotización futura garantizada. |
| invoice_creatable | boolean | 6.0.6+ | La configuración permite un método de factura pese a avisos temporales del escáner. El precio de moneda se comprueba al crear. Esto no verifica pagos: ready puede ser false mientras invoice_creatable es true. Las wallets ausentes, políticas desactivadas y adaptadores no compatibles siguen fallando de forma segura. |
| checked_at | timestamp | siempre | Hora de evaluación. Listar no hace solicitudes de red ni asigna direcciones. |
| issues | PaymentMethodIssue[] | siempre | Vacío cuando está listo; en otro caso, aviso de recepción o bloqueo de configuración. Comprueba invoice_creatable para distinguir avisos temporales del escáner de fallos de configuración de factura. |
PaymentMethodIssue
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | cuando se conoce | Identifica la red y activo afectados. Lightning puede omitir asset_id. |
| reason_code | string | siempre | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled o asset_not_accepted. |
| message / action | string | cuando está disponible | Explicación para el comercio e identificador de acción: chain_connections, wallets, rates, payment_methods, project_settings o store_settings. Sin credenciales ni URL privadas de proveedores. |
| required_endpoint_role | string | null | en blockchain | Rol API preferido del escáner (campo heredado). Usa accepted_endpoint_roles para la lista completa de compatibilidad. El estado básico de un nodo no demuestra soporte de historial de pagos. |
| accepted_endpoint_roles | string[] | null | en blockchain | Dialectos API compatibles, no prueba de historial ni capacidad del endpoint. node-rpc directo admite BTC/BCH/LTC/DOGE/DASH y ZEC transparente (bloques completos decodificados, 1–48 confirmaciones), TRX nativo solidificado, ALGO nativo mediante algod, XTZ mediante Octez, DOT finalizado de Asset Hub mediante metadatos SCALE y XLM nativo mediante Stellar RPC con ID memo de factura. El historial podado o incompleto no sirve. Estos adaptadores directos no añaden vías de tokens. Las API indexadas siguen siendo alternativas; consulta la tabla de vías de abajo. Las fuentes directas/indexadas mixtas verifican ventanas limitadas de forma independiente; el valor predeterminado sigue siendo dos proveedores independientes, no alias de un mismo operador. La altura básica de nodo, información de red ORDnet y un relay EVM para una vía no EVM no son pruebas de recepción. Monero sigue necesitando una wallet-RPC de solo lectura vinculada al proyecto. |
| healthy_endpoints | integer | en blockchain | Endpoints sanos coincidentes, no el número de proveedores independientes. |
| usable_independent_providers / required_independent_providers | integer | en blockchain | Plazas de verificación utilizables, limitadas a dos. required_independent_providers es el ajuste de red: 2 por defecto o 1 tras elección expresa del administrador. En modo de dos proveedores se requieren claves de proveedor Y hosts diferentes. Las fuentes desactivadas, antiguas (más de diez minutos) o en pausa no ocupan una plaza. Lightning usa sus propias reglas de conexión. |
| last_checked_at | timestamp | null | en blockchain | Última comprobación de estado de endpoint coincidente, separada de la hora de evaluación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{ "asset": { "id": "44444444-4444-4444-8444-444444444444", "symbol": "USDC" }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": null, "effective_required_confirmations": 12, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet_readiness": "ready" }
]
}PUTDefinir política de confirmaciones de una tienda/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policyLectura y escritura
Define o borra una excepción de confirmaciones específica de tienda y devuelve la lista actualizada de métodos de pago. El activo ya debe estar seleccionado para la tienda. La configuración sigue disponible mientras proyecto, tienda, red o wallet estén en pausa.
- Usa {"strategy":"inherit"} para quitar la excepción de tienda y seguir el valor predeterminado actual del proyecto en facturas futuras.
- Las redes finalized devuelven editable false y usan Finalidad de red; no aceptan un número de bloques personalizado.
- Un valor de 0 significa aceptar al detectar, sin confirmación de red ni protección frente a reorganizaciones. Solo se acepta donde minimum_required_confirmations es 0.
- Los cambios de política solo afectan a facturas nuevas. Las existentes conservan la instantánea de la política de confirmaciones de proyecto/tienda capturada al crear.
- Se actualiza un activo cada vez; serializa cambios simultáneos del mismo activo de tienda y usa la respuesta actualizada como estado actual.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatorio | application/json |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado a la credencial; puede estar en pausa. |
| store_id | path UUID | Tienda perteneciente a project_id; puede estar en pausa. |
| asset_id | path UUID | Activo de pago actualmente seleccionado en la tienda que se actualizará. |
Cuerpo de política de confirmaciones de tienda
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| strategy | inherit | custom | obligatorio | Estrategia etiquetada. inherit elimina la excepción de tienda; custom requiere required_confirmations. |
| required_confirmations | integer | solo custom | Entero dentro del mínimo/máximo devuelto para este activo. Se rechazan campos desconocidos o extra. |
PaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | Identificador persistente de activo de pago usado por rutas de política de proyecto y tienda. |
| asset_key | string | siempre | Identidad canónica de activo nativo o de contrato con estilo CAIP. |
| chain_slug / network | string | siempre | Identificador de cadena Wholly Crypto y red configurada. |
| caip_network_id / caip_asset_id | string / string|null | siempre | Identidades canónicas de red y activo. |
| asset_kind | native | token | siempre | Si la liquidación usa la moneda de red o un contrato/mint verificado. |
| payment_rail | string | siempre | Vía de ejecución: utxo, evm-native, solana-native, account-native, privacy-native o token-transfer. |
| symbol / name / decimals | string / string / integer | siempre | Identidad visual y precisión exacta de unidades atómicas. |
| contract_address | string | null | siempre | Contrato ERC-20 o mint SPL canónico para tokens; null para activos nativos. |
| coingecko_id | string | null | siempre | Identidad de descubrimiento/precios. Null para contratos personalizados; nunca deduzcas un precio de mercado de su símbolo. Los metadatos CoinGecko por sí solos nunca hacen seleccionable un token. |
| custom_token | boolean | siempre | Contrato personalizado verificado en blockchain con precio fijo USD o pool DEX seleccionado, limitado al proyecto. |
| icon_path | path | null | siempre | Icono del token en caché local cuando está disponible. |
| token_standard | erc20 | spl-token | null | siempre | Estándar de token verificado en ejecución; null para activos nativos. |
| metadata_verified_at | timestamp | null | siempre | Hora de verificación de metadatos en blockchain para tokens registrados. |
| payment_supported / scanner_ready / balance_ready | boolean | siempre | Condiciones del registro en compilación. scanner_ready significa que está instalado el escáner de pagos; confirmar pagos requiere el número configurado de proveedores sanos con rol exacto (2 por defecto, 1 opcional); la disponibilidad temporal del escáner no bloquea crear facturas desde 6.0.6. balance_ready solo es true para adaptadores de saldo implementados. |
| default_finality_mode | confirmations | finalized | siempre | Modelo predeterminado de finalidad que hereda una política nueva de proyecto. |
| default_required_confirmations / default_monitoring_minutes | integer | siempre | Política predeterminada de confirmaciones y seguimiento. |
StorePaymentAsset
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| asset | PaymentAsset | siempre | Activo nativo o token verificado visible para el proyecto. |
| project_policy | ProjectAssetPolicy | null | siempre | Política del proyecto principal. |
| selected | boolean | siempre | Si este método forma parte de la configuración deseada guardada de la tienda. Se ofrece si son válidos la política del proyecto, wallet, adaptador instalado y precios. Las interrupciones temporales del escáner no lo quitan de las facturas nuevas. |
| display_order | integer | null | siempre | Orden en el pago de la tienda si está seleccionado. |
| confirmation_policy | StoreConfirmationPolicy | null | siempre | Política efectiva de tienda para un activo configurado en el proyecto. Null si no existe política de proyecto. |
| wallet | WalletSummary | null | siempre | Wallet de red compartida por activos nativos y tokens. |
| wallet_readiness | readiness enum | siempre | Solo estado de wallet/política; usa receive_readiness para los requisitos del escáner. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Configuración compartida de recepción más aceptación de tienda. Usa observaciones en caché; no es reserva ni garantía. La creación vuelve a comprobar requisitos y el tipo de cambio real de la factura. |
StoreConfirmationPolicy
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| finality_mode | confirmations | finalized | siempre | Si la liquidación usa un número configurable de bloques o finalidad de red. |
| project_required_confirmations | integer | siempre | Valor predeterminado actual del proyecto usado por facturas futuras sin excepción de tienda. |
| override_required_confirmations | integer | null | siempre | Número específico de tienda, o null para heredar el predeterminado del proyecto. |
| effective_required_confirmations | integer | siempre | Número que guardarán las facturas nuevas de esta tienda y activo. |
| editable | boolean | siempre | False para redes finalized cuya política de finalidad no puede modificarse. |
| minimum_required_confirmations | integer | siempre | Límite inferior inclusivo según la red; 0 solo se expone en vías que admiten aceptar al detectar. |
| maximum_required_confirmations | integer | siempre | Límite superior inclusivo según la red. |
ReceiveReadiness
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ready | boolean | siempre | Las comprobaciones de configuración de recepción pasan. No describe disponibilidad de gasto, gas, actualización de saldos ni una cotización futura garantizada. |
| invoice_creatable | boolean | 6.0.6+ | La configuración permite un método de factura pese a avisos temporales del escáner. El precio de moneda se comprueba al crear. Esto no verifica pagos: ready puede ser false mientras invoice_creatable es true. Las wallets ausentes, políticas desactivadas y adaptadores no compatibles siguen fallando de forma segura. |
| checked_at | timestamp | siempre | Hora de evaluación. Listar no hace solicitudes de red ni asigna direcciones. |
| issues | PaymentMethodIssue[] | siempre | Vacío cuando está listo; en otro caso, aviso de recepción o bloqueo de configuración. Comprueba invoice_creatable para distinguir avisos temporales del escáner de fallos de configuración de factura. |
PaymentMethodIssue
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | cuando se conoce | Identifica la red y activo afectados. Lightning puede omitir asset_id. |
| reason_code | string | siempre | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled o asset_not_accepted. |
| message / action | string | cuando está disponible | Explicación para el comercio e identificador de acción: chain_connections, wallets, rates, payment_methods, project_settings o store_settings. Sin credenciales ni URL privadas de proveedores. |
| required_endpoint_role | string | null | en blockchain | Rol API preferido del escáner (campo heredado). Usa accepted_endpoint_roles para la lista completa de compatibilidad. El estado básico de un nodo no demuestra soporte de historial de pagos. |
| accepted_endpoint_roles | string[] | null | en blockchain | Dialectos API compatibles, no prueba de historial ni capacidad del endpoint. node-rpc directo admite BTC/BCH/LTC/DOGE/DASH y ZEC transparente (bloques completos decodificados, 1–48 confirmaciones), TRX nativo solidificado, ALGO nativo mediante algod, XTZ mediante Octez, DOT finalizado de Asset Hub mediante metadatos SCALE y XLM nativo mediante Stellar RPC con ID memo de factura. El historial podado o incompleto no sirve. Estos adaptadores directos no añaden vías de tokens. Las API indexadas siguen siendo alternativas; consulta la tabla de vías de abajo. Las fuentes directas/indexadas mixtas verifican ventanas limitadas de forma independiente; el valor predeterminado sigue siendo dos proveedores independientes, no alias de un mismo operador. La altura básica de nodo, información de red ORDnet y un relay EVM para una vía no EVM no son pruebas de recepción. Monero sigue necesitando una wallet-RPC de solo lectura vinculada al proyecto. |
| healthy_endpoints | integer | en blockchain | Endpoints sanos coincidentes, no el número de proveedores independientes. |
| usable_independent_providers / required_independent_providers | integer | en blockchain | Plazas de verificación utilizables, limitadas a dos. required_independent_providers es el ajuste de red: 2 por defecto o 1 tras elección expresa del administrador. En modo de dos proveedores se requieren claves de proveedor Y hosts diferentes. Las fuentes desactivadas, antiguas (más de diez minutos) o en pausa no ocupan una plaza. Lightning usa sus propias reglas de conexión. |
| last_checked_at | timestamp | null | en blockchain | Última comprobación de estado de endpoint coincidente, separada de la hora de evaluación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"strategy": "custom",
"required_confirmations": 0
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"strategy": "custom",
"required_confirmations": 0
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"strategy": "custom",
"required_confirmations": 0
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"strategy": "custom",
"required_confirmations": 0
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{
"asset": { "id": "YOUR_ASSET_ID", "chain_slug": "bitcoin", "symbol": "BTC" },
"selected": true,
"display_order": 0,
"confirmation_policy": {
"finality_mode": "confirmations",
"project_required_confirmations": 2,
"override_required_confirmations": 0,
"effective_required_confirmations": 0,
"editable": true,
"minimum_required_confirmations": 0,
"maximum_required_confirmations": 10000
},
"wallet_readiness": "ready"
}
]
}GETListar wallets y saldos del proyecto/v1/projects/{project_id}/walletsSolo lectura
Devuelve metadatos públicos de wallet y cada activo registrado con lectura de saldo en la cadena y red exactas de la wallet. Cubre las 30 vías nativas; también sigue activos ERC-20 y SPL verificados. Los activos aparecen inmediatamente, incluso antes de su primer escaneo o si no se aceptan para pagos. project_enabled informa de aceptación de pagos; tracking_active informa por separado de elegibilidad de actualización de solo lectura. Monero requiere su wallet-RPC externa de solo lectura vinculada al proyecto. Escanear saldos en consola prioriza lecturas limitadas con progreso/errores por activo; solo ciclos completos actualizan totales recientes. La liquidación de facturas sigue guiada por seguimiento de transacciones y política de confirmaciones, no por estos saldos en caché.
- Esta ruta bearer nunca devuelve frase de recuperación, clave privada, secreto cifrado ni método de gasto.
- Un activo nuevo registrado de la misma red se devuelve con saldos null y estado pending antes de completar su primer escaneo; nunca se informa un cero inventado.
- Desactivar proyecto, wallet para aceptación de pagos, vía nativa o activo individual no detiene el seguimiento de saldos de solo lectura: wallets activas y desactivadas con dirección principal siguen actualizando cada activo registrado compatible de la misma red. No se escanean wallets pendientes ni con error.
- project_enabled solo informa la política de aceptación de activos del proyecto y puede ser false mientras tracking_active sigue true.
- balance y balance_atomic son cadenas exactas; price_usd, value_usd y total_value_usd son orientativos y pueden ser null. Un estado de saldo reciente no garantiza un precio de mercado reciente.
- La valoración prioriza precios CoinGecko de hasta dos horas. Las monedas nativas y USDC/USDT canónicos verificados pueden recurrir a cotizaciones USD habilitadas de Kraken/Binance de hasta cinco minutos, primero el proveedor principal. No se supone paridad con el dólar ni se ponen precios de tokens personalizados solo por símbolo; los precios fijos/DEX del proyecto van aparte. Las cotizaciones de facturas no cambian.
- Pendiente no tiene instantánea completa. Actualizando conserva el último importe completo y checked_at; no significa que haya una transferencia blockchain pendiente. Los importes antiguos/con error también pueden conservar valores anteriores. Nunca trates una caché no disponible como cero ni como pago ausente. Las actualizaciones habituales EVM/Solana reutilizan direcciones vacías comprobadas recientemente hasta 30 minutos entre auditorías, mientras vuelven a comprobar direcciones con fondos, nuevas o cambiadas. Escanear saldos explícitamente en consola solicita un escaneo completo.
- recent_payments se limita a tres observaciones por wallet y excluye el historial invalidado.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto habilitado asignado a la credencial. |
WalletSummary
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | siempre | Identificadores de wallet, proyecto propietario y activo nativo de la red. |
| chain_slug / network | string | siempre | Cadena y red de la wallet. |
| asset_symbol / asset_name | string | siempre | Identidad visual nativa de la red. |
| status | pending | active | disabled | error | siempre | Estado operativo de la wallet. |
| label | string | siempre | Etiqueta del operador. |
| public_key / primary_address | string | null | siempre | Identidad pública de wallet; no expone frase semilla ni clave privada. |
| derivation_scheme / address_format | string | null | siempre | Política y formato de direcciones. |
| backup_confirmed_at | timestamp | null | siempre | Distinto de null después de que el operador confirme la copia de recuperación. |
| activation_required / activation_verified_at | boolean / timestamp|null | siempre | Las cuentas compartidas XRP y Stellar siguen sin estar disponibles hasta que el operador aporte fondos a la dirección mostrada y los proveedores de escaneo configurados verifiquen esa cuenta exacta. La prueba persistente no caduca; el estado vivo del escáner se comprueba por separado para verificar pagos, no para crear facturas. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Incluido en listas de wallets: configuración receptora del proyecto y requisitos previos del escáner de red. Separado de saldos, gas de tokens y disponibilidad de envío. Otras respuestas de wallet pueden dejarlo null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | siempre | Estado del vínculo externo wallet-RPC de solo lectura de Monero, sin datos sensibles. Incluye endpoint, modo de autenticación, dirección principal de cuenta 0, indicadores/alturas de prueba técnica y fechas de declaración del operador; nunca se serializan credenciales, claves ni archivos de wallet. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | siempre | Metadatos de auditoría de revelación de secretos en consola. |
| next_receive_index | integer | siempre | Siguiente índice reservado de dirección derivada. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | siempre | Estado del escáner de wallet. |
| balances | WalletAssetBalance[] | siempre | Saldos en caché de cada una de las 30 vías nativas, además de activos ERC-20 y SPL verificados. Monero requiere una wallet-RPC externa de solo lectura configurada. |
| total_value_usd | decimal string | null | siempre | Suma orientativa de saldos con precio USD actual. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | siempre | Actualidad agregada de la caché; unknown es una alternativa defensiva y ninguno de estos estados demuestra liquidación de factura. |
| balance_checked_at | timestamp | null | siempre | Comprobación correcta de saldo relevante más antigua representada en el agregado. |
| recent_payments | WalletRecentPayment[] | siempre | Hasta las tres observaciones válidas detected, confirming o final más recientes atribuidas a esta wallet exacta. |
| created_at / updated_at | RFC 3339 timestamp | siempre | Hora de creación y última actualización de wallet. |
WalletAssetBalance
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| wallet_id / asset_id | UUID | siempre | Identidades de wallet y activo persistente. |
| project_enabled | boolean | siempre | Si este activo está habilitado actualmente por la política de activos del proyecto. |
| active_store_count | integer | siempre | Número de tiendas habilitadas que seleccionan este activo actualmente. Es una proyección de aceptación; el seguimiento de saldos de solo lectura sigue independiente. |
| active_store_ids | UUID[] | siempre | Tiendas habilitadas de este proyecto que aceptan el activo actualmente. Permite un filtro local exacto de tiendas sin otra solicitud API. |
| tracking_active | boolean | siempre | Si esta wallet con lectura y el activo registrado de la misma red son aptos para actualizaciones de saldo en segundo plano. Los controles de aceptación del proyecto y método de pago no pausan el seguimiento de solo lectura. |
| asset_kind | native | token | siempre | Moneda nativa o activo de contrato/mint verificado. |
| contract_address | string | null | siempre | Contrato o mint del token; null para moneda nativa. |
| symbol / name / decimals | string / string / integer | siempre | Identidad visual y precisión atómica. |
| coingecko_id | string | null | siempre | Identidad de precios si está vinculada. |
| balance / balance_atomic | decimal string|null / integer string|null | siempre | Saldo visible y atómico exacto de la dirección principal de wallet y las direcciones de factura emitidas. Null mientras no esté disponible un valor completo. |
| price_usd | decimal string | null | siempre | Precio unitario USD orientativo en caché usado para valoración. |
| value_usd | decimal string | null | siempre | Valoración fiat orientativa cuando existe un tipo actual. |
| status | pending | refreshing | fresh | stale | error | siempre | Estado de escaneo en caché. refreshing puede conservar un saldo completo: usa checked_at para su antigüedad. Pendiente significa que no hay instantánea completa. Ninguno de estos estados demuestra una transferencia pendiente ni una factura liquidada. |
| checked_at | timestamp | null | siempre | Hora representada por un escaneo de saldo completo. |
| last_error | string | null | siempre | Diagnóstico seguro para el operador. |
WalletRecentPayment
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| invoice_public_id | UUID | siempre | Identidad de factura visible para el cliente asociada a la observación. |
| chain_slug / symbol | string | siempre | Red y símbolo visible de moneda nativa o token verificado. |
| transaction_id / event_index | string / integer | siempre | Identidad canónica de transacción y evento de transferencia. |
| amount | decimal string | siempre | Importe exacto observado del activo sin conversión a coma flotante. |
| status | detected | confirming | final | siempre | Estado válido actual de la observación. Se excluyen observaciones reorganizadas, sustituidas e inválidas. |
| confirmations | integer | siempre | Último número observado de confirmaciones. |
| observed_at | RFC 3339 timestamp | siempre | Hora en que Wholly Crypto observó el pago por primera vez. |
ReceiveReadiness
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ready | boolean | siempre | Las comprobaciones de configuración de recepción pasan. No describe disponibilidad de gasto, gas, actualización de saldos ni una cotización futura garantizada. |
| invoice_creatable | boolean | 6.0.6+ | La configuración permite un método de factura pese a avisos temporales del escáner. El precio de moneda se comprueba al crear. Esto no verifica pagos: ready puede ser false mientras invoice_creatable es true. Las wallets ausentes, políticas desactivadas y adaptadores no compatibles siguen fallando de forma segura. |
| checked_at | timestamp | siempre | Hora de evaluación. Listar no hace solicitudes de red ni asigna direcciones. |
| issues | PaymentMethodIssue[] | siempre | Vacío cuando está listo; en otro caso, aviso de recepción o bloqueo de configuración. Comprueba invoice_creatable para distinguir avisos temporales del escáner de fallos de configuración de factura. |
PaymentMethodIssue
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | cuando se conoce | Identifica la red y activo afectados. Lightning puede omitir asset_id. |
| reason_code | string | siempre | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled o asset_not_accepted. |
| message / action | string | cuando está disponible | Explicación para el comercio e identificador de acción: chain_connections, wallets, rates, payment_methods, project_settings o store_settings. Sin credenciales ni URL privadas de proveedores. |
| required_endpoint_role | string | null | en blockchain | Rol API preferido del escáner (campo heredado). Usa accepted_endpoint_roles para la lista completa de compatibilidad. El estado básico de un nodo no demuestra soporte de historial de pagos. |
| accepted_endpoint_roles | string[] | null | en blockchain | Dialectos API compatibles, no prueba de historial ni capacidad del endpoint. node-rpc directo admite BTC/BCH/LTC/DOGE/DASH y ZEC transparente (bloques completos decodificados, 1–48 confirmaciones), TRX nativo solidificado, ALGO nativo mediante algod, XTZ mediante Octez, DOT finalizado de Asset Hub mediante metadatos SCALE y XLM nativo mediante Stellar RPC con ID memo de factura. El historial podado o incompleto no sirve. Estos adaptadores directos no añaden vías de tokens. Las API indexadas siguen siendo alternativas; consulta la tabla de vías de abajo. Las fuentes directas/indexadas mixtas verifican ventanas limitadas de forma independiente; el valor predeterminado sigue siendo dos proveedores independientes, no alias de un mismo operador. La altura básica de nodo, información de red ORDnet y un relay EVM para una vía no EVM no son pruebas de recepción. Monero sigue necesitando una wallet-RPC de solo lectura vinculada al proyecto. |
| healthy_endpoints | integer | en blockchain | Endpoints sanos coincidentes, no el número de proveedores independientes. |
| usable_independent_providers / required_independent_providers | integer | en blockchain | Plazas de verificación utilizables, limitadas a dos. required_independent_providers es el ajuste de red: 2 por defecto o 1 tras elección expresa del administrador. En modo de dos proveedores se requieren claves de proveedor Y hosts diferentes. Las fuentes desactivadas, antiguas (más de diez minutos) o en pausa no ocupan una plaza. Lightning usa sus propias reglas de conexión. |
| last_checked_at | timestamp | null | en blockchain | Última comprobación de estado de endpoint coincidente, separada de la hora de evaluación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{
"id": "55555555-5555-4555-8555-555555555555",
"project_id": "11111111-1111-4111-8111-111111111111",
"native_asset_id": "10000000-0000-4000-8000-000000000003",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_symbol": "ETH",
"asset_name": "Ethereum",
"status": "active",
"label": "Primary Ethereum wallet",
"public_key": "0x…",
"primary_address": "0x…",
"derivation_scheme": "bip44",
"address_format": "eip55",
"backup_confirmed_at": "2026-08-31T17:00:00Z",
"last_secret_revealed_at": null,
"secret_reveal_count": 0,
"next_receive_index": 43,
"last_scanned_height": 23123456,
"last_scanned_at": "2026-08-31T18:05:00Z",
"last_error": null,
"balances": [
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000003", "project_enabled": true, "tracking_active": true, "asset_kind": "native", "contract_address": null, "symbol": "ETH", "name": "Ethereum", "decimals": 18, "coingecko_id": "ethereum", "balance": "0.125", "balance_atomic": "125000000000000000", "price_usd": "4500", "value_usd": "562.50", "status": "fresh", "checked_at": "2026-08-31T18:05:00Z", "last_error": null },
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000099", "project_enabled": false, "tracking_active": true, "asset_kind": "token", "contract_address": "0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48", "symbol": "USDC", "name": "USDC", "decimals": 6, "coingecko_id": "usd-coin", "balance": null, "balance_atomic": null, "price_usd": "1", "value_usd": null, "status": "pending", "checked_at": null, "last_error": null }
],
"total_value_usd": "562.50",
"balance_status": "fresh",
"balance_checked_at": "2026-08-31T18:05:00Z",
"recent_payments": [
{ "invoice_public_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50", "chain_slug": "ethereum", "symbol": "USDC", "transaction_id": "0x…", "event_index": 0, "amount": "25", "status": "final", "confirmations": 12, "observed_at": "2026-08-31T18:04:00Z" }
],
"created_at": "2026-08-31T16:00:00Z",
"updated_at": "2026-08-31T18:05:00Z"
}
]
}POSTCrear factura/v1/projects/{project_id}/stores/{store_id}/invoicesLectura y escritura
Crea una factura atómicamente con destinos de wallet, cotizaciones exactas recientes, historial de auditoría y entradas en la bandeja de salida de notificaciones. Repetir los mismos bytes del cuerpo original con la misma credencial e Idempotency-Key devuelve la factura original.
- payment_methods filtra los métodos habilitados de la tienda solo para esta factura. Omitido/null conserva todos los métodos; [] es inválido. Busca la indicación chain_slug y los símbolos visibles de activos en Proyecto → Tiendas → Métodos de pago. La lista API payment-assets proporciona chain_slug, asset.symbol y asset.id. Usa {chain_slug: ethereum, asset_tickers: [USDC, USDT]} para tokens Ethereum aceptados; BTC y PEPE funcionan igual en sus redes elegidas. Los símbolos no distinguen mayúsculas, se limitan a una red y solo se resuelven dentro de la tienda. Dos contratos aceptados con el mismo símbolo devuelven 400 en lugar de elegir uno, incluso si uno no está listo; usa asset_ids en ese caso. Activos nativos, tokens de catálogo y personalizados siguen las mismas reglas. Cada cadena/vía puede aparecer una vez; máximo 64 métodos finales. Merchant 5.4.0+: se ignoran opciones desconocidas, desactivadas, de red incorrecta o no aceptadas. Si toda la selección carece de coincidencias activas aceptadas, se usan los valores de la tienda; en otro caso, solo las coincidencias. Una entrada solo de red incluye todos los activos activos aceptados en esa blockchain. Los métodos activos seleccionados necesitan wallets válidas, adaptadores de escáner instalados y tipos fiables. Desde 6.0.6, escáneres no disponibles, pausas y comprobaciones de estado pendientes/antiguas no bloquean crear facturas ni quitan métodos en blockchain configurados. La detección reintenta automáticamente; liquidar sigue requiriendo cuórum de proveedores y confirmaciones. Supervisa receive_readiness y mantén proveedores disponibles: una factura puede seguir sin verificar hasta que se recuperen los escáneres. La asignación de subdirecciones Monero y la generación BOLT11 Lightning siguen requiriendo su servicio externo de wallet/nodo. Los fallos devuelven error.message más error.details.payment_methods con chain_slug, asset_ticker, reason_code y, para diagnósticos de escáner, required_endpoint_role, healthy_endpoints y required_independent_providers. TRON acepta historial indexado o API directas compatibles de bloques nativos solidificados; el estado básico por sí solo no demuestra compatibilidad con el escáner. Los fallos de precios identifican el activo/moneda. Nada activa un activo no aceptado ni cambia la política de tienda. En versiones de merchant anteriores a 5.4.0, las opciones explícitas desconocidas/inactivas fallan. Los métodos de facturas existentes nunca se amplían al cambiar ajustes de tienda. Lightning debe seleccionarse por separado. Las repeticiones conservan los métodos originales y cambiar selecciones con el mismo Idempotency-Key devuelve 409.
- checkout_appearance admite todos los ajustes de presentación indicados arriba. Los campos omitidos se heredan, los arrays sustituyen y los campos de mensajes anidados se combinan; un objeto de mensaje vacío borra ese ámbito. El diseño e imágenes resueltos se guardan para esta factura sin editar la tienda. Lee appearance del JSON público de pago para inspeccionar el resultado. La solicitud completa se limita a 32 KiB y los ajustes resueltos a 20 KiB.
- Cambiar checkout_appearance con el mismo Idempotency-Key devuelve 409; reintenta con bytes originales idénticos. La apariencia no cambia importes, tipos, activos aceptados, confirmaciones requeridas, estado real ni permisos de integración. Sin HTML, CSS, scripts ni descarga de imágenes remotas.
- exchange_rate_spread_percent sustituye el valor de la tienda para esta factura: omite o envía null para heredar, o envía "0" para desactivarlo. Las cotizaciones de facturas existentes nunca cambian.
- El margen se aplica antes del redondeo al alza. Las comisiones siguen basadas en el importe fiat original de la factura, excluido el margen.
- Envía siempre el expected_amount o expected_amount_atomic devuelto. El redondeo es al alza, limitado por la precisión del activo, 0.1% del importe y una unidad menor fiat.
- Los reintentos deben conservar credencial, Idempotency-Key y bytes exactos del cuerpo. Cambiar el margen con la misma clave devuelve 409 idempotency_conflict.
- Se comprueba una repetición exacta antes de nuevas cotizaciones, DNS de notificación o preparación de direcciones. El ámbito de la credencial y la autorización de proyecto/tienda se siguen comprobando en cada solicitud.
- Una ipn_url efectiva requiere el secreto de firma IPN de la tienda. Se rechazan campos de cuerpo desconocidos.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Idempotency-Key | obligatorio | 1–128 caracteres ASCII visibles únicos, sin espacios en blanco. |
| Content-Type | recomendado | application/json. El manejador actual del cuerpo original analiza JSON sin exigir el tipo de contenido. |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Copia el ID API de proyecto desde Proyecto → Ajustes → IDs API. Debe estar asignado a la credencial; no se acepta un identificador legible del proyecto. |
| store_id | path UUID | Copia el ID API de tienda desde Proyecto → Tiendas → selecciona una tienda → Básico → IDs API. Obligatorio incluso para la tienda predeterminada; debe estar habilitada y pertenecer a project_id. |
Cuerpo de creación de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| amount | string | obligatorio | Cadena decimal simple sin signo ni exponente, hasta 48 dígitos enteros y 30 decimales. Debe ser positiva por defecto. Una tienda puede permitir facturas de importe cero en Tiendas → Factura; los totales cero se liquidan sin recibir fondos, asignar direcciones ni comisiones de procesamiento. |
| currency | string | null | opcional | Moneda fiat compatible de tres letras, normalizada a mayúsculas. Omitida o null hereda la moneda de factura de la tienda. La creación también requiere un tipo de conversión de facturación disponible de forma independiente. |
| payment_methods | InvoicePaymentSelection[] | null | opcional | Selecciona métodos habilitados de tienda para esta factura. Merchant 5.4.0+: ignora opciones desconocidas/inactivas/no aceptadas; si ninguna coincide, usa valores de tienda. Omitido/null también usa valores de tienda; [] es inválido. Nunca activa un método ni cambia ajustes de tienda. Consulta el esquema de selección de abajo. |
| order_id | string | null | opcional | Referencia de pedido del comercio, 1–128 caracteres tras quitar espacios de extremos; se rechazan caracteres de control. |
| string | null | opcional | Email de cliente solo para el comercio, normalizado a una dirección ASCII utilizable de máximo 254 caracteres. Omitido o null no guarda email. | |
| description | string | null | opcional | Descripción visible para el cliente, 1–500 caracteres; se permiten saltos de línea y tabulaciones. |
| expires_in_seconds | integer | null | opcional | Validez de la cotización de factura de 300 a 86,400 segundos; omitido o null hereda la política de tienda. |
| exchange_rate_spread_percent | decimal string | null | opcional | Margen de cotización de 0 a 100, máximo dos decimales. Omitido o null hereda el valor de la tienda; "0" lo desactiva para esta factura. Se aplica antes del redondeo al alza y queda fijado. No cambia el importe fiat de factura ni la base de comisión de procesamiento. |
| underpayment_tolerance_percent | decimal string | null | opcional | Diferencia por defecto aceptada de 0 a 99.99 con máximo dos decimales. Omitido o null hereda el valor de tienda. |
| ipn_url | string | null | opcional | Notificación HTTPS pública, máximo 2,048 bytes y sin credenciales ni fragmento. Sustituye el valor de tienda; null/omitido lo hereda. |
| redirect_url | string | null | opcional | URL HTTPS de éxito tras liquidar, máximo 2,048 bytes y sin credenciales incluidas. Omitido o null hereda el valor de tienda y no puede borrarlo. |
| cancel_url | string | null | opcional | URL HTTPS de retorno cuando el pago termina sin éxito. Omitido o null hereda el valor de tienda y no puede borrarlo. |
| redirect_automatically | boolean | null | opcional | Omitido o null hereda la política de tienda. true requiere una redirect_url efectiva. |
| language | string | null | opcional | Etiqueta BCP 47 inglesa o alemana como en, de o de-DE; omitido o null hereda la política de tienda. |
| checkout_appearance | CheckoutAppearanceOverride | null | opcional | Ajustes parciales de presentación para esta factura. Omitido/null sigue el diseño actual de la tienda. Un objeto, incluido {}, fija el diseño e imágenes resueltos al crear. Consulta el esquema de personalización de abajo; sin ajustes financieros, HTML, CSS, JavaScript ni URL de imágenes remotas. |
| metadata | object | null | opcional | Objeto JSON solo para el comercio; omitido o null pasa a {}, máximo 4,096 bytes codificados y cinco niveles anidados. firstname, lastname, street, street2, zip, city, country, countryiso2, company y vatid se validan, normalizan y proyectan en campos de resumen del cliente. |
InvoicePaymentSelection · elige redes y activos de tienda
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug | string | obligatorio | Copia chain_slug en Proyecto → Tiendas → Métodos de pago o léelo de GET /v1/projects/{project_id}/stores/{store_id}/payment-assets, como ethereum, base o bitcoin. Un par cadena/vía solo puede aparecer una vez. |
| asset_ids | UUID[] | null | opcional | UUID asset.id en blockchain, no direcciones de contrato ni IDs de métodos de factura. Usa esto O asset_tickers. Omite ambos selectores para todos los activos activos aceptados en esta red. [] y los IDs duplicados/nulos son inválidos. En 5.4.0+, ignora IDs no activos/aceptados en esta red de esta tienda; una selección sin coincidencias usa valores de tienda. |
| asset_tickers | string[] | null | opcional | Merchant 5.3.0+. Símbolos como BTC, USDC o PEPE limitados a chain_slug y esta tienda. 1–64 símbolos únicos; recorta espacios y no distingue mayúsculas, 1–40 letras/dígitos/punto/guion bajo/guion ASCII. Usa esto O asset_ids. En 5.4.0+, ignora símbolos desconocidos/inactivos/no aceptados. Los símbolos aceptados ambiguos siguen fallando: usa asset_ids. Los métodos activos seleccionados necesitan wallets y precios válidos; interrupciones temporales del escáner en blockchain no bloquean crear desde 6.0.6. Lightning opcionalmente acepta solo BTC. |
| payment_rail | onchain | lightning | opcional | Por defecto onchain. Para elegir Bitcoin Lightning, usa {chain_slug: bitcoin, payment_rail: lightning} sin asset_ids; asset_tickers puede ser opcionalmente [BTC]. Bitcoin en blockchain no incluye Lightning. La conexión Lightning de la tienda ya debe estar activada y lista. |
CheckoutAppearanceOverride · todos los campos opcionales
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| inherit_default_store | boolean | opcional | true elige como base el diseño de la tienda predeterminada del proyecto; si no, usa el diseño efectivo de la tienda destino. Después se aplican y guardan las personalizaciones de forma independiente; el indicador resuelto de factura es false. |
| title | string | opcional | Título de pago, hasta 120 caracteres. Vacío usa el título estándar. |
| intro / outro | string | opcional | Texto simple, hasta 2,000 caracteres cada uno. Intro aparece arriba y Outro abajo en todos los estados. Se conservan saltos de línea; las URL seguras del texto se vuelven enlaces. Una cadena vacía borra. Se acepta el antiguo customer_message como alias de intro; no envíes ambos. |
| intro_font_size / outro_font_size | integer | opcional | Píxeles: 12, 14, 16, 18, 20 o 24. Predeterminado 16 salvo herencia diferente. |
| theme | system | light | dim | dark | opcional | Sigue el dispositivo del cliente o usa un tema fijo. |
| accent_color / background_color / card_color / button_color | string | opcional | #RRGGBB. Fondo, tarjeta y botón pueden estar vacíos para colores automáticos. El contraste del texto es automático. |
| logo_size / logo_alignment | string | opcional | small, medium o large; left o center. |
| images | object | opcional | Claves logo_light, logo_dark, favicon. Omitir una clave conserva la imagen base; null la quita. Un objeto {store_id: UUID, kind?: logo_light|logo_dark|favicon} reutiliza la imagen subida efectiva de esa tienda en el MISMO proyecto. kind usa como predeterminado la clave destino. Primero sube la imagen en Tienda → Pago; copia el ID API de tienda de Básico → IDs API. Imágenes ausentes o IDs de otros proyectos devuelven 400. No se aceptan URL externas ni datos de imágenes. |
| show_order_id / show_description / details_expanded | boolean | opcional | Muestra detalles del ID de pedido y descripción de texto simple bajo el título. details_expanded abre inicialmente los detalles del ID. Solo afecta a la visualización, no oculta datos. |
| show_project_name / show_store_name | boolean | opcional | Merchant 5.6.0+: muestra u oculta cada nombre en el encabezado del pago del cliente. Ambos usan true por defecto. También disponible en Tienda → Pago; se hereda y guarda por factura como los demás ajustes de apariencia. Solo visual, no oculta datos. |
| featured_chains | string[] | opcional | Slugs de red ordenados, máximo 60 valores únicos (letras minúsculas, dígitos, guiones; hasta 64 caracteres). [] borra. Solo reordena métodos disponibles de factura. |
| featured_asset_ids / default_asset_id | UUID[] / UUID|null | opcional | Hasta 100 IDs únicos de activos ordenados; [] borra. El activo predeterminado puede ser null. Los IDs vienen de payment-assets, no de intenciones de pago. Nunca activan métodos; los pagos recibidos y preferencias válidas del cliente tienen prioridad. |
| messages | object | opcional | Objetos en/de con cadenas simples waiting, confirming, paid, underpaid, expired (500 caracteres cada una). Solo cambian los idiomas/estados enviados; {} borra todos los mensajes, {en:{}} borra inglés y una cadena de estado vacía borra ese estado. El idioma alternativo es inglés. No sustituye el estado real. |
| support_email | string | opcional | Email ASCII, hasta 254 caracteres. Vacío borra. |
| support_url / terms_url / privacy_url | string | opcional | URL HTTPS de hasta 2,048 caracteres, sin credenciales. Vacío borra. Los enlaces se abren en una ventana nueva. |
| return_button_text | string | opcional | Etiqueta de hasta 60 caracteres. Usa redirect_url/cancel_url/redirect_automatically/language de nivel superior para el comportamiento de factura. |
Resumen de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | UUID interno de factura. No lo uses en rutas de detalle de comercio ni de pago. |
| invoice_id | UUID | siempre | UUID público de factura usado por rutas de detalle de comercio y pago. |
| project_id | UUID | siempre | Proyecto propietario. |
| store_id | UUID | siempre | Tienda propietaria. |
| source | manual | api | siempre | Cómo se creó la factura. |
| order_id | string | null | siempre | Referencia de pedido del comercio. |
| string | null | siempre | Email de cliente solo para el comercio. Nunca se devuelve en el pago público. | |
| customer_name | string | null | siempre | Nombre visible derivado de metadatos privados firstname, lastname y company. |
| customer_address | string | null | siempre | Dirección en una línea para el comercio derivada de metadatos privados company, street, street2, zip, city, country, countryiso2 y vatid. |
| description | string | null | siempre | Descripción visible para el cliente. |
| amount | decimal string | siempre | Importe canónico de factura. |
| currency | string | siempre | Código normalizado de moneda/activo de factura. |
| exchange_rate_spread_percent | decimal string | siempre | Margen de cotización fijado: el valor personalizado al crear o el de la tienda si se omite. Se aplica antes del redondeo al alza; nunca cambia en esta factura. |
| underpayment_tolerance_percent | decimal string | siempre | Porcentaje inmutable de diferencia por defecto aceptada, capturado al crear la factura. |
| status | invoice status | siempre | new, processing, settled, expired, invalid o cancelled. |
| amount_status | amount status | siempre | none, partial, paid u overpaid. Una factura de importe cero permitida expresamente se liquida con none y sin métodos de pago. |
| timing_status | timing status | siempre | on_time o late. |
| resolution | resolution | siempre | automatic, manually_settled o manually_invalidated. |
| sequence | integer | siempre | Secuencia monótona del estado de factura, desde 1. |
| winning_payment_intent_id | UUID | null | siempre | Método de pago que resolvió la factura, si está seleccionado. |
| expires_at | RFC 3339 timestamp | siempre | Plazo de cotización/pago. |
| monitoring_expires_at | RFC 3339 timestamp | siempre | Último límite configurado de seguimiento tardío entre los métodos de pago. |
| settled_at | timestamp | null | siempre | Hora de liquidación cuando está liquidada. |
| cancelled_at | timestamp | null | siempre | Hora de cancelación cuando está cancelada. |
| archived_at | timestamp | null | siempre | Hora de archivo cuando está archivada. |
| created_at | RFC 3339 timestamp | siempre | Hora de creación. |
| updated_at | RFC 3339 timestamp | siempre | Hora de la última actualización de estado. |
Datos adicionales del detalle de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ipn_url | string | null | siempre | Destino IPN efectivo por factura. Solo en respuesta al comercio; se omite en el pago público. |
| redirect_url | string | null | siempre | URL efectiva de éxito usada tras liquidar. |
| cancel_url | string | null | siempre | URL efectiva de retorno cuando el pago termina sin éxito. |
| redirect_automatically | boolean | siempre | Si la página de pago debe redirigir automáticamente tras el éxito. |
| checkout_language | string | siempre | Etiqueta efectiva de idioma de la página de pago. |
| metadata | object | siempre | Metadatos del comercio. Nunca se devuelven en el pago público. |
| payment_intents | PaymentIntent[] | siempre | Métodos de pago cotizados y estado de seguimiento. |
PaymentIntent
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | Identificador de intención de pago; también usado como intent_id del QR de pago. |
| payment_rail | onchain | lightning | siempre | Transporte de factura. Bitcoin en blockchain y Lightning pueden compartir asset_id; usa el ID de intención más este campo, no solo el símbolo. Difiere del payment_rail del escáner del catálogo de activos. |
| bolt11 | string | null | siempre | Solicitud de pago Lightning, en otro caso null. Paga esta solicitud con una wallet Lightning; nunca envíes fondos en blockchain a su hash de pago. |
| asset_id | UUID | siempre | Identificador configurado de activo de pago. |
| asset_key | string | siempre | Clave canónica de activo con estilo CAIP. |
| chain_slug | string | siempre | Identificador de cadena Wholly Crypto. |
| network | string | siempre | Red configurada, actualmente mainnet para activos de pago compatibles. |
| caip_network_id | string | siempre | Identificador canónico de red CAIP-2. |
| caip_asset_id | string | null | siempre | Identificador canónico CAIP-19 si está registrado. |
| symbol | string | siempre | Símbolo del activo. |
| asset_decimals | integer | siempre | Precisión en unidades atómicas. Lightning BTC usa 11 (millisatoshis), no los 8 de Bitcoin en blockchain. Las cotizaciones son en satoshis enteros; los recibos conservan precisión de millisatoshi. |
| status | intent status | siempre | pending, partial, paid, overpaid, expired o invalid. |
| finality_mode | confirmations | finalized | siempre | Política de finalidad. |
| required_confirmations | integer | siempre | Confirmaciones requeridas cuando corresponda. |
| quote_rate | decimal string | siempre | Unidades de activo por una unidad de moneda de factura, incluido el margen fijado. Por ejemplo, 1.02 USDC por USD. No es el tipo inverso. |
| quote_details | object | null | siempre | Procedencia fijada de cotización: reference_rate antes del margen, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at y asset_fetched_at. Null en facturas antiguas; no se inventan valores históricos. |
| expected_amount | decimal string | siempre | Importe exacto fijado del activo a pagar tras margen y redondeo al alza. Desde 4.1.1, las stablecoins fiat reconocidas y verificadas (como USDC, USDT, DAI, USDS, EURC) se redondean hacia arriba a máximo dos decimales; 1.321 pasa a 1.33, nunca 1.32. Este es el importe esperado incluso con tolerancia cero. Otros activos conservan precisión adaptativa. Nunca se recalculan facturas existentes. |
| expected_amount_atomic | integer string | siempre | Importe exacto en la unidad mínima del activo. |
| minimum_payment_amount | decimal string | siempre | Importe mínimo aceptado como pagado tras aplicar la tolerancia de factura. |
| minimum_payment_amount_atomic | integer string | siempre | Umbral aceptado exacto en la unidad mínima del activo. |
| received_amount | decimal string | siempre | Importe observado. |
| received_amount_atomic | integer string | siempre | Importe atómico observado. |
| confirmed_amount | decimal string | siempre | Importe confirmado/final. |
| confirmed_amount_atomic | integer string | siempre | Importe atómico confirmado/final. |
| destination_address | string | siempre | Dirección receptora en blockchain o hash de pago de 64 caracteres para Lightning. Usa bolt11 para pagar Lightning; su hash no es una dirección Bitcoin. |
| destination_tag | string | null | siempre | Referencia pública de pago obligatoria si la vía la usa: tag destino XRP, ID memo Stellar o comentario de factura TON. Null para vías de direcciones únicas. |
| derivation_index | integer | siempre | Índice derivado reservado de wallet; solo detalle del comercio. |
| quote_expires_at | RFC 3339 timestamp | siempre | Vencimiento de cotización. |
| monitoring_expires_at | RFC 3339 timestamp | siempre | Límite de seguimiento tardío de este método. |
| next_check_at | timestamp | null | siempre | Próxima comprobación programada de red. |
| last_checked_at | timestamp | null | siempre | Última comprobación de red. |
| last_chain_height | integer | null | siempre | Última altura fiable observada por el monitor. |
| last_anchor_hash | string | null | siempre | Último anclaje/hash de bloque del monitor. |
| last_monitor_error | string | null | siempre | Diagnóstico seguro de seguimiento para operadores. |
| first_payment_at | timestamp | null | siempre | Hora del primer pago observado. |
| fully_paid_at | timestamp | null | siempre | Hora en que se alcanzó por primera vez el mínimo aceptado. |
| finalized_at | timestamp | null | siempre | Hora en que el pago cumplió la política de finalidad. |
PaymentMethodIssue
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | cuando se conoce | Identifica la red y activo afectados. Lightning puede omitir asset_id. |
| reason_code | string | siempre | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled o asset_not_accepted. |
| message / action | string | cuando está disponible | Explicación para el comercio e identificador de acción: chain_connections, wallets, rates, payment_methods, project_settings o store_settings. Sin credenciales ni URL privadas de proveedores. |
| required_endpoint_role | string | null | en blockchain | Rol API preferido del escáner (campo heredado). Usa accepted_endpoint_roles para la lista completa de compatibilidad. El estado básico de un nodo no demuestra soporte de historial de pagos. |
| accepted_endpoint_roles | string[] | null | en blockchain | Dialectos API compatibles, no prueba de historial ni capacidad del endpoint. node-rpc directo admite BTC/BCH/LTC/DOGE/DASH y ZEC transparente (bloques completos decodificados, 1–48 confirmaciones), TRX nativo solidificado, ALGO nativo mediante algod, XTZ mediante Octez, DOT finalizado de Asset Hub mediante metadatos SCALE y XLM nativo mediante Stellar RPC con ID memo de factura. El historial podado o incompleto no sirve. Estos adaptadores directos no añaden vías de tokens. Las API indexadas siguen siendo alternativas; consulta la tabla de vías de abajo. Las fuentes directas/indexadas mixtas verifican ventanas limitadas de forma independiente; el valor predeterminado sigue siendo dos proveedores independientes, no alias de un mismo operador. La altura básica de nodo, información de red ORDnet y un relay EVM para una vía no EVM no son pruebas de recepción. Monero sigue necesitando una wallet-RPC de solo lectura vinculada al proyecto. |
| healthy_endpoints | integer | en blockchain | Endpoints sanos coincidentes, no el número de proveedores independientes. |
| usable_independent_providers / required_independent_providers | integer | en blockchain | Plazas de verificación utilizables, limitadas a dos. required_independent_providers es el ajuste de red: 2 por defecto o 1 tras elección expresa del administrador. En modo de dos proveedores se requieren claves de proveedor Y hosts diferentes. Las fuentes desactivadas, antiguas (más de diez minutos) o en pausa no ocupan una plaza. Lightning usa sus propias reglas de conexión. |
| last_checked_at | timestamp | null | en blockchain | Última comprobación de estado de endpoint coincidente, separada de la hora de evaluación. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 201 factura nueva; 200 repetición idempotente exacta
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "new",
"amount_status": "none",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 1,
"winning_payment_intent_id": null,
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:00:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "pending",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0",
"received_amount_atomic": "0",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": "2026-08-31T18:00:00Z",
"last_checked_at": null,
"last_chain_height": null,
"last_anchor_hash": null,
"last_monitor_error": null,
"first_payment_at": null,
"fully_paid_at": null,
"finalized_at": null
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GETListar facturas/v1/projects/{project_id}/invoicesSolo lectura
Devuelve una página compacta de resúmenes de factura del ámbito, más recientes primero, incluidos email solo para el comercio y campos de cliente derivados de metadatos reconocidos. Los filtros de búsqueda, estado y tienda se evalúan en servidor; la respuesta incluye total y has_more para paginación predecible.
- Ordenado por created_at descendente y después id interno descendente.
- Los elementos son objetos InvoiceSummary; email, customer_name y customer_address son solo para el comercio. Consulta el detalle para metadatos originales e intenciones de pago.
- Para la página siguiente, define offset como pagination.offset + pagination.limit solo si has_more es true.
- El recuento y la página se leen desde una instantánea de base de datos con lectura repetible; las escrituras simultáneas aparecen en una solicitud posterior.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto habilitado asignado a la credencial. |
| store_id | query UUID | Filtro exacto opcional de tienda. |
| status | query enum | Opcional: new, processing, settled, expired, invalid o cancelled. |
| search | query string | Prefijo opcional de ID de factura, ID de pedido o email sin distinguir mayúsculas; UUID exacto de factura; o subcadena en descripción y campos reconocidos de cliente. Todas las claves de metadatos y valores de texto, numéricos y booleanos (incluidos objetos/arrays anidados) también admiten búsqueda indexada por prefijo de palabra: cada palabra buscada debe coincidir y la puntuación se trata como separador. Se recortan extremos, máximo 100 caracteres, sin controles. Las coincidencias de metadatos no añaden metadatos originales a respuestas de lista; usa el detalle de factura para leerlos. |
| limit | query integer | Opcional 1–100; predeterminado 50. |
| offset | query integer | Opcional 0–1,000,000; predeterminado 0. |
Resumen de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | UUID interno de factura. No lo uses en rutas de detalle de comercio ni de pago. |
| invoice_id | UUID | siempre | UUID público de factura usado por rutas de detalle de comercio y pago. |
| project_id | UUID | siempre | Proyecto propietario. |
| store_id | UUID | siempre | Tienda propietaria. |
| source | manual | api | siempre | Cómo se creó la factura. |
| order_id | string | null | siempre | Referencia de pedido del comercio. |
| string | null | siempre | Email de cliente solo para el comercio. Nunca se devuelve en el pago público. | |
| customer_name | string | null | siempre | Nombre visible derivado de metadatos privados firstname, lastname y company. |
| customer_address | string | null | siempre | Dirección en una línea para el comercio derivada de metadatos privados company, street, street2, zip, city, country, countryiso2 y vatid. |
| description | string | null | siempre | Descripción visible para el cliente. |
| amount | decimal string | siempre | Importe canónico de factura. |
| currency | string | siempre | Código normalizado de moneda/activo de factura. |
| exchange_rate_spread_percent | decimal string | siempre | Margen de cotización fijado: el valor personalizado al crear o el de la tienda si se omite. Se aplica antes del redondeo al alza; nunca cambia en esta factura. |
| underpayment_tolerance_percent | decimal string | siempre | Porcentaje inmutable de diferencia por defecto aceptada, capturado al crear la factura. |
| status | invoice status | siempre | new, processing, settled, expired, invalid o cancelled. |
| amount_status | amount status | siempre | none, partial, paid u overpaid. Una factura de importe cero permitida expresamente se liquida con none y sin métodos de pago. |
| timing_status | timing status | siempre | on_time o late. |
| resolution | resolution | siempre | automatic, manually_settled o manually_invalidated. |
| sequence | integer | siempre | Secuencia monótona del estado de factura, desde 1. |
| winning_payment_intent_id | UUID | null | siempre | Método de pago que resolvió la factura, si está seleccionado. |
| expires_at | RFC 3339 timestamp | siempre | Plazo de cotización/pago. |
| monitoring_expires_at | RFC 3339 timestamp | siempre | Último límite configurado de seguimiento tardío entre los métodos de pago. |
| settled_at | timestamp | null | siempre | Hora de liquidación cuando está liquidada. |
| cancelled_at | timestamp | null | siempre | Hora de cancelación cuando está cancelada. |
| archived_at | timestamp | null | siempre | Hora de archivo cuando está archivada. |
| created_at | RFC 3339 timestamp | siempre | Hora de creación. |
| updated_at | RFC 3339 timestamp | siempre | Hora de la última actualización de estado. |
Paginación de facturas
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| limit | integer | siempre | Tamaño efectivo de página, 1–100. |
| offset | integer | siempre | Desplazamiento efectivo de filas desde cero, 0–1,000,000. |
| total | integer | siempre | Total de filas que coinciden con filtros de proyecto, tienda, estado y búsqueda en la instantánea de página. |
| has_more | boolean | siempre | True si offset más el número de filas devueltas es menor que total. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": [
{
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:04:10Z"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 143,
"has_more": true
}
}GETObtener factura/v1/projects/{project_id}/invoices/{invoice_id}Solo lectura
Devuelve el detalle completo de factura de comercio y la URL activa actual de pago. Usa esta ruta para sondeo y conciliación.
- Una consulta limitada al ámbito devuelve deliberadamente invoice_not_found si el ID público no está en el proyecto autorizado.
- links.checkout usa Tienda → Básico → Dominios de tienda: el host de pago activo de esta tienda, después la elección de su tienda predeterminada y después el principal del sistema. Se ignoran hosts retirados, borradores o de servicio incorrecto. También se aplica a respuestas de creación y MCP; los enlaces se resuelven al responder, incluidas repeticiones idempotentes. Los enlaces firmados de notificaciones quedan fijos al crear el evento y no se reescriben al reintentar. Estas preferencias solo generan enlaces; no redirigen tráfico ni cambian restricciones IP.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto habilitado asignado a la credencial. |
| invoice_id | path UUID | El invoice_id devuelto al crear/listar, no el id interno. |
Resumen de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | UUID interno de factura. No lo uses en rutas de detalle de comercio ni de pago. |
| invoice_id | UUID | siempre | UUID público de factura usado por rutas de detalle de comercio y pago. |
| project_id | UUID | siempre | Proyecto propietario. |
| store_id | UUID | siempre | Tienda propietaria. |
| source | manual | api | siempre | Cómo se creó la factura. |
| order_id | string | null | siempre | Referencia de pedido del comercio. |
| string | null | siempre | Email de cliente solo para el comercio. Nunca se devuelve en el pago público. | |
| customer_name | string | null | siempre | Nombre visible derivado de metadatos privados firstname, lastname y company. |
| customer_address | string | null | siempre | Dirección en una línea para el comercio derivada de metadatos privados company, street, street2, zip, city, country, countryiso2 y vatid. |
| description | string | null | siempre | Descripción visible para el cliente. |
| amount | decimal string | siempre | Importe canónico de factura. |
| currency | string | siempre | Código normalizado de moneda/activo de factura. |
| exchange_rate_spread_percent | decimal string | siempre | Margen de cotización fijado: el valor personalizado al crear o el de la tienda si se omite. Se aplica antes del redondeo al alza; nunca cambia en esta factura. |
| underpayment_tolerance_percent | decimal string | siempre | Porcentaje inmutable de diferencia por defecto aceptada, capturado al crear la factura. |
| status | invoice status | siempre | new, processing, settled, expired, invalid o cancelled. |
| amount_status | amount status | siempre | none, partial, paid u overpaid. Una factura de importe cero permitida expresamente se liquida con none y sin métodos de pago. |
| timing_status | timing status | siempre | on_time o late. |
| resolution | resolution | siempre | automatic, manually_settled o manually_invalidated. |
| sequence | integer | siempre | Secuencia monótona del estado de factura, desde 1. |
| winning_payment_intent_id | UUID | null | siempre | Método de pago que resolvió la factura, si está seleccionado. |
| expires_at | RFC 3339 timestamp | siempre | Plazo de cotización/pago. |
| monitoring_expires_at | RFC 3339 timestamp | siempre | Último límite configurado de seguimiento tardío entre los métodos de pago. |
| settled_at | timestamp | null | siempre | Hora de liquidación cuando está liquidada. |
| cancelled_at | timestamp | null | siempre | Hora de cancelación cuando está cancelada. |
| archived_at | timestamp | null | siempre | Hora de archivo cuando está archivada. |
| created_at | RFC 3339 timestamp | siempre | Hora de creación. |
| updated_at | RFC 3339 timestamp | siempre | Hora de la última actualización de estado. |
Datos adicionales del detalle de factura
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| ipn_url | string | null | siempre | Destino IPN efectivo por factura. Solo en respuesta al comercio; se omite en el pago público. |
| redirect_url | string | null | siempre | URL efectiva de éxito usada tras liquidar. |
| cancel_url | string | null | siempre | URL efectiva de retorno cuando el pago termina sin éxito. |
| redirect_automatically | boolean | siempre | Si la página de pago debe redirigir automáticamente tras el éxito. |
| checkout_language | string | siempre | Etiqueta efectiva de idioma de la página de pago. |
| metadata | object | siempre | Metadatos del comercio. Nunca se devuelven en el pago público. |
| payment_intents | PaymentIntent[] | siempre | Métodos de pago cotizados y estado de seguimiento. |
PaymentIntent
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| id | UUID | siempre | Identificador de intención de pago; también usado como intent_id del QR de pago. |
| payment_rail | onchain | lightning | siempre | Transporte de factura. Bitcoin en blockchain y Lightning pueden compartir asset_id; usa el ID de intención más este campo, no solo el símbolo. Difiere del payment_rail del escáner del catálogo de activos. |
| bolt11 | string | null | siempre | Solicitud de pago Lightning, en otro caso null. Paga esta solicitud con una wallet Lightning; nunca envíes fondos en blockchain a su hash de pago. |
| asset_id | UUID | siempre | Identificador configurado de activo de pago. |
| asset_key | string | siempre | Clave canónica de activo con estilo CAIP. |
| chain_slug | string | siempre | Identificador de cadena Wholly Crypto. |
| network | string | siempre | Red configurada, actualmente mainnet para activos de pago compatibles. |
| caip_network_id | string | siempre | Identificador canónico de red CAIP-2. |
| caip_asset_id | string | null | siempre | Identificador canónico CAIP-19 si está registrado. |
| symbol | string | siempre | Símbolo del activo. |
| asset_decimals | integer | siempre | Precisión en unidades atómicas. Lightning BTC usa 11 (millisatoshis), no los 8 de Bitcoin en blockchain. Las cotizaciones son en satoshis enteros; los recibos conservan precisión de millisatoshi. |
| status | intent status | siempre | pending, partial, paid, overpaid, expired o invalid. |
| finality_mode | confirmations | finalized | siempre | Política de finalidad. |
| required_confirmations | integer | siempre | Confirmaciones requeridas cuando corresponda. |
| quote_rate | decimal string | siempre | Unidades de activo por una unidad de moneda de factura, incluido el margen fijado. Por ejemplo, 1.02 USDC por USD. No es el tipo inverso. |
| quote_details | object | null | siempre | Procedencia fijada de cotización: reference_rate antes del margen, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at y asset_fetched_at. Null en facturas antiguas; no se inventan valores históricos. |
| expected_amount | decimal string | siempre | Importe exacto fijado del activo a pagar tras margen y redondeo al alza. Desde 4.1.1, las stablecoins fiat reconocidas y verificadas (como USDC, USDT, DAI, USDS, EURC) se redondean hacia arriba a máximo dos decimales; 1.321 pasa a 1.33, nunca 1.32. Este es el importe esperado incluso con tolerancia cero. Otros activos conservan precisión adaptativa. Nunca se recalculan facturas existentes. |
| expected_amount_atomic | integer string | siempre | Importe exacto en la unidad mínima del activo. |
| minimum_payment_amount | decimal string | siempre | Importe mínimo aceptado como pagado tras aplicar la tolerancia de factura. |
| minimum_payment_amount_atomic | integer string | siempre | Umbral aceptado exacto en la unidad mínima del activo. |
| received_amount | decimal string | siempre | Importe observado. |
| received_amount_atomic | integer string | siempre | Importe atómico observado. |
| confirmed_amount | decimal string | siempre | Importe confirmado/final. |
| confirmed_amount_atomic | integer string | siempre | Importe atómico confirmado/final. |
| destination_address | string | siempre | Dirección receptora en blockchain o hash de pago de 64 caracteres para Lightning. Usa bolt11 para pagar Lightning; su hash no es una dirección Bitcoin. |
| destination_tag | string | null | siempre | Referencia pública de pago obligatoria si la vía la usa: tag destino XRP, ID memo Stellar o comentario de factura TON. Null para vías de direcciones únicas. |
| derivation_index | integer | siempre | Índice derivado reservado de wallet; solo detalle del comercio. |
| quote_expires_at | RFC 3339 timestamp | siempre | Vencimiento de cotización. |
| monitoring_expires_at | RFC 3339 timestamp | siempre | Límite de seguimiento tardío de este método. |
| next_check_at | timestamp | null | siempre | Próxima comprobación programada de red. |
| last_checked_at | timestamp | null | siempre | Última comprobación de red. |
| last_chain_height | integer | null | siempre | Última altura fiable observada por el monitor. |
| last_anchor_hash | string | null | siempre | Último anclaje/hash de bloque del monitor. |
| last_monitor_error | string | null | siempre | Diagnóstico seguro de seguimiento para operadores. |
| first_payment_at | timestamp | null | siempre | Hora del primer pago observado. |
| fully_paid_at | timestamp | null | siempre | Hora en que se alcanzó por primera vez el mínimo aceptado. |
| finalized_at | timestamp | null | siempre | Hora en que el pago cumplió la política de finalidad. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 4,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": "2026-08-31T18:05:00Z",
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:05:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "paid",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0004554",
"received_amount_atomic": "45540",
"confirmed_amount": "0.0004554",
"confirmed_amount_atomic": "45540",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": null,
"last_checked_at": "2026-08-31T18:05:00Z",
"last_chain_height": 912345,
"last_anchor_hash": "000000000000000000example",
"last_monitor_error": null,
"first_payment_at": "2026-08-31T18:03:00Z",
"fully_paid_at": "2026-08-31T18:03:00Z",
"finalized_at": "2026-08-31T18:05:00Z"
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GETListar pagos de una factura/v1/projects/{project_id}/invoices/{invoice_id}/paymentsSolo lectura
Historial actual completo de transferencias, incluidas observaciones invalidadas. Úsalo cuando una notificación marque payments_truncated. Es el estado actual, no una reconstrucción de un evento antiguo.
- Una observación es un log de token, salida UTXO u otra transferencia de vía, no necesariamente un hash de transacción único. Elimina duplicados por payment_id; transaction_id más event_index identifica la transferencia en red.
- status es detected, confirming, final, reorged, replaced o invalid. Solo las observaciones counts_towards_received contribuyen a los importes recibidos. Nunca sumes importes de activos diferentes.
- Los registros Lightning usan payment_hash con transaction_id, confirmations y enlaces de explorador null; la precisión BTC es 11 (millisatoshis). No se exponen preimágenes, BOLT11 ni secretos de wallet.
- Ordenado por observed_at descendente y después payment_id descendente. Recuento y página usan una instantánea de lectura repetible; las páginas posteriores pueden cambiar al llegar pagos. Elimina duplicados por payment_id al paginar una factura activa.
- Se aplican el ámbito de proyecto de solo lectura, restricciones IP y límites por credencial existentes. Nunca sigas un enlace de notificación con tu token salvo que su origen coincida con tu host API configurado.
| Encabezado | Presencia | Regla |
|---|---|---|
| Authorization | obligatorio | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recomendado | application/json |
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | Proyecto asignado a esta credencial. |
| invoice_id | path UUID | invoice_id público devuelto al crear. |
| payment_method_id | optional query UUID | Limita a un método de pago de factura. |
| limit | query integer | 1–100; predeterminado 25. |
| offset | query integer | 0–1,000,000; predeterminado 0. |
Solicitud
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"data": [{
"payment_id": "44444444-4444-4444-8444-444444444444",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 12,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "55555555-5555-4555-8555-555555555555",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 25975377,
"observed_at": "2026-09-14T12:03:00Z",
"chain_time": "2026-09-14T12:02:48Z",
"finalized_at": "2026-09-14T12:04:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}],
"pagination": {"limit": 25, "offset": 0, "total": 1, "has_more": false}
}GETEstructura de la página de pago/Público
Raíz del host de pago gestionado que sirve la aplicación de pago sin seleccionar factura. Las integraciones de clientes normalmente deben usar links.checkout.
- No se necesita token bearer.
- La entrada de pago gestionada permite GET/HEAD y rechaza otros métodos.
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())Ejemplo de respuesta · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout shell -->GETPágina de pago alojada/invoice/{invoice_id}Público
Página de pago HTML para clientes. Obtiene JSON seguro para pago del mismo host. La integración en marcos se deniega salvo que la tienda la active y permita expresamente el origen HTTPS padre.
- No se acepta ni necesita token bearer.
- La estructura HTML devuelve 200 incluso si no hay factura; su solicitud JSON de pago recibe entonces invoice_not_found.
- La respuesta es no-store, noindex y tiene CSP frame-ancestors específica de factura.
- Proyecto/tienda desactivado o factura desconocida no expone datos de pago.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invoice_id | path UUID | UUID público de factura devuelto por la API de comercio. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())Ejemplo de respuesta · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout application -->GETFactura segura para la página de pago/checkout-api/invoices/{invoice_id}Público
Devuelve solo campos necesarios para mostrar el pago. Omite deliberadamente IDs internos, email del cliente y campos de dirección derivados, URL IPN, metadatos del comercio, IDs de wallets, rutas de derivación y diagnósticos del monitor.
- No se necesita token bearer.
- Cache-Control es no-store y la indexación en buscadores está desactivada.
- Trata invoice_id como dato que permite acceso al cliente; evita publicarlo innecesariamente.
- asset_icon_url es un recurso local del mismo origen; el pago del cliente nunca necesita contactar con CoinGecko para mostrarlo.
- Cuando destination_tag no sea null, muéstralo y cópialo junto a la dirección: es una tag destino XRP, ID memo Stellar o comentario de factura TON obligatorio y debe enviarse exactamente.
- Para tokens verificados, asset_kind es token, contract_address identifica el contrato ERC-20 o mint SPL exacto, token_standard identifica la vía y payment_uri contiene esa identidad de token.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invoice_id | path UUID | UUID público de factura. |
Factura pública de pago
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| invoice_id | UUID | siempre | UUID público de factura. |
| order_id | string | null | siempre | Referencia de pedido del comercio. |
| description | string | null | siempre | Descripción visible para el cliente. |
| amount | decimal string | siempre | Importe de factura. |
| currency | string | siempre | Moneda de factura. |
| exchange_rate_spread_percent | decimal string | siempre | Margen efectivo de cotización fijado al crear, incluida personalización por factura. |
| underpayment_tolerance_percent | decimal string | siempre | Porcentaje de diferencia por defecto aceptada para esta factura. |
| status | invoice status | siempre | Estado actual de factura. |
| amount_status | amount status | siempre | none, partial, paid u overpaid. Una factura de importe cero permitida expresamente se liquida con none y sin métodos de pago. |
| timing_status | timing status | siempre | on_time o late. |
| sequence | integer | siempre | Secuencia actual de estado. |
| active_payment_method_id | UUID | null | siempre | El método listado que recibió fondos. El pago permanece en este método para no continuar un pago insuficiente con un activo incompatible. |
| payment_method_locked | boolean | siempre | True después de que un pago válido seleccione active_payment_method_id. |
| server_time | RFC 3339 timestamp | siempre | Reloj del servidor capturado para esta respuesta; úsalo con expires_at para evitar desfase del reloj del cliente. |
| expires_at | RFC 3339 timestamp | siempre | Plazo de factura. |
| expires_in_seconds | integer | siempre | Segundos enteros restantes en server_time, redondeados hacia arriba y con mínimo cero. |
| payment_open | boolean | siempre | True solo si una factura new o processing está dentro del plazo y tiene al menos un método pagable con importe pendiente. |
| redirect_url | string | null | siempre | Destino de retorno del cliente tras liquidación correcta. |
| cancel_url | string | null | siempre | Destino de retorno del cliente al salir sin liquidación correcta. |
| redirect_automatically | boolean | siempre | Política de redirección automática. |
| checkout_language | string | siempre | Idioma de pago. |
| project | object | siempre | name, checkout_title, checkout_description, theme, accent_color y logo_url. |
| store | object | siempre | Nombre público de tienda. |
| appearance | CheckoutAppearance | siempre | Presentación efectiva: personalización por factura fijada si se proporciona, o diseño actual de tienda. Nunca cambia campos financieros ni avisos de seguridad. |
| payment_methods | CheckoutPaymentMethod[] | siempre | Métodos de pago seguros para la página de pago. |
CheckoutAppearance
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| inherit_default_store | boolean | siempre | True cuando la tienda predeterminada del proyecto proporciona esta apariencia. False para tiendas independientes y personalizaciones de factura fijadas. |
| invoice_override | boolean | siempre | True si se proporcionó checkout_appearance al crear la factura. Omitido/null mantiene false. |
| title / intro / outro | string | siempre | Título, mensaje superior e inferior del comercio en texto simple. intro sustituye customer_message; se conserva el texto antiguo almacenado. Nunca lo evalúes como marcado. |
| intro_font_size / outro_font_size | integer | siempre | Tamaños de fuente en píxeles: 12, 14, 16, 18, 20 o 24. |
| customer_message | string | siempre | Alias de compatibilidad obsoleto de intro. Usa intro en integraciones nuevas. |
| theme | system | light | dim | dark | siempre | Preferencia del dispositivo del cliente o tema fijo. |
| accent_color / background_color / card_color / button_color | string | siempre | Colores estrictos #RRGGBB. Los opcionales están vacíos para valores automáticos; se calcula el contraste del primer plano. |
| logo_size / logo_alignment | string | siempre | small, medium o large; left o center. Las imágenes se ajustan completas, no se recortan. |
| images | object | siempre | URL opcionales logo_light, logo_dark y favicon: imágenes PNG normalizadas, limitadas al ámbito y del mismo origen. |
| show_order_id / show_description / details_expanded | boolean | siempre | Visibilidad del ID de pedido, descripción bajo el título y expansión inicial del ID. El importe sigue visible; son controles visuales, no ocultación de datos. |
| show_project_name / show_store_name | boolean | siempre | Merchant 5.6.0+: visibilidad de nombres en el encabezado. Ambos usan true por defecto. La identidad de proyecto/tienda sigue disponible en JSON. |
| featured_chains / featured_asset_ids | array | siempre | Preferencias ordenadas, aplicadas solo a métodos ya presentes en la factura. Se ignoran métodos ausentes o desactivados. |
| default_asset_id | UUID | null | siempre | Método inicial sugerido. Una preferencia válida recordada del cliente o un método que ya recibe fondos tiene prioridad. |
| messages | object | siempre | Texto simple en/de con claves waiting, confirming, paid, underpaid y expired. Inglés como alternativa. Complementario; nunca sustituye el estado real. |
| support_email / support_url / terms_url / privacy_url | string | siempre | Contacto y enlaces HTTPS opcionales, sin credenciales en URL. Los enlaces externos abren una ventana nueva. |
| return_button_text | string | siempre | Solo etiqueta opcional. Los destinos de éxito/cancelación y la política de redirección siguen perteneciendo a la factura. |
CheckoutPaymentMethod
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| payment_rail | onchain | lightning | siempre | Lightning sigue siendo un método Bitcoin, separado de BTC en blockchain. Identifica la opción por ID de intención y vía, no solo asset_id. |
| bolt11 | string | null | siempre | Solicitud Lightning firmada; null para métodos en blockchain. Nunca pagues después de que payable pase a false. |
| payment_hash | string | null | siempre | Hash de pago Lightning para conciliación, no dirección receptora. Null para métodos en blockchain. |
| id | UUID | siempre | Identificador de intención de pago. |
| asset_id | UUID | siempre | UUID de activo usado por preferencias de apariencia; distinto del ID de intención de pago de esta factura. |
| asset_key | string | siempre | Clave canónica de activo. |
| chain_slug / chain_name | string | siempre | Nombres de red para máquina y visualización. |
| network | string | siempre | Red de pago. |
| caip_network_id | string | siempre | Identidad canónica de red para distinguir la red elegida. |
| caip_asset_id | string | null | siempre | Identidad canónica exacta del activo, incluido contrato de token o mint verificado cuando corresponda. |
| asset_name / symbol | string | siempre | Valores visuales del activo de pago. |
| asset_icon_url | string | null | siempre | Icono del activo en caché local del mismo origen, o null sin correspondencia CoinGecko verificada. |
| asset_kind | native | token | siempre | Distingue moneda nativa de pago por contrato/mint. |
| contract_address | string | null | siempre | Contrato ERC-20 o mint SPL canónico para tokens; null para moneda nativa. |
| token_standard | erc20 | spl-token | null | siempre | Implementación verificada del token, o null para moneda nativa. |
| asset_decimals | integer | siempre | Precisión atómica: 11 para millisatoshis Lightning BTC, 8 para satoshis BTC en blockchain. |
| status | intent status | siempre | Estado actual del método de pago. |
| payable | boolean | siempre | True solo si este método exacto puede aceptar pagos ahora; false para métodos inactivos después de que otro activo reciba fondos. |
| finality_mode / required_confirmations | string / integer | siempre | Política de finalidad. |
| expected_amount / expected_amount_atomic | decimal / integer string | siempre | Cotización total fijada en unidades visibles y reales en blockchain. Las stablecoins fiat reconocidas usan máximo dos decimales de cotización, siempre al alza tras el margen; otros activos usan precisión adaptativa. Los decimales reales de tokens, fondos recibidos y restos de pagos parciales siguen exactos. Usa los importes devueltos sin modificarlos. |
| minimum_payment_amount / minimum_payment_amount_atomic | decimal / integer string | siempre | Umbral de liquidación aceptado tras aplicar tolerancia de pago insuficiente. |
| received_amount / received_amount_atomic | decimal / integer string | siempre | Importe observado. |
| remaining_amount | decimal string | siempre | Importe visible exacto que falta para alcanzar el umbral aceptado, con mínimo cero. |
| remaining_amount_atomic | integer string | siempre | Diferencia hasta el umbral aceptado en unidades atómicas. No es el importe solicitado: la tolerancia solo afecta a la aceptación. |
| confirmed_amount / confirmed_amount_atomic | decimal / integer string | siempre | Importe confirmado/final. |
| destination_address / destination_tag | string / string|null | siempre | Destino en blockchain y referencia opcional. Para Lightning es el hash de pago sin tag; paga mediante bolt11/payment_uri. |
| quote_expires_at | RFC 3339 timestamp | siempre | Vencimiento de cotización. |
| payment_uri | string | null | siempre | Solicitud adaptada a la red: ERC-681, Solana Pay, URI nativa o lightning:<bolt11>. Las solicitudes con importe usan el esperado completo menos fondos recibidos, nunca el umbral de tolerancia. Null cuando payable es false, incluido tras aceptar una diferencia tolerada. El QR Lightning codifica la solicitud Lightning completa, no el hash de pago. |
| qr_url | path | null | siempre | Ruta QR SVG del mismo origen con revisión por secuencia y resto exacto, o null cuando payable es false. El SVG es no-store. |
| address_explorer_name / address_explorer_url | string|null | siempre | Explorador alternativo validado de red principal donde se admite. |
| transaction_count | integer | siempre | Total de transacciones públicas válidas distintas observadas para este método. |
| transactions_truncated | boolean | siempre | True cuando transaction_count supera la lista devuelta de transacciones recientes. |
| transactions | CheckoutTransaction[] | siempre | Hasta las 10 transacciones públicas válidas más recientes. Los totales exactos recibidos siguen independientes de este límite visual. |
CheckoutTransaction
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| transaction_id | string | siempre | Identificador de transacción observada. |
| status | detected | confirming | final | siempre | Estado público de observación. |
| confirmations | integer | siempre | Número observado de confirmaciones. |
| block_height | integer | null | siempre | Altura observada de bloque/registro. |
| explorer_name | string | cuando se devuelve | Nombre fijo validado de explorador. |
| explorer_url | string | cuando se devuelve | URL fija validada del explorador de red principal. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": {
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"order_id": "order-1042",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "partial",
"timing_status": "on_time",
"sequence": 3,
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_method_locked": true,
"server_time": "2026-08-31T18:10:00Z",
"expires_at": "2026-08-31T18:15:00Z",
"expires_in_seconds": 300,
"payment_open": true,
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/invoices/…/logo/…/image.png"
},
"store": { "name": "Online shop" },
"payment_methods": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"chain_name": "Bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"asset_name": "Bitcoin",
"symbol": "BTC",
"asset_icon_url": "/assets/coingecko/bitcoin.png",
"asset_kind": "native",
"contract_address": null,
"token_standard": null,
"asset_decimals": 8,
"status": "partial",
"payable": true,
"finality_mode": "confirmations",
"required_confirmations": 1,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0002",
"received_amount_atomic": "20000",
"remaining_amount": "0.0002554",
"remaining_amount_atomic": "25540",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"quote_expires_at": "2026-08-31T18:15:00Z",
"payment_uri": "bitcoin:bc1q…example?amount=0.00026",
"qr_url": "/checkout-api/invoices/…/payment-methods/…/qr.svg?sequence=3&amount_atomic=26000",
"address_explorer_name": "mempool.space",
"address_explorer_url": "https://mempool.space/address/…",
"transaction_count": 0,
"transactions_truncated": false,
"transactions": []
}
]
}
}GETVista previa del pago de la tienda/invoice/preview/{project_id}Público
Muestra la apariencia guardada de tienda con importe ilustrativo y metadatos reales de activos aceptados. Cambia entre ejemplos waiting, confirming, paid, underpaid y expired sin crear pagos.
- La vista previa es solo de marca y nunca debe enviarse a un cliente como solicitud de pago.
- Sin dirección receptora, QR pagable, acción de wallet, redirección ni sondeo de pagos. Los ejemplos no cambian el estado real de facturas.
- La respuesta es no-store, noindex y no puede integrarse en marcos.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | UUID de proyecto copiado al enlace de vista previa por la consola autenticada. |
| store_id | query UUID, optional | Tienda de este proyecto. Omite para usar su primera tienda/predeterminada. |
| state | query string, optional | waiting, confirming, paid, underpaid o expired. Ilustración solo en navegador. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming" \
--output 'checkout-preview.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview.html").write_bytes(response.read())Ejemplo de respuesta · 200 text/html
<!doctype html>
<!-- Hosted branding preview; no invoice is created -->GETDatos de vista previa del pago/checkout-api/previews/{project_id}Público
Devuelve apariencia efectiva de tienda y metadatos seguros de activos aceptados. payment_methods sigue vacío; preview_methods no contiene direcciones de pago, cotizaciones ni datos privados de wallet.
- No se acepta ni necesita token bearer.
- No devuelve factura, destino, wallet, transacción, IPN, webhook ni metadatos de comercio.
- Usa la consola autenticada para obtener el enlace correcto de vista previa en el dominio de pago.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | UUID de proyecto del enlace de vista previa de consola. |
| store_id | query UUID, optional | Debe pertenecer a este proyecto; los IDs no coincidentes devuelven 404. Se rechazan campos de consulta desconocidos. |
CheckoutAppearance
| Campo | Tipo | Presencia | Descripción |
|---|---|---|---|
| inherit_default_store | boolean | siempre | True cuando la tienda predeterminada del proyecto proporciona esta apariencia. False para tiendas independientes y personalizaciones de factura fijadas. |
| invoice_override | boolean | siempre | True si se proporcionó checkout_appearance al crear la factura. Omitido/null mantiene false. |
| title / intro / outro | string | siempre | Título, mensaje superior e inferior del comercio en texto simple. intro sustituye customer_message; se conserva el texto antiguo almacenado. Nunca lo evalúes como marcado. |
| intro_font_size / outro_font_size | integer | siempre | Tamaños de fuente en píxeles: 12, 14, 16, 18, 20 o 24. |
| customer_message | string | siempre | Alias de compatibilidad obsoleto de intro. Usa intro en integraciones nuevas. |
| theme | system | light | dim | dark | siempre | Preferencia del dispositivo del cliente o tema fijo. |
| accent_color / background_color / card_color / button_color | string | siempre | Colores estrictos #RRGGBB. Los opcionales están vacíos para valores automáticos; se calcula el contraste del primer plano. |
| logo_size / logo_alignment | string | siempre | small, medium o large; left o center. Las imágenes se ajustan completas, no se recortan. |
| images | object | siempre | URL opcionales logo_light, logo_dark y favicon: imágenes PNG normalizadas, limitadas al ámbito y del mismo origen. |
| show_order_id / show_description / details_expanded | boolean | siempre | Visibilidad del ID de pedido, descripción bajo el título y expansión inicial del ID. El importe sigue visible; son controles visuales, no ocultación de datos. |
| show_project_name / show_store_name | boolean | siempre | Merchant 5.6.0+: visibilidad de nombres en el encabezado. Ambos usan true por defecto. La identidad de proyecto/tienda sigue disponible en JSON. |
| featured_chains / featured_asset_ids | array | siempre | Preferencias ordenadas, aplicadas solo a métodos ya presentes en la factura. Se ignoran métodos ausentes o desactivados. |
| default_asset_id | UUID | null | siempre | Método inicial sugerido. Una preferencia válida recordada del cliente o un método que ya recibe fondos tiene prioridad. |
| messages | object | siempre | Texto simple en/de con claves waiting, confirming, paid, underpaid y expired. Inglés como alternativa. Complementario; nunca sustituye el estado real. |
| support_email / support_url / terms_url / privacy_url | string | siempre | Contacto y enlaces HTTPS opcionales, sin credenciales en URL. Los enlaces externos abren una ventana nueva. |
| return_button_text | string | siempre | Solo etiqueta opcional. Los destinos de éxito/cancelación y la política de redirección siguen perteneciendo a la factura. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Ejemplo de respuesta · 200 application/json
{
"data": {
"preview": true,
"invoice_id": "YOUR_PROJECT_ID",
"amount": "100.00",
"currency": "USD",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Choose a network and send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/previews/…/logo/…/image.png"
},
"appearance": {"inherit_default_store": true, "theme": "system", "accent_color": "#42E39B", "images": {}},
"preview_methods": [],
"payment_methods": []
}
}GETImagen de pago de la tienda/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngPúblico
Devuelve un logo o favicon normalizado de tienda perteneciente a esta factura. Usa las URL appearance.images de los datos de pago.
- Usa appearance.images del JSON de pago. Las imágenes de factura fijadas siguen funcionando después de que la tienda de origen sustituya o quite una imagen subida. Las revisiones eliminadas expresamente, de otra factura, tipo incorrecto o desconocidas devuelven 404; una instantánea nunca recurre a una imagen actual de tienda.
- Sin personalización de factura, se usa la imagen efectiva actual de tienda y las revisiones sustituidas/eliminadas devuelven 404. Solo PNG, nosniff y caché privada.
- Las subidas de imágenes de tienda aceptan PNG, JPEG o WebP limitados en la consola autenticada; nunca SVG, HTML ni URL remotas de imágenes.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invoice_id | path UUID | UUID público de factura. |
| kind | path enum | logo_light, logo_dark o favicon. |
| revision | path UUID | Revisión actual de imagen. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-logo.png").write_bytes(response.read())Ejemplo de respuesta · 200 image/png
(binary PNG response)GETImagen de vista previa de la tienda/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngPúblico
Devuelve una imagen normalizada de vista previa solo para proyecto, tienda, tipo y revisión actual coincidentes.
- Usa appearance.images de los datos de vista previa. IDs desconocidos o no coincidentes devuelven 404. No se expone información de wallets ni pagos.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | UUID de proyecto. |
| store_id | path UUID | Tienda perteneciente al proyecto. |
| kind | path enum | logo_light, logo_dark o favicon. |
| revision | path UUID | Revisión actual de imagen. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-preview-logo.png").write_bytes(response.read())Ejemplo de respuesta · 200 image/png
(binary PNG response)GETLogo de vista previa con versión/checkout-api/previews/{project_id}/logo/{revision}/image.pngPúblico
Devuelve el logo normalizado del proyecto solo si coinciden proyecto y revisión de logo segura para caché. Usa project.logo_url de la vista previa en lugar de construir esta URL.
- Proyectos desconocidos y revisiones antiguas de logo devuelven invoice_not_found sin revelar qué componente faltaba.
- La imagen con revisión correcta es inmutable y puede almacenarse en caché.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| project_id | path UUID | UUID de proyecto. |
| revision | path UUID | Revisión actual del logo de pago devuelta en project.logo_url. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview-logo.png").write_bytes(response.read())Ejemplo de respuesta · 200 image/png
(binary PNG response)GETImagen QR de pago/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgPúblico
Genera un QR SVG de 512×512 con los datos de pago exactos adaptados a la red para un método de factura.
- No se necesita token bearer.
- Usa qr_url con revisión de secuencia y resto devuelta por el JSON de pago; el SVG es privado y no-store.
- Tras un pago parcial solicita el resto exacto y sigue fijado a ese activo.
- Devuelve 409 tras vencimiento, finalización o si otro método está activo; devuelve payment_qr_unavailable (422) si la solicitud es demasiado grande para codificarla.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invoice_id | path UUID | UUID público de factura. |
| intent_id | path UUID | ID de método de pago del JSON de pago. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg" \
--output 'payment-qr.svg'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("payment-qr.svg", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("payment-qr.svg", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("payment-qr.svg").write_bytes(response.read())Ejemplo de respuesta · 200 image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">…</svg>GETLogo de pago con versión/checkout-api/invoices/{invoice_id}/logo/{revision}/image.pngPúblico
Devuelve el logo normalizado de pago del proyecto solo si coinciden factura y revisión actual del logo. Prefiere project.logo_url del JSON de pago en lugar de construir esta ruta.
- No se necesita token bearer.
- La duración de caché pública es un año con immutable porque la revisión identifica el estado por contenido.
- Las revisiones desconocidas/no coincidentes devuelven invoice_not_found.
| Parámetro | Tipo / ubicación | Regla |
|---|---|---|
| invoice_id | path UUID | UUID público de factura. |
| revision | path UUID | Revisión actual del logo de pago incluida en project.logo_url. |
Solicitud
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-logo.png").write_bytes(response.read())Ejemplo de respuesta · 200 image/png
(binary PNG response)Referencia para Wholly Crypto 7.5.5. Para tu versión instalada, abre Ajustes → Acceso API → Documentación en tu consola. Ver versiones.