# piv.day API Reference Full reference for the piv.day REST API: every endpoint, request body, response shape, error code and webhook. Exported from https://piv.day/api/docs for use with AI assistants. --- ## Getting started REST API en el plan Business. Tokens Bearer, JSON, endpoints idempotentes, códigos de error predecibles. Cada endpoint incluye un ejemplo de solicitud y respuesta. ### Base URL ``` https://app.piv.day/api/v1 ``` Todos los endpoints se encuentran bajo este prefijo. Un solo entorno — no hay hosts de staging separados. ### Autenticación ``` Authorization: Bearer YOUR_API_KEY ``` Las claves se crean en el dashboard en `Configuración → API Keys`. Cada clave tiene permisos granulares (leer números, enviar SMS, comprar proxies, etc.). Un token comprometido se rota con un clic — sin necesidad de volver a registrar al equipo. ### Cuenta **200 OK** ``` curl https://app.piv.day/api/v1/account \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Un endpoint utilitario único — balance y email de la cuenta actual. Útil para confirmar que la clave funciona y la autenticación está configurada correctamente.** ``` { "success": true, "data": { "user_id": "usr-uuid-...", "balance": 150.50, "email": "u***@example.com", "team_id": null, "is_team_member": false } } ``` GET /api/v1/account ### Formato de respuesta ``` { "success": true, "data": { ... } } ``` Todas las respuestas son JSON. En caso de error, `success` es `false` y el cuerpo contiene `error.code` y `error.message`. ### Rate limits **100 solicitudes por minuto por API key. El límite es compartido entre todos los endpoints. ¿Necesita más margen para un pico de tráfico? Contacte al soporte de Telegram y aumentaremos el límite. Al superar el límite recibirá `429 RATE_LIMITED` y el encabezado `Retry-After` — cuántos segundos esperar antes de la próxima solicitud.** ``` HTTP/1.1 429 Too Many Requests Retry-After: 12 { "success": false, "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded, retry in 12s" } } ``` 429 Too Many Requests ## Números Compra, renueva y restaura números. Recibe y envía SMS. Inicia verificaciones Google QR. Suscríbete a eventos mediante webhooks. ### Países y precios `GET /api/v1/numbers/countries` Devuelve los países disponibles para compra con el precio actual y capacidades SMS. Todos los precios incluyen el descuento de tu suscripción. **Response fields** | Field | Type | Description | | --- | --- | --- | | `country_code` | string | Código de país ISO de dos letras (p. ej. `SE`). | | `price_per_month` | number | Precio final para tu plan — lo que se cobra realmente al comprar. Los descuentos de Premium / Business ya están aplicados. | | `base_price` | number | Precio sin descuentos (plan Free). Se devuelve para comparación, para ver cuánto ahorra la suscripción. | | `can_send_sms` | boolean | Si el envío de SMS salientes es compatible desde este número. | | `can_receive_sms` | boolean | Si los SMS entrantes son compatibles. | | `sms_send_price` | number\|null | Precio por SMS saliente, también con el descuento del plan aplicado. `null` cuando el envío no es compatible desde este país. | **curl** ```bash curl https://app.piv.day/api/v1/numbers/countries \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": [ { "country_code": "SE", "price_per_month": 4.00, "base_price": 5.00, "can_send_sms": true, "can_receive_sms": true, "sms_send_price": 0.25 }, { "country_code": "GB", "price_per_month": 3.00, "base_price": 3.00, "can_send_sms": true, "can_receive_sms": true, "sms_send_price": 0.20 } ] } ``` ### Listar números `GET /api/v1/numbers` Devuelve una página de números de cuenta con filtros por país, estado y búsqueda por número o nombre personalizado. **Query parameters** | Field | Type | Description | | --- | --- | --- | | `limit` | integer | Tamaño de página (predeterminado 50, máximo 200). | | `offset` | integer | Desplazamiento de paginación. | | `country` | string | Filtro de código de país ISO. | | `status` | string | Uno de `active`, `expired`, `pending_restore`. | | `search` | string | Búsqueda de subcadena por número de teléfono o nombre personalizado. | **curl** ```bash curl "https://app.piv.day/api/v1/numbers?country=SE&status=active&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "numbers": [ { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "phone_number": "+46764794425", "country_code": "SE", "status": "active", "created_at": "2026-05-01T10:00:00Z", "expires_at": "2026-05-31T10:00:00Z", "auto_renew": false, "custom_name": "Office line", "purchased_at": "2026-05-01T10:00:00Z", "next_renewal_date": "2026-05-31T10:00:00Z", "tags": ["support"], "can_send_sms": true, "can_receive_sms": true } ], "pagination": { "total": 1, "limit": 10, "offset": 0 } } } ``` ### Obtener un número por ID `GET /api/v1/numbers/{piv_num_id}` Registro completo del número: estado, fechas, renovación automática, etiquetas, direcciones SMS permitidas. **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `NUMBER_NOT_FOUND` | 404 | El número no existe o no pertenece a la cuenta. | **curl** ```bash curl https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "phone_number": "+46764794425", "country_code": "SE", "status": "active", "created_at": "2026-05-01T10:00:00Z", "expires_at": "2026-05-31T10:00:00Z", "auto_renew": false, "custom_name": "Office line", "purchased_at": "2026-05-01T10:00:00Z", "next_renewal_date": "2026-05-31T10:00:00Z", "tags": ["support"], "can_send_sms": true, "can_receive_sms": true } } ``` ### Comprar un número `POST /api/v1/numbers/purchase` Compra un número en el país seleccionado por el período indicado. El costo se debita del saldo de la cuenta. Devuelve `piv_num_id` que se utiliza en todas las operaciones posteriores del número. **Request body** | Field | Type | Description | | --- | --- | --- | | `country_code` * | string | Código de país ISO. | | `duration_months` * | integer | Período de alquiler en meses (mínimo 1). | | `auto_renew` * | boolean | Activar la renovación automática justo después de la compra. | | `custom_name` | string | Nombre opcional legible para el número. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `COUNTRY_NOT_AVAILABLE` | 400 | El país no está disponible o no tiene precios. | | `NO_NUMBERS_AVAILABLE` | 400 | No hay números disponibles en el país solicitado en este momento. | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes en el saldo. | | `VALIDATION_ERROR` | 400 | Campos inválidos en el cuerpo de la solicitud. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/numbers/purchase \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "country_code": "SE", "duration_months": 1, "auto_renew": false, "custom_name": "My Sweden Number" }' ``` **200 OK** ```json { "success": true, "cost": 5.00, "numbers": [ { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "country_code": "SE", "phone_number": "+46764794425", "created_at": "2026-05-01T10:00:00Z", "expires_at": "2026-05-31T10:00:00Z", "auto_renew": false, "custom_name": "My Sweden Number" } ] } ``` ### Renovar un número `POST /api/v1/numbers/{piv_num_id}/renew` Extiende un número activo por el período indicado. El costo se debita en el momento de la llamada. **Request body** | Field | Type | Description | | --- | --- | --- | | `duration_months` * | integer | Número de meses a agregar. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `NUMBER_NOT_FOUND` | 404 | El número no existe o no pertenece a la cuenta. | | `COUNTRY_NOT_AVAILABLE` | 400 | No se encontraron precios para el país del número. | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes para renovar. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/renew \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "duration_months": 1 }' ``` **200 OK** ```json { "success": true, "data": { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "phone_number": "+46764794425", "old_expires_at": "2026-05-31T10:00:00Z", "new_expires_at": "2026-06-30T10:00:00Z", "cost": 5.00 } } ``` ### Actualizar un número `PATCH /api/v1/numbers/{piv_num_id}` Actualmente solo se puede actualizar el nombre personalizado. **Request body** | Field | Type | Description | | --- | --- | --- | | `custom_name` * | string | Nuevo nombre para el número. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `NUMBER_NOT_FOUND` | 404 | El número no existe o no pertenece a la cuenta. | | `VALIDATION_ERROR` | 400 | Valor inválido en el cuerpo de la solicitud. | **curl** ```bash curl -X PATCH https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "custom_name": "Support line" }' ``` **200 OK** ```json { "success": true, "data": { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "phone_number": "+46764794425", "country_code": "SE", "status": "active", "custom_name": "Support line", "auto_renew": false, "expires_at": "2026-05-31T10:00:00Z", "tags": [] } } ``` ### Gestión de renovación automática `PATCH /api/v1/numbers/{piv_num_id}/auto-renewal` Activa o desactiva la renovación automática del número. Cuando está habilitada, la renovación se debita automáticamente 24 horas antes del vencimiento. Si el saldo es insuficiente, el número expira; puedes reclamarlo mediante `POST /numbers/restore` dentro de 7 días. **Request body** | Field | Type | Description | | --- | --- | --- | | `auto_renew` * | boolean | Nuevo valor del indicador de renovación automática. | **curl** ```bash curl -X PATCH https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/auto-renewal \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "auto_renew": true }' ``` **200 OK** ```json { "success": true, "data": { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "phone_number": "+46764794425", "auto_renew": true } } ``` ### Restaurar números vencidos `POST /api/v1/numbers/restore` Recupera números vencidos dentro de un período de 7 días. Pasa un array de `piv_num_id` — los números regresan con el historial SMS y la configuración conservados. La restauración cuesta más que comprar un número nuevo (incluye un multiplicador de restauración) — las cifras exactas se muestran en el dashboard antes de confirmar. El estado final de cada número llega mediante el webhook `number.restore_completed`; los reembolsos por números fallidos se acreditan automáticamente. **Request body** | Field | Type | Description | | --- | --- | --- | | `piv_num_ids` * | string[] | Array de IDs de números a restaurar. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `NO_RESTORABLE_NUMBERS` | 404 | Ninguno de los números proporcionados puede restaurarse (el período de 7 días venció o no son tuyos). | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes para cubrir la restauración. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/numbers/restore \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "piv_num_ids": [ "vzPA1-kHKSg-EAL7e-Jqd3o", "abc12-defgh-ijklm-nopqr" ] }' ``` **200 OK** ```json { "success": true, "queued": 2, "skipped": 0, "total_charged": 9.00, "numbers": [ { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "phone_number": "+46764794425", "status": "pending" } ] } ``` ### Historial SMS del número `GET /api/v1/numbers/{piv_num_id}/sms` Devuelve los mensajes entrantes y salientes del número en orden cronológico inverso. **Query parameters** | Field | Type | Description | | --- | --- | --- | | `limit` | integer | Cuántos mensajes devolver (predeterminado 50, máximo 200). | | `offset` | integer | Desplazamiento de paginación. | **curl** ```bash curl "https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/sms?limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "messages": [ { "id": "42", "from_number": "+46764794425", "to_number": "+14155551234", "message_body": "Hello from piv.day", "direction": "outbound", "status": "delivered", "received_at": "2026-05-22T11:30:00Z" }, { "id": "41", "from_number": "+14155551234", "to_number": "+46764794425", "message_body": "Hey, got your message!", "direction": "inbound", "status": "received", "received_at": "2026-05-22T11:35:00Z" } ], "pagination": { "total": 2, "limit": 20, "offset": 0 } } } ``` ### Enviar SMS `POST /api/v1/numbers/{piv_num_id}/sms/send` Disponible para países donde el envío saliente está permitido (p. ej. CA, GB, SE). El costo del mensaje se debita en el momento del envío. **Request body** | Field | Type | Description | | --- | --- | --- | | `to_number` * | string | Número del destinatario en formato internacional (E.164). | | `message_body` * | string | Texto del mensaje. Se admiten GSM-7 y UCS-2. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `NUMBER_NOT_FOUND` | 404 | El número no existe o no pertenece a la cuenta. | | `NUMBER_EXPIRED` | 403 | El número ya ha vencido. | | `NUMBER_NOT_ACTIVE` | 400 | El número está en un estado que no permite el envío. | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes para enviar. | | `INVALID_MESSAGE_FORMAT` | 400 | El cuerpo del mensaje supera la longitud permitida o contiene caracteres prohibidos. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/sms/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to_number": "+14155551234", "message_body": "Hello from piv.day" }' ``` **200 OK** ```json { "success": true, "data": { "message_id": "42", "from_number": "+46764794425", "to_number": "+14155551234", "message_body": "Hello from piv.day", "status": "queued", "created_at": "2026-05-22T11:30:00Z" } } ``` ### Iniciar verificación Google QR `POST /api/v1/numbers/{piv_num_id}/verify` Inicia una verificación Google QR usando la URL proporcionada. El resultado — éxito o fallo — llega mediante el webhook verify.completed o verify.failed. El costo se debita cuando la tarea se pone en cola. **Request body** | Field | Type | Description | | --- | --- | --- | | `gv_url` * | string | URL completa de verificación de Google (obtenida de la página de Google). | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `NUMBER_NOT_FOUND` | 404 | El número no existe o no es tuyo. | | `NUMBER_NOT_ACTIVE` | 400 | El número no está activo. | | `SMS_DISABLED` | 400 | El envío de SMS está desactivado para este número. | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes para la verificación. | | `PROXY_DEAD` | 502 | Proxy no disponible — saldo reembolsado. | | `QUEUE_FULL` | 503 | La cola está llena, inténtelo más tarde — saldo reembolsado. | | `SERVER_UNAVAILABLE` | 503 | Servidor de automatización no disponible — saldo reembolsado. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/verify \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "gv_url": "https://gv.google.com/..." }' ``` **202 Accepted** ```json { "success": true, "message": "Verification initiated" } ``` ## Proxy IPv6 residencial en docenas de países. Compra masiva, renovación, Restore que mantiene el mismo login y contraseña, exportación en el formato que necesitas. ### Lista de servidores `GET /api/v1/proxy/servers` Servidores proxy disponibles con precios. **curl** ```bash curl https://app.piv.day/api/v1/proxy/servers \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "servers": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "EU-DE-01", "country": "DE", "socks5_port": 1080, "http_port": 3128, "price_per_proxy": 0.50 }, { "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "name": "EU-NL-01", "country": "NL", "socks5_port": 1080, "http_port": 3128, "price_per_proxy": 0.45 } ] } } ``` ### Lista de pedidos `GET /api/v1/proxy/orders` Lista de tus pedidos de proxy con filtros por estado o búsqueda. **Query parameters** | Field | Type | Description | | --- | --- | --- | | `limit` | integer | Tamaño de página. Por defecto 100, máximo 500. | | `offset` | integer | Desplazamiento de paginación. | | `status` | string | Filtro por estado: `active`, `expired`. | | `search` | string | Búsqueda en pedidos. | **curl** ```bash curl "https://app.piv.day/api/v1/proxy/orders?status=active&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "orders": [ { "id": "ord-uuid-...", "country": "DE", "quantity": 10, "price_per_proxy": 0.50, "price_total": 5.00, "status": "active", "purchased_at": "2026-01-01T00:00:00Z", "expires_at": "2026-02-01T00:00:00Z" } ], "pagination": { "total": 1, "limit": 20, "offset": 0 } } } ``` ### Obtener un pedido `GET /api/v1/proxy/orders/{id}` Obtener los detalles de un pedido específico, incluida la lista de credenciales de proxy. **curl** ```bash curl https://app.piv.day/api/v1/proxy/orders/ord-uuid-... \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "order": { "id": "ord-uuid-...", "country": "DE", "quantity": 10, "price_per_proxy": 0.50, "price_total": 5.00, "status": "active", "purchased_at": "2026-01-01T00:00:00Z", "expires_at": "2026-02-01T00:00:00Z", "created_at": "2026-01-01T00:00:00Z", "items": [ { "login": "user1", "password": "pass1", "host": "185.1.2.3", "socks5_port": 1080, "http_port": 3128, "ipv6": "2a00:1:2:3::1" } ] } } } ``` ### Comprar proxies `POST /api/v1/proxy/purchase` Comprar proxies en el servidor elegido. Una solicitud crea un pedido con N proxies (hasta 500). No existen pedidos masivos: llama al método nuevamente si necesitas más. **Request body** | Field | Type | Description | | --- | --- | --- | | `server_id` * | string | ID del servidor de `GET /proxy/servers`. | | `quantity` * | integer | Cuántos proxies comprar (1–500). | | `months` * | integer | Período de arrendamiento en meses (1–12). | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes. | | `NOT_FOUND` | 404 | Servidor proxy no encontrado o inactivo. | | `VALIDATION_ERROR` | 400 | Cuerpo de solicitud inválido. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/proxy/purchase \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "server_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "quantity": 5, "months": 1 }' ``` **200 OK** ```json { "success": true, "data": { "order": { "id": "ord-uuid-...", "country": "DE", "quantity": 5, "price_total": 2.50, "status": "active", "expires_at": "2026-02-01T00:00:00Z", "created_at": "2026-01-01T00:00:00Z", "items": [ { "login": "user1", "password": "pass1", "host": "185.1.2.3", "socks5_port": 1080, "http_port": 3128, "ipv6": "2a00:1:2:3::1" }, { "login": "user2", "password": "pass2", "host": "185.1.2.3", "socks5_port": 1080, "http_port": 3128, "ipv6": "2a00:1:2:3::2" } ] } } } ``` ### Renovar un pedido `POST /api/v1/proxy/orders/{id}/renew` Extender un pedido de proxy activo por un número de meses. **Request body** | Field | Type | Description | | --- | --- | --- | | `months` * | integer | Meses a añadir. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/proxy/orders/ord-uuid-.../renew \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "months": 1 }' ``` **200 OK** ```json { "success": true, "data": { "order_id": "ord-uuid-...", "cost": 5.00, "new_expires_at": "2026-03-01T00:00:00Z" } } ``` ### Restore de orden vencida `POST /api/v1/proxy/orders/{id}/restore` Recupera una orden de proxy vencida sin recomprarla. Login, contraseña, direcciones IPv6, país y vínculos — todo igual. El plazo se extiende 1 mes. No es necesario reconfigurar el antidetect. **curl** ```bash curl -X POST https://app.piv.day/api/v1/proxy/orders/ord-uuid-.../restore \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "order_id": "ord-uuid-...", "quantity": 10, "total_cost": 5.00, "expires_at": "2026-03-01T00:00:00Z" } } ``` ### Exportar proxies `GET /api/v1/proxy/orders/{id}/export` Exportar las credenciales de proxy del pedido como una lista de texto (login:password@host:port). **Query parameters** | Field | Type | Description | | --- | --- | --- | | `format` | string | Formato de exportación: `socks5`, `http` o `both` (por defecto `socks5`). | **curl** ```bash curl "https://app.piv.day/api/v1/proxy/orders/ord-uuid-.../export?format=socks5" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "content": "user1:pass1@1.2.3.4:1080\nuser2:pass2@1.2.3.4:1080", "filename": "piv-day-proxy-ord-uuid--socks5.txt", "format": "socks5", "count": 2 } } ``` ## Dominios Búsqueda y registro sin KYC, Cloudflare y SSL automáticos, gestión de registros DNS y servidores NS a través de la API. ### Verificación de disponibilidad `GET /api/v1/domains/search` Verificar si un dominio está disponible y cuánto cuesta registrarlo. **Query parameters** | Field | Type | Description | | --- | --- | --- | | `q` * | string | Nombre de dominio a buscar, p. ej. `mysite.com`. | **curl** ```bash curl "https://app.piv.day/api/v1/domains/search?q=mysite.com" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "domain": "mysite.com", "available": true, "create_price": 12.50, "renew_price": 12.50, "currency": "USD" } } ``` ### Listar dominios `GET /api/v1/domains` Lista de sus dominios registrados con posibilidad de filtrado. **Query parameters** | Field | Type | Description | | --- | --- | --- | | `limit` | integer | Tamaño de página. Por defecto 100, máximo 500. | | `offset` | integer | Desplazamiento para paginación. | | `status` | string | Estado del dominio: `active`, `pending`, `expired`. | | `cloudflare` | string | Filtrar por estado de Cloudflare: `true` o `false`. | | `auto_renew` | string | Filtrar por renovación automática: `true` o `false`. | | `search` | string | Buscar por nombre de dominio. | **curl** ```bash curl "https://app.piv.day/api/v1/domains?status=active&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "domains": [ { "id": "dom-uuid-...", "domain_name": "mysite.com", "status": "active", "registered_at": "2026-01-01T00:00:00Z", "expires_at": "2027-01-01T00:00:00Z", "auto_renew": true, "cloudflare_enabled": true, "cloudflare_status": "active", "ssl_type": "lets_encrypt", "ssl_status": "active", "ssl_mode": "full", "ssl_expires_at": "2026-04-01T00:00:00Z", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" } ], "pagination": { "total": 1, "limit": 20, "offset": 0 } } } ``` ### Registrar un dominio `POST /api/v1/domains` Registrar un nuevo dominio. La solicitud es asíncrona — devuelve `queue_id` para seguimiento. Cloudflare ACTIVADO: no pasar `nameservers`; opcionalmente puede pasar `dns_records` y `ssl_mode`. Cloudflare DESACTIVADO: no pasar `dns_records` ni `ssl_mode`; `nameservers` es obligatorio (mínimo 2). Nunca exponemos los nameservers de Cloudflare. **Request body** | Field | Type | Description | | --- | --- | --- | | `domain` * | string | Nombre de dominio a registrar, p. ej. `mysite.com`. | | `period` * | integer | Período de registro en años (1–10). | | `cloudflare` * | boolean | Conectar el dominio a Cloudflare automáticamente. | | `auto_renew` * | boolean | Habilitar renovación automática. | | `ssl_mode` | string | `flexible` \| `full` \| `strict`. Solo con `cloudflare: true`. | | `nameservers` | string[] | Obligatorio con `cloudflare: false` (≥2 NS). Prohibido con `cloudflare: true`. | | `dns_records` | object[] | Registros DNS iniciales. Solo con `cloudflare: true`. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `DOMAIN_NOT_AVAILABLE` | 400 | El dominio no está disponible para registro. | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes. | | `VALIDATION_ERROR` | 400 | Cuerpo de la solicitud no válido. | **Cloudflare ACTIVADO** ```bash curl -X POST https://app.piv.day/api/v1/domains \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "domain": "mysite.com", "period": 1, "cloudflare": true, "auto_renew": true, "ssl_mode": "full", "dns_records": [ { "type": "A", "name": "@", "content": "1.2.3.4", "proxied": true } ] }' ``` **Cloudflare DESACTIVADO — NS propios** ```bash curl -X POST https://app.piv.day/api/v1/domains \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "domain": "mysite.com", "period": 1, "cloudflare": false, "auto_renew": false, "nameservers": ["ns1.myhost.com", "ns2.myhost.com"] }' ``` **202 Accepted** ```json { "success": true, "data": { "queue_id": "que-uuid-...", "domain": { "id": "dom-uuid-...", "domain_name": "mysite.com", "status": "purchasing", "period": 1, "cloudflare_enabled": true, "auto_renew": true, "ssl_mode": "full", "created_at": "2026-01-01T00:00:00Z" } } } ``` ### Estado de la tarea de registro `GET /api/v1/domains/queue/{id}` Verificar el estado de una entrada en la cola de registro o renovación de dominio. Con el estado `completed`, la respuesta ya contiene la ficha completa del dominio — no es necesaria una llamada separada a `/domains/{id}`. **curl** ```bash curl https://app.piv.day/api/v1/domains/queue/que-uuid-... \ -H "Authorization: Bearer YOUR_API_KEY" ``` **pending / processing** ```json { "success": true, "data": { "queue_id": "que-uuid-...", "domain": "mysite.com", "status": "pending" } } ``` **completed — ficha completa** ```json { "success": true, "data": { "queue_id": "que-uuid-...", "status": "completed", "domain": { "id": "dom-uuid-...", "domain_name": "mysite.com", "status": "active", "registered_at": "2026-01-01T12:00:00Z", "expires_at": "2027-01-01T12:00:00Z", "auto_renew": true, "cloudflare_enabled": true, "cloudflare_status": "active", "ssl_type": "lets_encrypt", "ssl_status": "active", "ssl_mode": "full", "ssl_expires_at": "2026-04-01T00:00:00Z", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T12:00:00Z", "dns_records": [ { "name": "@", "type": "A", "content": "1.2.3.4", "ttl": 1, "priority": null, "proxied": true } ] } } } ``` ### Obtener un dominio `GET /api/v1/domains/{id}` Obtener información completa sobre un dominio registrado. El campo `nameservers` solo está presente cuando `cloudflare_enabled: false`. **curl** ```bash curl https://app.piv.day/api/v1/domains/dom-uuid-... \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "domain": { "id": "dom-uuid-...", "domain_name": "mysite.com", "status": "active", "registered_at": "2026-01-01T12:00:00Z", "expires_at": "2027-01-01T12:00:00Z", "auto_renew": true, "cloudflare_enabled": true, "cloudflare_status": "active", "ssl_type": "lets_encrypt", "ssl_status": "active", "ssl_mode": "full", "ssl_expires_at": "2026-04-01T00:00:00Z", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T12:00:00Z", "dns_records": [ { "name": "@", "type": "A", "content": "1.2.3.4", "ttl": 1, "priority": null, "proxied": true }, { "name": "www", "type": "CNAME", "content": "mysite.com", "ttl": 1, "priority": null, "proxied": true } ] } } } ``` ### Renovar dominio `POST /api/v1/domains/{id}/renew` Renovar el registro del dominio. Devuelve `queue_id` para seguimiento. **Request body** | Field | Type | Description | | --- | --- | --- | | `period` * | integer | Años a agregar. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../renew \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": 1 }' ``` **202 Accepted** ```json { "success": true, "data": { "queue_id": "que-uuid-...", "domain_id": "dom-uuid-...", "expires_at": "2027-01-01T12:00:00Z" } } ``` ### Toggle de renovación automática `POST /api/v1/domains/{id}/auto-renew` Activar o desactivar la renovación automática del dominio. **Request body** | Field | Type | Description | | --- | --- | --- | | `auto_renew` * | boolean | Nuevo valor del indicador. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../auto-renew \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "auto_renew": true }' ``` **200 OK** ```json { "success": true, "data": { "id": "dom-uuid-...", "auto_renew": true } } ``` ### Registros DNS del dominio `GET /api/v1/domains/{id}/dns` Listar los registros DNS del dominio (a través de Cloudflare). **curl** ```bash curl https://app.piv.day/api/v1/domains/dom-uuid-.../dns \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "records": [ { "id": "rec-uuid-...", "type": "A", "name": "@", "content": "1.2.3.4", "ttl": 1, "priority": null, "proxied": true } ] } } ``` ### Agregar registro DNS `POST /api/v1/domains/{id}/dns` Crear un nuevo registro DNS. **Request body** | Field | Type | Description | | --- | --- | --- | | `type` * | string | Tipo de registro: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, etc. | | `name` * | string | Nombre del registro, p. ej. `@` o `subdomain`. | | `content` * | string | Valor del registro. | | `ttl` | integer | TTL en segundos (`1` = automático). | | `proxied` | boolean | Redirigir a través de Cloudflare. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../dns \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "A", "name": "@", "content": "1.2.3.4", "ttl": 1, "proxied": true }' ``` **201 Created** ```json { "success": true, "data": { "record": { "id": "rec-uuid-...", "type": "A", "name": "@", "content": "1.2.3.4", "ttl": 1, "priority": null, "proxied": true } } } ``` ### Actualizar registro DNS `PUT /api/v1/domains/{id}/dns/{recordId}` Actualizar un registro DNS existente por ID. Solo se sobreescriben los campos enviados. **curl** ```bash curl -X PUT https://app.piv.day/api/v1/domains/dom-uuid-.../dns/rec-uuid-... \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "5.6.7.8" }' ``` ### Eliminar registro DNS `DELETE /api/v1/domains/{id}/dns/{recordId}` Eliminar un registro DNS por ID. **curl** ```bash curl -X DELETE https://app.piv.day/api/v1/domains/dom-uuid-.../dns/rec-uuid-... \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Habilitar Cloudflare `POST /api/v1/domains/{id}/cloudflare` Activar Cloudflare para el dominio. No devolvemos los nameservers de Cloudflare — la delegación se configura de nuestra parte. No se admite la desactivación de Cloudflare mediante este endpoint. **curl** ```bash curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../cloudflare \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": true }' ``` **200 OK** ```json { "success": true, "data": { "domain_id": "dom-uuid-...", "ssl_mode": "flexible" } } ``` ### Configurar nameservers `POST /api/v1/domains/{id}/ns-servers` Configurar nameservers personalizados para el dominio. **curl** ```bash curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../ns-servers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "nameservers": ["ns1.example.com", "ns2.example.com"] }' ``` **200 OK** ```json { "success": true, "data": { "nameservers": [ { "nameserver": "ns1.example.com", "order_index": 1 }, { "nameserver": "ns2.example.com", "order_index": 2 } ] } } ``` ## Whites Generación de white-pages por nicho y tier. Cada sitio es único. Cambia dominio y contactos sin regenerar. ### Listar tiers `GET /api/v1/whites/tiers` Tiers de generación disponibles con precios. `price` es el precio por generación ya ajustado a tu cuenta (suscripción aplicada), un único número. `quality: "premium"` cuesta más (se calcula al crear). Blog solo en `v2_tier3`: los primeros 3 artículos están incluidos; cada artículo adicional (hasta 20) se cobra a `extra_article_price`. **curl** ```bash curl https://app.piv.day/api/v1/whites/tiers \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "tiers": [ { "tier_key": "v2_tier1", "name": "Landing", "generator_version": "v2", "min_articles": 0, "max_articles": 0, "price": 3.00, "extra_article_price": null }, { "tier_key": "v2_tier2", "name": "Multi-page", "generator_version": "v2", "min_articles": 0, "max_articles": 0, "price": 6.00, "extra_article_price": null }, { "tier_key": "v2_tier3", "name": "Full + Blog", "generator_version": "v2", "min_articles": 3, "max_articles": 20, "price": 10.00, "extra_article_price": 1.50 } ] } } ``` ### Listar whites `GET /api/v1/whites` Lista de tus trabajos de generación con filtrado. **Query parameters** | Field | Type | Description | | --- | --- | --- | | `limit` | integer | Tamaño de página. Por defecto 100, máximo 500. | | `offset` | integer | Desplazamiento de paginación. | | `status` | string | `queued` \| `processing` \| `done` \| `failed`. | | `tier_key` | string | `v2_tier1` \| `v2_tier2` \| `v2_tier3`. | **curl** ```bash curl "https://app.piv.day/api/v1/whites?status=done&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "whites": [ { "id": "job-uuid-...", "name": "My White", "status": 10, "tier_key": "v2_tier3", "quality": "standard", "niche": "IT Consulting", "country": "DE", "language": "de", "domain": "mysite.com", "result_url": null, "created_at": "2026-01-01T00:00:00Z" } ], "pagination": { "total": 1, "limit": 20, "offset": 0 } } } ``` ### Iniciar generación `POST /api/v1/whites` Encola un job de generación de white-page. El pago se cobra de inmediato. El estado final llega vía el webhook `white.completed` / `white.failed` o puede consultarse mediante `GET /whites/{id}`. **Request body** | Field | Type | Description | | --- | --- | --- | | `name` * | string | Nombre del trabajo (2–20 caracteres). | | `tier_key` * | string | `v2_tier1` \| `v2_tier2` \| `v2_tier3`. | | `niche` * | string | Nicho del sitio (hasta 25 caracteres). | | `country` * | string | Código de país (ISO 3166-1 alpha-2). | | `language` * | string | Idioma principal del sitio (ISO 639-1). | | `quality` | string | `standard` \| `premium` (por defecto `standard`). | | `languages` | string[] | Lista de idiomas. El primero está incluido en el precio; cada idioma adicional tiene un costo extra. | | `domain` | string | Dominio completo (p. ej. `example.com`). Sin autocompletado. | | `email` | string | Email completo (p. ej. `info@example.com`). Sin autocompletado. | | `phone` | string | Teléfono de contacto. | | `address` | string | Dirección de la empresa. | | `legal_name` | string | Nombre legal. | | `style_hint` | string | Estilo de diseño (Random, Minimal, Corporativo, etc.). | | `keywords` | string[] | Palabras clave SEO. | | `banned_words` | string[] | Palabras prohibidas en el contenido. | | `blog_count` | integer | Cantidad de artículos del blog 3–20. Solo para `v2_tier3`. Los artículos que superen 3 tienen costo adicional. | | `contacts_mode` | string | `same` \| `template`. | | `contacts_template` | string | Plantilla de la página de contacto (cuando `contacts_mode=template`). | | `facebook` | string | URL completa de la página de Facebook. | | `instagram` | string | URL completa del perfil de Instagram. | | `linkedin` | string | URL completa del perfil de LinkedIn. | | `youtube` | string | URL completa del canal de YouTube. | | `tiktok` | string | URL completa del perfil de TikTok. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes. | | `INVALID_TIER` | 400 | No se encontró ningún tier con esa clave. | | `VALIDATION_ERROR` | 400 | Cuerpo de solicitud inválido. | **curl** ```bash curl -X POST https://app.piv.day/api/v1/whites \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Tech Blog DE", "tier_key": "v2_tier3", "niche": "IT Consulting", "country": "DE", "language": "de", "quality": "premium", "languages": ["de", "en"], "domain": "techblog-de.com", "email": "info@techblog-de.com", "phone": "+49 30 123456", "address": "Berliner Str. 1, 10115 Berlin", "legal_name": "TechBlog GmbH", "style_hint": "Корпоративный", "keywords": ["IT", "consulting", "cloud"], "banned_words": ["cheap", "free"], "blog_count": 8, "contacts_mode": "same", "facebook": "https://facebook.com/techblogde", "instagram": "https://instagram.com/techblogde", "linkedin": "https://linkedin.com/company/techblogde" }' ``` **200 OK** ```json { "success": true, "data": { "status": "queued", "white": { "id": "job-uuid-...", "name": "Tech Blog DE", "status": 0, "tier_key": "v2_tier3", "quality": "premium", "niche": "IT Consulting", "country": "DE", "language": "de", "domain": "techblog-de.com", "result_url": null, "created_at": "2026-01-01T00:00:00Z" } } } ``` ### Obtener un white `GET /api/v1/whites/{id}` Obtener el estado actual de un trabajo de generación. **curl** ```bash curl https://app.piv.day/api/v1/whites/job-uuid-... \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "white": { "id": "job-uuid-...", "name": "Tech Blog DE", "status": 10, "tier_key": "v2_tier3", "quality": "premium", "niche": "IT Consulting", "country": "DE", "language": "de", "domain": "techblog-de.com", "result_url": null, "created_at": "2026-01-01T00:00:00Z" } } } ``` ### Descargar archivo `GET /api/v1/whites/{id}/download` Obtén un enlace firmado de corta duración al archivo terminado de la white-page. El enlace es válido ~5 minutos (ver `expires_at`). Si expiró, llama a `/download` de nuevo — emitiremos uno nuevo. Si la white-page aún no está generada, devuelve `409 NOT_READY`. **Query parameters** | Field | Type | Description | | --- | --- | --- | | `format` | string | Formato del archivo. Por defecto `php`. | **Errors** | Code | HTTP | When it fires | | --- | --- | --- | | `NOT_READY` | 409 | La white-page aún se está generando — reintenta después de `white.completed`. | **curl** ```bash curl "https://app.piv.day/api/v1/whites/job-uuid-.../download?format=php" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **200 OK** ```json { "success": true, "data": { "url": "https://strg.piv.day/download//?format=php&token=&filename=.zip", "filename": "example.com_2026-05-28_DE_en.zip", "format": "php", "expires_at": "2026-05-28T14:46:00Z" } } ``` ### Cambiar dominio y contactos `POST /api/v1/whites/{id}/config` Actualiza los datos de contacto de la empresa en la white-page sin regenerarla. **curl** ```bash curl -X POST https://app.piv.day/api/v1/whites/job-uuid-.../config \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "company": "My Brand", "domain": "newdomain.com", "email": "info@newdomain.com", "phone": "+49 30 123456", "address": "Berliner Str. 1, Berlin", "legal": "My Brand GmbH" }' ``` ## Webhooks Enviamos un POST simple con cuerpo JSON a tu URL. La estructura es la misma para todos los eventos: `event_type`, `timestamp` y un bloque `data`. Tu servidor debe responder con cualquier 2xx en 5 segundos. ``` POST Content-Type: application/json User-Agent: piv.day-webhook/1.0 { "event_type": "", "timestamp": "2026-05-22T12:34:56Z", "data": { ... } } ``` **Retries.** En caso de no-2xx o timeout — hasta 3 reintentos con espera de 1s / 2s / 4s. El historial completo de entregas está en la página de claves API en el dashboard. Si los tres fallan, el evento se marca como failed y puede reenviarse manualmente. **Security.** Usa un endpoint HTTPS. Cada solicitud llega con el encabezado `User-Agent: piv.day-webhook/1.0`. La URL del webhook se configura en el perfil de la clave API. ### All events | Event | When it fires | | --- | --- | | `sms.received` | Un remitente de confianza acaba de enviar un SMS a uno de tus números. | | `sms.status_updated` | Un SMS saliente cambió de estado — entregado, fallido, etc. | | `number.restore_completed` | Un Restore por lotes finalizó — el resumen final llega aquí. | | `verify.completed` | La verificación de la cuenta de Google finalizó correctamente. | | `verify.failed` | Algo salió mal — Google no lo aceptó. | | `proxy.expires_soon` | Quedan aproximadamente tres días en el pedido de proxy. | | `domain.registered` | El pedido de registro finalizó correctamente — el dominio está activo. | | `domain.failed` | El pedido no se completó — causa común: el nombre acaba de ser tomado. | | `domain.expires_soon` | Queda aproximadamente una semana antes de que venza el dominio. | | `white.completed` | La tarea de generación del White finalizó correctamente. | | `white.failed` | La tarea falló — los fondos se reembolsan al balance. | ### SMS entrante a tu número **`sms.received`** La mayoría de las veces son códigos de verificación de redes publicitarias. El payload incluye el texto sin procesar y un código detectado automáticamente (cuando está presente) para que puedas pasarlo directamente a tu flujo de trabajo sin parsear. Los mensajes de remitentes conocidos de spam no se reenvían. ```json { "event_type": "sms.received", "timestamp": "2026-05-22T12:34:56Z", "data": { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "text": "Your verification code is 123456", "code": "123456", "country": "SE", "received_at": "2026-05-22T12:34:56Z" } } ``` ### Estado del SMS enviado cambió **`sms.status_updated`** Útil cuando envías confirmaciones desde tu propio número y necesitas saber si realmente llegaron. Si el estado es failed, el código de error y el mensaje del operador están en el payload. ```json { "event_type": "sms.status_updated", "timestamp": "2026-05-22T12:34:56Z", "data": { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "message_id": "42", "old_status": "sent", "new_status": "delivered", "error_code": null, "error_message": null } } ``` ### Restauración de números completada **`number.restore_completed`** Contiene dos listas: qué números volvieron y cuáles no. El monto reembolsado por los fallidos también está en el payload. Práctico para scripts que reintentan de forma inteligente. ```json { "event_type": "number.restore_completed", "timestamp": "2026-05-22T12:34:56Z", "data": { "restored": [ { "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "phone_number": "+46764794425", "country": "SE" } ], "failed": [ { "piv_num_id": "abc12-defgh-ijklm-nopqr", "phone_number": "+46123456789", "country": "SE", "refunded": 7.50 } ], "total_restored": 1, "total_failed": 1, "total_refunded": 7.50 } } ``` ### Verificación Google QR exitosa **`verify.completed`** Si llegó un SMS con código durante el proceso, ya está aquí — no hace falta una solicitud adicional. captured_phone y captured_message contienen lo que envió Google. ```json { "event_type": "verify.completed", "timestamp": "2026-05-22T12:34:56Z", "data": { "task_id": 12, "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "status": "sms_sent", "captured_phone": "+14155551234", "captured_message": "Your Google verification code is 123456", "sms_message_id": 42, "sms_status": "queued" } } ``` ### Verificación Google QR fallida **`verify.failed`** error_code indica qué ocurrió específicamente — timeout, rechazo, URL inválida. Las verificaciones fallidas se reembolsan al balance automáticamente. ```json { "event_type": "verify.failed", "timestamp": "2026-05-22T12:34:56Z", "data": { "task_id": 12, "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o", "status": "failed", "error_code": "VERIFY_TIMEOUT", "error_message": "Verification timed out" } } ``` ### Proxy próximo a vencer **`proxy.expires_soon`** Diseñado para quienes no renuevan automáticamente: lo ves con anticipación y decides — extender o cerrar la campaña. Vincúlalo a POST /proxy/orders/{id}/renew para renovación totalmente automática. ```json { "event_type": "proxy.expires_soon", "timestamp": "2026-05-22T12:34:56Z", "data": { "order_id": "ord-uuid-...", "country": "DE", "quantity": 10, "expires_at": "2026-05-25T00:00:00Z" } } ``` ### Dominio registrado **`domain.registered`** Si solicitaste Cloudflare y SSL al comprar, ambos ya están activos — cloudflare_enabled refleja el estado. Puedes agregar registros DNS o desplegar el sitio de inmediato. ```json { "event_type": "domain.registered", "timestamp": "2026-05-22T12:34:56Z", "data": { "domain_id": "dom-uuid-...", "domain_name": "mysite.com", "registered_at": "2026-05-22T12:34:56Z", "expires_at": "2027-05-22T12:34:56Z", "cloudflare_enabled": true } } ``` ### Registro de dominio fallido **`domain.failed`** El campo error tiene una razón legible por humanos. El costo se reembolsa al balance. Elige otro nombre e inténtalo de nuevo. ```json { "event_type": "domain.failed", "timestamp": "2026-05-22T12:34:56Z", "data": { "domain_id": "dom-uuid-...", "domain_name": "mysite.com", "error": "Domain is no longer available" } } ``` ### Dominio próximo a vencer **`domain.expires_soon`** Útil cuando la renovación automática está desactivada: tienes tiempo de renovar manualmente antes de que el dominio entre en redención. El indicador auto_renew indica si es necesaria la intervención. ```json { "event_type": "domain.expires_soon", "timestamp": "2026-05-22T12:34:56Z", "data": { "domain_id": "dom-uuid-...", "domain_name": "mysite.com", "expires_at": "2026-05-29T00:00:00Z", "auto_renew": false } } ``` ### White generado **`white.completed`** El archivo está disponible vía GET /whites/{id}/download. Si el White tenía un dominio configurado, el sitemap y los contactos ya están vinculados — listo para desplegar. ```json { "event_type": "white.completed", "timestamp": "2026-05-22T12:34:56Z", "data": { "job_id": "job-uuid-...", "name": "My White", "tier_key": "t1", "country": "DE" } } ``` ### Generación de White fallida **`white.failed`** El campo error explica por qué. Inicia un nuevo trabajo vía POST /whites — sin pérdida de dinero. ```json { "event_type": "white.failed", "timestamp": "2026-05-22T12:34:56Z", "data": { "job_id": "job-uuid-...", "name": "My White", "error": "Generation failed" } } ``` ## Error codes Todos los errores siguen el mismo formato: estado HTTP + campo `error.code` + un mensaje legible en `error.message`. ```json { "success": false, "error": { "code": "INSUFFICIENT_BALANCE", "message": "Not enough balance: required $5.00, available $1.20" } } ``` | Code | HTTP | When it fires | | --- | --- | --- | | `UNAUTHORIZED` | 401 | Clave ausente, vencida o revocada. | | `INSUFFICIENT_PERMISSIONS` | 403 | La clave no tiene el scope requerido para esta acción. | | `PERMISSION_DENIED` | 403 | El acceso a nivel de cuenta no permite esta acción (p. ej., función deshabilitada). | | `VALIDATION_ERROR` | 400 | Cuerpo de la solicitud inválido. El mensaje explica qué campo falló y por qué. | | `NUMBER_NOT_FOUND` | 404 | El número no existe o no pertenece a la cuenta. | | `NUMBER_EXPIRED` | 403 | El número ha vencido — use Restore (dentro de los 7 días) o adquiera uno nuevo. | | `NUMBER_NOT_ACTIVE` | 400 | El número se encuentra en un estado que no permite esta operación. | | `INSUFFICIENT_BALANCE` | 402 | Fondos insuficientes para realizar esta operación. | | `INVALID_MESSAGE_FORMAT` | 400 | El cuerpo del SMS es demasiado largo o contiene caracteres no permitidos. | | `COUNTRY_NOT_AVAILABLE` | 400 | El país no está disponible para compra o renovación. | | `NO_NUMBERS_AVAILABLE` | 400 | No hay números disponibles en el país seleccionado en este momento. | | `NO_RESTORABLE_NUMBERS` | 404 | Ninguno de los números proporcionados puede ser restaurado. | | `RATE_LIMITED` | 429 | Se superó el rate limit. Espere el tiempo indicado en el encabezado `Retry-After`. | | `INTERNAL_ERROR` | 500 | Algo falló de nuestro lado. Si persiste, contacte al soporte. |