Integra envíos de dinero entre usuarios en minutos. API REST con autenticación por API key, webhooks y rate limiting listo para producción.
Tres pasos para enviar tu primer pago.
# POST /api/v1/auth/login
curl -X POST https://api.payg0.io/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"tu@email.com","password":"tu-password"}'
# Activa modo dev primero
curl -X PATCH https://api.payg0.io/api/v1/users/developer-mode \
-H 'Authorization: Bearer {token}' \
-H 'Content-Type: application/json' \
-d '{"enabled": true}'
# Genera tu API key
curl -X POST https://api.payg0.io/api/v1/keys \
-H 'Authorization: Bearer {token}' \
-H 'Content-Type: application/json' \
-d '{"name": "Mi app"}'
# Enviar $100 MXN a un usuario
curl -X POST https://api.payg0.io/api/v1/payments/send \
-H 'X-API-Key: pyg0_test_xxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"recipient": "@carlos", "amount": 100.00}'
# Response
{
"id": "uuid...",
"status": "COMPLETED",
"reason": "transferred",
"amount": "100.00"
}
Dos métodos según el contexto.
X-API-KeyIdeal para integraciones server-to-server y agentes de IA. Genera tus keys en POST /api/v1/keys.
X-API-Key: pyg0_test_a1b2c3d4e5f6g7h8
Para sesiones de usuario. Token de 24 horas obtenido en POST /api/v1/auth/login.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Sandbox vs Producción:
Las API keys de sandbox tienen prefijo pyg0_test_. En producción usarán pyg0_live_.
En sandbox el dinero es simulado y no hay movimientos bancarios reales.
Todos los endpoints disponibles en la API.
| Método | Ruta | Descripción | Rate limit |
|---|---|---|---|
| POST | /api/v1/auth/register | Crear cuenta | 10/min |
| POST | /api/v1/auth/login | Obtener JWT | 10/min |
| GET | /api/v1/auth/me | Perfil del usuario autenticado | 30/min |
| Método | Ruta | Descripción | Rate limit |
|---|---|---|---|
| GET | /api/v1/wallet/balance | Saldo disponible, retenido y total | 30/min |
| Método | Ruta | Descripción | Rate limit |
|---|---|---|---|
| POST | /api/v1/payments/validate | Validar pago sin ejecutarlo (pre-confirmación, límites de tier) | 5/min |
| POST | /api/v1/payments/send | Enviar pago a nickname o email | 5/min |
| GET | /api/v1/payments/history | Historial paginado con filtros | 30/min |
| GET | /api/v1/payments/{id} | Detalle de transacción | 30/min |
| POST | /api/v1/payments/{id}/cancel | Cancelar pago PENDING (solo emisor) | 30/min |
| Método | Ruta | Descripción | Rate limit |
|---|---|---|---|
| GET | /api/v1/users/lookup?q= | Buscar usuario por nickname o email. Devuelve nickname y display_name enmascarado (ej. Carlos L.) — nunca el nombre completo | 30/min |
| GET | /api/v1/users/{id}/tier | Tier activo, límites y % de uso del mes (solo propio) | 30/min |
| PATCH | /api/v1/users/developer-mode | Activar/desactivar modo desarrollador | 30/min |
| POST | /api/v1/users/me/pin | Crear PIN de confirmación de pagos | 10/min |
| PATCH | /api/v1/users/me/pin | Cambiar PIN | 10/min |
| DELETE | /api/v1/users/me/pin | Eliminar PIN | 10/min |
| GET | /api/v1/users/me/pin/status | Estado del PIN (activo o no) | 30/min |
| PATCH | /api/v1/users/me/api-keys/{key_id}/allow-withdrawals | Habilitar/deshabilitar retiros para una API key | 30/min |
| Método | Ruta | Descripción | Rate limit |
|---|---|---|---|
| POST | /api/v1/keys | Crear API key (full key solo en este response) | 30/min |
| GET | /api/v1/keys | Listar API keys activas | 30/min |
| DELETE | /api/v1/keys/{id} | Revocar API key | 30/min |
X-API-Key o Bearer)| Método | Ruta | Descripción | Rate limit |
|---|---|---|---|
| POST | /api/v1/webhooks | Registrar webhook — devuelve secret una única vez, guárdalo | 10/min |
| GET | /api/v1/webhooks | Listar webhooks activos (sin secret) | 30/min |
| PATCH | /api/v1/webhooks/{id} | Actualizar URL y/o eventos sin borrar el webhook (mantiene el secret) | 10/min |
| POST | /api/v1/webhooks/{id}/rotate-secret | Genera nuevo secret — invalida el anterior de inmediato, devuelto una única vez | 10/min |
| DELETE | /api/v1/webhooks/{id} | Eliminar webhook | 30/min |
| Método | Ruta | Descripción | Rate limit |
|---|---|---|---|
| GET | /api/v1/usage | Consumo de API: total de calls, breakdown por key y por endpoint | 20/min |
Recibe notificaciones en tiempo real cuando ocurre un evento de pago.
Comportamiento del secret
POST /webhooks devuelve el secret una sola vez al registrar — guárdalo de inmediatoGET /webhooks no devuelve el secret (solo metadata)POST /webhooks/{id}/rotate-secret genera un nuevo secret, lo devuelve una sola vez e invalida el anterior de inmediatoAcepta X-API-Key o Bearer — los agentes de IA pueden registrar y gestionar sus webhooks directamente con su API key, sin necesidad de login.
curl -X POST https://api.payg0.io/api/v1/webhooks \
-H 'X-API-Key: pyg0_test_xxxx' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://tu-app.com/webhooks/payg0",
"events": ["payment.received", "payment.completed"]
}'
# Response — guarda el secret ahora, no se vuelve a mostrar
{
"id": "uuid...",
"url": "https://tu-app.com/webhooks/payg0",
"events": ["payment.received", "payment.completed"],
"secret": "a3f8c2d1e9b4...",
"is_active": true
}
Cada request incluye el header X-Payg0-Signature: sha256={hmac}. Verifica con el secret recibido al registrar.
import hmac, hashlib
def verify_signature(payload: bytes, secret: str, signature: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# En tu handler:
body = await request.body()
sig = request.headers["X-Payg0-Signature"]
if not verify_signature(body, WEBHOOK_SECRET, sig):
raise HTTPException(401)
Puedes cambiar la URL o los eventos suscritos en cualquier momento. El secret y el ID permanecen igual.
# Suscribirse solo a pagos recibidos (quitar payment.completed)
curl -X PATCH https://api.payg0.io/api/v1/webhooks/{id} \
-H 'X-API-Key: pyg0_test_xxxx' \
-H 'Content-Type: application/json' \
-d '{"events": ["payment.received"]}'
# También puedes actualizar solo la URL
curl -X PATCH https://api.payg0.io/api/v1/webhooks/{id} \
-H 'X-API-Key: pyg0_test_xxxx' \
-H 'Content-Type: application/json' \
-d '{"url": "https://nueva-url.com/webhook"}'
No es necesario borrar y volver a registrar el webhook. Usa este endpoint — el secret anterior queda inválido de inmediato.
curl -X POST https://api.payg0.io/api/v1/webhooks/{id}/rotate-secret \
-H 'X-API-Key: pyg0_test_xxxx'
# Response — nuevo secret, devuelto una única vez
{
"id": "uuid...",
"secret": "b7e1a4f2c9d8..."
}
Cada entrega es un POST con Content-Type: application/json y este cuerpo:
{
"event": "payment.received",
"data": {
"id": "bc1b2680-d870-41fd-8063-d79bd749d724",
"sender_id": "808e5be1-12d7-4a58-ad56-f3d6a2a8fd38",
"sender_nickname": "nando",
"receiver_id": "ff793bad-7d97-4e5e-baba-6ecc70c12260",
"receiver_nickname": "diegotco",
"receiver_email": null,
"amount": "2.97",
"currency": "MXN",
"status": "COMPLETED",
"reason": "transferred",
"description": "Es otra prueba",
"created_at": "2026-05-26T17:35:00+00:00",
"updated_at": "2026-05-26T17:35:00+00:00"
}
}
| Evento | Cuándo se dispara | En la cuenta de |
|---|---|---|
payment.completed | Pago completado exitosamente | Emisor |
payment.received | Llegó un pago completado | Receptor |
payment.pending | Pago en espera de que el receptor se registre | Emisor |
payment.cancelled | Pago cancelado por el emisor | Emisor |
payment.failed | Pago fallido | Emisor |
payment.expired | Pago pendiente expirado sin ser reclamado | Emisor |
Enviar un pago en distintos lenguajes.
import httpx
client = httpx.Client(
base_url="https://api.payg0.io",
headers={"X-API-Key": "pyg0_test_xxxxxxxxxxxxx"},
)
# Consultar saldo
balance = client.get("/api/v1/wallet/balance").json()
print(f"Disponible: ${balance['available_balance']} MXN")
# Enviar pago
response = client.post("/api/v1/payments/send", json={
"recipient": "@carlos",
"amount": 150.00,
"description": "Pago por servicio",
})
tx = response.json()
print(f"Transacción {tx['id']}: {tx['status']}")
const BASE = "https://api.payg0.io";
const HEADERS = {
"X-API-Key": "pyg0_test_xxxxxxxxxxxxx",
"Content-Type": "application/json",
};
// Consultar saldo
const balance = await fetch(`${BASE}/api/v1/wallet/balance`, { headers: HEADERS })
.then(r => r.json());
console.log(`Disponible: $${balance.available_balance} MXN`);
// Enviar pago
const tx = await fetch(`${BASE}/api/v1/payments/send`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ recipient: "@carlos", amount: 150 }),
}).then(r => r.json());
console.log(`Transacción ${tx.id}: ${tx.status}`);
# Saldo
curl https://api.payg0.io/api/v1/wallet/balance \
-H "X-API-Key: pyg0_test_xxxxxxxxxxxxx"
# Enviar pago
curl -X POST https://api.payg0.io/api/v1/payments/send \
-H "X-API-Key: pyg0_test_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"recipient":"@carlos","amount":150,"description":"Pago"}'
# Historial
curl "https://api.payg0.io/api/v1/payments/history?status=COMPLETED&limit=10" \
-H "X-API-Key: pyg0_test_xxxxxxxxxxxxx"
Los límites se aplican por API key, sesión JWT o IP.
| Operación | Límite |
|---|---|
POST /api/v1/payments/send | 5 por minuto |
| GET endpoints en general | 30 por minuto |
POST /api/v1/webhooks | 10 por minuto |
GET /api/v1/usage | 20 por minuto |
| Auth (login / register) | 10 por minuto |
Al superar el límite recibirás 429 Too Many Requests. El header Retry-After indica cuándo reintentar.
Todos los errores retornan JSON con el campo detail.
| Código | Significado |
|---|---|
| 400 | Bad Request — parámetros inválidos |
| 401 | Unauthorized — token o API key inválida o expirada |
| 403 | Forbidden — sin permisos (ej. modo dev no activo) |
| 404 | Not Found — recurso no encontrado |
| 409 | Conflict — email o nickname ya registrado |
| 422 | Unprocessable — fondos insuficientes, datos inválidos o límite de tier excedido (LIMIT_SINGLE_TX, LIMIT_BALANCE, LIMIT_MONTHLY) |
| 429 | Too Many Requests — rate limit excedido |
| 500 | Internal Server Error — error interno inesperado |