Errores
Cuando una petición falla, la API responde con un documento application/problem+json
(RFC 9457), siempre con la misma forma y siempre en
español:
{ "type": "https://docs.emitay.com/errors/invalid_request", "title": "La petición no es válida", "status": 422, "detail": "Revisa 3 campos: customer.document_type, items.0.unit_price, items.0.unitPrice. El motivo de cada uno está en errors", "errors": [ { "field": "customer.document_type", "message": "Indica el tipo de documento (catálogo 06 de SUNAT: \"1\" DNI, \"4\" carné de extranjería, \"7\" pasaporte, \"0\" sin documento…): solo se deduce cuando el número es un RUC válido" }, { "field": "items.0.unit_price", "message": "Es obligatorio" }, { "field": "items.0.unitPrice", "message": "Campo no reconocido. Los campos de la API se escriben en snake_case: prueba con unit_price" } ]}| Campo | Qué es |
|---|---|
type |
La dirección de la página que explica el error. Su último tramo es el código (invalid_request): compara contra él, no contra los textos |
title |
El error en una frase. Es el mismo para todas las respuestas con ese código |
status |
El estado HTTP, repetido en el cuerpo |
detail |
Qué pasó en esta petición en concreto |
errors |
Cuando el problema es de campos: una entrada por campo, con su ruta (items.0.unit_price) y el motivo |
Los errores de validación nombran el campo
Sección titulada «Los errores de validación nombran el campo»Un 422 no dice «petición inválida» y te deja buscando. Dice qué campo, por qué, y a veces
cómo arreglarlo: un nombre en camelCase recibe su forma en snake_case; un código fuera de
catálogo, la lista de los válidos. Esos mensajes se pueden mostrar tal cual a quien llenó el
formulario.
Y un 422 nunca consume número: lo que SUNAT rechazaría se niega antes de numerar. La
serie no queda con huecos por un error de validación.
Con el SDK
Sección titulada «Con el SDK»Todo lo que el cliente lanza extiende EmitayError:
import { EmitayApiError, EmitayConnectionError } from "emitay";
try { await emitay.invoices.create(body);} catch (error) { if (error instanceof EmitayApiError) { error.status; // 422 error.code; // "invalid_request" error.detail; // el motivo, en español error.fields; // { "customer.document_type": "Indica el tipo de documento…" } error.requestId; // "req_…": cítalo al pedir soporte } else if (error instanceof EmitayConnectionError) { // No hubo respuesta tras los reintentos: red caída o tiempo agotado. }}Todos los códigos
Sección titulada «Todos los códigos»La lista es cerrada: la API no responde con ningún otro. Cada código enlaza a su página, que
es la dirección que trae type.
| Estado | Código | Qué pasó |
|---|---|---|
400 | bad_request | No se pudo leer la petición |
401 | unauthorized | Falta la llave de API o no es válida |
403 | forbidden | No tienes permiso para hacer esto |
404 | not_found | No existe el recurso |
409 | conflict | La petición choca con el estado actual del recurso |
409 | number_already_used | Ese número de comprobante ya existe |
409 | company_not_configured | La empresa no está lista para producción |
413 | payload_too_large | El cuerpo de la petición es demasiado grande |
415 | unsupported_media_type | El cuerpo de la petición debe ser JSON |
422 | invalid_request | La petición no es válida |
422 | invalid_document | El comprobante no es válido |
429 | rate_limited | Demasiadas peticiones |
500 | internal_error | Error inesperado |
501 | not_implemented | Aún no está implementado |
Depurar
Sección titulada «Depurar»-
Request-Id: toda respuesta, también las de error, lleva esta cabecera (req_…). -
Registro de peticiones: Emitay guarda 30 días el método, la ruta, el estado, la duración y el código de error de cada petición autenticada (nunca el cuerpo).
Ventana de terminal npx emitay logs --errors2026-10-08 17:40:12 POST /v1/invoices 422 invalid_request 4 ms req_01m4… -
Los rechazos de SUNAT no son errores HTTP. Un comprobante que SUNAT rechaza se creó (
201) y quedarejected, con el código y el mensaje de SUNAT ensunat. Ver El veredicto de SUNAT.