Convenciones
Esta página describe las reglas transversales del API. Léela una vez y aplican a todos los endpoints.
Identificadores de documento
Cada DTE emitido por el API tiene tres identificadores, pensados para usos distintos:
| Identificador | Quién lo asigna | Formato | Uso típico |
|---|---|---|---|
document_id | Ocote | UUID v4 | Identificador interno del API. Se devuelve en el response. |
external_ref | Tú | Texto libre (máx. 100 caracteres) | Identificador que tú controlas. Idempotencia y retry. |
control_number | MH (fijado por Ocote) | DTE-{tipo}-{sucursal}{pos}-{15 dígitos} | Correlativo fiscal. El que aparece en el JSON firmado y en el ticket. |
Reglas importantes
- El
external_refes único por empresa. Dos emisiones del mismo API key con el mismoexternal_refse referencian al mismo documento. Ver Retry e idempotencia. - El
control_numbernunca se regenera. Una vez asignado, queda fijo aun en reintentos. - Los endpoints que reciben un
id_or_refaceptan los tres identificadores indistintamente — UUID o tuexternal_reffuncionan en/status/{id},/invalidate/{id},/file/{id}, etc.
El external_ref es opcional pero altamente recomendado
Si no envías external_ref en el request, Ocote asigna uno automáticamente derivado del UUID. Funciona, pero pierdes la capacidad de encontrar el documento desde tu lado sin guardar el UUID devuelto, y pierdes la ventana de retry idempotente ante un timeout de red.
Recomendación: genera tu propio external_ref con una convención legible (VENTA-2026-000042, INV-A1B2C3, lo que sea), guárdalo antes de llamar al API, y mándalo en cada request.
HTTP status codes
El API sigue convenciones REST estándar con un matiz importante para emisión:
| Código | Cuándo ocurre | Qué hacer |
|---|---|---|
200 OK | La operación se registró en Ocote. Puede ser éxito, contingencia o rechazo por MH. | Leer los flags del body. |
400 Bad Request | Error de validación del payload (campo faltante, NIT mal formado, cantidad negativa). | Corregir el payload. |
401 Unauthorized | API key ausente, inválida o empresa desactivada. | Revisar la key. |
404 Not Found | Documento no encontrado por id_or_ref, o ruta inexistente. | Verificar el identificador. |
409 Conflict | Operación incompatible con el estado actual (p.ej. invalidar un documento ya invalidado). | Consultar /status/{id} para ver el estado actual. |
429 Too Many Requests | Rate limit excedido. | Esperar y respetar Retry-After. Ver Rate limits. |
500 Internal Server Error | Error no controlado en Ocote. | Reintentar tras unos segundos; si persiste, contactar a Ocote. |
200 OK no siempre significa "MH aceptó"Un response 200 con rejected: true significa: Ocote registró tu petición correctamente, pero el MH rechazó el documento por datos del receptor. Corrige y reintenta con el mismo external_ref.
Ver la tabla completa de flags en Respuestas y estados.
Encoding y content-type
- Request: todas las peticiones POST deben llevar
Content-Type: application/jsony body codificado en UTF-8. - Response: JSON UTF-8. Los acentos y caracteres españoles no requieren escape.
- Archivos descargados: ticket en
application/pdf, JSON enapplication/json(con el JWS completo incluyendo sello MH).
Fechas y zonas horarias
- Todas las fechas en responses son ISO 8601 con zona horaria. Ocote corre en
America/El_Salvador(UTC-6). Ejemplo:2026-04-21T10:42:15-06:00. - Para filtros por fecha en endpoints de listado/consulta, usa formato
YYYY-MM-DD(p.ej.date_from=2026-04-01). Se interpretan en hora local (UTC-6). - No necesitas enviar fechas en los requests de emisión. Ocote asigna el timestamp de posteo al momento de recibir la petición aceptada.
Montos, moneda y decimales
- Moneda única: USD. El API no acepta ni devuelve montos en otra moneda. El Salvador opera en dólar como moneda de curso legal para DTE.
- Todos los precios unitarios se envían con IVA incluido en ventas locales (tipos 01, 03, 14). Ocote desglosa internamente base gravable y 13 % de IVA.
- Decimales: acepta hasta 2 decimales en montos y hasta 8 decimales en cantidades (para unidades fraccionarias de tipo peso/volumen). Los montos se redondean a 2 decimales en el DTE final según reglas del MH.
- No envíes símbolos de moneda ni separadores de miles. Correcto:
100.00. Incorrecto:"$100","1,000.00".
Valores enumerados que no son autodescriptivos
Algunos campos del MH usan códigos numéricos que no se pueden inferir de contexto. Los más frecuentes:
| Campo | Valores | Significado |
|---|---|---|
doc_type | "01", "03", "05", "11", "14" | Factura, CCF, Nota de Crédito, Factura Exportación, Sujeto Excluido. |
type_contributor | 1, 2, 3, 4 | Pequeño, Mediano, Grande, Gobierno. |
transaction_condition | 1, 2, 3 | Contado, Crédito, Otro. |
payment_method | "01", "02", "03", "05", "08", … | Efectivo, Tarjeta Débito, Tarjeta Crédito, Transferencia, Dinero Electrónico. Ver Catálogos MH. |
Campos opcionales vs omitidos
El API distingue entre campo ausente (no lo mandaste) y campo enviado con valor vacío. Regla práctica:
- Si un campo es opcional y no lo necesitas, omítelo. No mandes
"email": ""ni"email": nullsi no quieres email; simplemente no incluyas la llave. - Los valores
nullexplícitos se aceptan como equivalentes a "omitido" en la mayoría de campos, pero algunos validadores del MH los rechazan. Prefiere omitir.
Bug conocido: phone
Actualmente el campo customer.phone no se mapea correctamente al modelo interno y puede causar un error de validación si lo envías. Workaround: omite el campo phone del objeto customer hasta que el fix esté desplegado.
Este bug no afecta la emisión de ningún documento, solo el mapeo del teléfono del receptor al registro del cliente en Ocote. El MH no requiere teléfono en ningún tipo de DTE.
Versionado
El API está en su primera versión pública estable. No hay prefijo de versión en la URL (/api/connect/... no /api/connect/v1/...). Los cambios futuros seguirán estas reglas:
- Aditivos no rompen. Agregar campos nuevos al response o parámetros opcionales al request nunca requerirá cambios del cliente.
- Cambios que rompen se comunican con preaviso y se introducen en una ruta
/api/connect/v2/dedicada, manteniendo la v1 por un periodo de deprecación.
Ver Changelog para el historial de cambios.