Tokens, empresas y permisos¶
Cada llamada a esta API lleva tres cosas que conviene no confundir (la tercera viaja dentro del token):
| Qué | Dónde viaja | Qué dice |
|---|---|---|
| El token | Authorization: Bearer … |
Quién llama: su integración (su ERP, su plataforma). Lleva declarado qué puede hacer. |
| La empresa | el propio mensaje: emisor.rnc (JSON), <RNCEmisor> (XML), emisor_rnc (consultas) o el prefijo del identificador (un documento suyo) |
En nombre de quién actúa: el RNC de la empresa a la que se refiere la petición. Es el mismo dato que dice de quién es el comprobante. |
| Los permisos | dentro del token | Qué operaciones puede hacer esa credencial. |
Esta página explica las tres: cómo funciona el token, cómo se indica la empresa y cómo se acotan los permisos.
1. El token¶
Un token Bearer:
No hay endpoint de login. El token es de su integración —no de un usuario ni de una empresa— y se lo
entrega quien da de alta esa integración. Un mismo token puede atender a varias empresas: las que
tenga habilitadas. Para comprobar que el suyo funciona y ver cuáles son, GET /api/v1/companies
(más abajo).
Un par de cosas que suelen preguntarse:
| ¿Caduca? | No. Vive hasta que se revoca. |
| ¿Se puede revocar? | Sí, y tiene efecto inmediato: se comprueba contra la base en cada petición. |
| ¿Qué se guarda de él? | Sólo un SHA-256. Ni nosotros podemos recuperar el original: si se pierde, se emite otro. |
| ¿Es seguro siendo tan corto? | Sí. Son unos 238 bits de entropía (40 caracteres aleatorios); 128 ya se consideran inquebrantables. Un JWT parece más largo porque lleva datos legibles dentro, no más secreto. |
Trate el token como una clave de sistema. Quien lo presenta puede actuar en nombre de todas las empresas habilitadas para él, dentro de sus permisos. Guárdelo en la configuración de su servidor, nunca en una aplicación que se distribuya a los usuarios finales, y no lo comparta entre sistemas: si hay que revocar uno, que no arrastre a los demás.
2. La empresa: el RNC que declara la petición¶
El token dice quién llama, y quien llama puede atender a varias empresas, así que cada petición dice a
cuál se refiere con el RNC (9 dígitos) o la cédula (11) de la empresa. No hay ninguna cabecera para
eso: el RNC viaja donde ya viaja el emisor del comprobante, y la API comprueba que su token esté
vinculado a esa empresa. Se admiten guiones y espacios (1-33-80207-4); se compara sólo con los dígitos.
| La petición es… | El RNC va en… | Ejemplo |
|---|---|---|
| Un cuerpo JSON (emitir, anular secuencias, aprobación comercial, subir el certificado) | emisor.rnc |
{"emisor": {"rnc": "133802074"}, …} |
| Un cuerpo XML (modo XML) | <RNCEmisor> del propio e-CF |
<RNCEmisor>133802074</RNCEmisor> |
Una consulta sin cuerpo (GET: listado, recibidos, directorio, certificado, anulaciones…) |
el parámetro emisor_rnc |
GET /api/v1/documents?emisor_rnc=133802074 |
La consulta de un documento suyo (GET /documents/{identificador}, …/dgii-status, …/track-ids) |
el prefijo del identificador de la ruta: el RNC del emisor delante del e-NCF. No hace falta emisor_rnc. |
GET /api/v1/documents/133802074E310000000001 |
Si no puede anidar el campo en su JSON o formulario, también vale emisor_rnc como campo plano; en un
formulario (multipart o urlencoded) se escribe emisor[rnc].
La cuarta forma: el identificador de la ruta. El e-NCF sólo es único dentro de un emisor, así que un
documento suyo se consulta por su identificador: {RNC o cédula del emisor}{e-NCF}, p. ej.
133802074E310000000001 (cómo se arma). Como ya lleva el RNC del
emisor, esas rutas no piden emisor_rnc: la empresa es el prefijo del identificador y se comprueba contra
los vínculos de su token igual que las demás (sin vínculo, el mismo 403 de abajo). Si además manda
emisor_rnc y es otro RNC —aunque sea otra de sus empresas—, es el 422 de «más de una empresa»: nunca se
elige una en silencio; si es el mismo, vale.
GET /api/v1/documents/133802074E310000000001 HTTP/1.1
Host: endpoint-test.emision.do
Authorization: Bearer <su-token>
Un documento de un tercero (un e-CF que usted recibió: GET /api/v1/received-documents/{eNCF}/dgii-status,
aprobación comercial) no se pide por identificador: ahí el e-NCF no es suyo y su empresa sigue yendo en
emisor_rnc (o emisor.rnc).
POST /api/v1/documents/31 HTTP/1.1
Host: endpoint-test.emision.do
Authorization: Bearer <su-token>
Content-Type: application/json
{
"emisor": { "rnc": "133802074" },
"id_doc": { "e_ncf": "E310000000001", … },
…
}
Es obligatorio en todas las llamadas que actúan en nombre de una empresa: emitir, consultar
comprobantes, recibidos, certificado, anulación de secuencias, directorio… (en la consulta de un documento
suyo lo cumple el identificador). Las únicas que no declaran empresa son GET /api/v1/companies (que
lista las empresas, no habla de una) y GET /api/v1/statuses (una tabla de referencia, igual para todos).
Una sola regla, y un solo RNC. En una petición con cuerpo,
emisor.rncya era obligatorio (dice de quién es el comprobante); ahora además identifica la empresa en cuyo nombre se actúa. No hay que mandar el RNC dos veces ni se puede mandar uno distinto en otro sitio: si la petición declara dos RNC distintos (por ejemploemisor.rncyemisor_rnc), responde422sin elegir uno en silencio. Ninguna cabecera HTTP identifica la empresa: si su integración enviaba una, se ignora y hay que declarar el RNC como se indica arriba.
Qué responde si algo falla¶
Todas llevan el envelope único de la API: status (error), message y la
explicación en data.detalle. Todas son antes de crear nada: no se consume ningún e-NCF.
422 — la petición no declara la empresa. El detalle cambia según el tipo de petición. En una consulta (GET):
{
"status": "error",
"message": "Falta indicar la empresa.",
"data": {
"detalle": "Envíe en la consulta el parámetro «emisor_rnc» con el RNC (9 dígitos) o la cédula (11 dígitos) de la empresa en cuyo nombre actúa, por ejemplo «?emisor_rnc=133802074». El token identifica a quien llama, que puede atender a varias empresas; el RNC de la petición dice a cuál se refiere."
}
}
En una petición con cuerpo JSON o de formulario, el detalle es: «Envíe en el cuerpo el nodo «emisor.rnc» (en un formulario, «emisor[rnc]») con el RNC (9 dígitos) o la cédula (11 dígitos) de la empresa en cuyo nombre actúa. En un XML va en
422 — el RNC no tiene forma de RNC (letras, longitud distinta de 9 u 11):
{
"status": "error",
"message": "El RNC de la empresa no es válido.",
"data": {
"detalle": "«emisor_rnc (consulta)» debe traer el RNC (9 dígitos) o la cédula (11 dígitos) de la empresa, sin letras."
}
}
El texto nombra el sitio donde venía el valor (emisor.rnc, emisor_rnc (consulta)…). Un valor que no es un texto (un número o un objeto JSON) también se rechaza: «El valor de «emisor.rnc» debe ser texto: el RNC (9 dígitos) o la cédula (11 dígitos) de la empresa.» Mande el RNC entre comillas, para no perder ceros a la izquierda. En la consulta también vale emisor[rnc]= (equivale a emisor_rnc=), y cuenta como una declaración más: si discrepa de la del cuerpo, es el 422 de «más de una empresa».
422 — la petición declara dos RNC distintos:
{
"status": "error",
"message": "La petición declara más de una empresa.",
"data": {
"detalle": "El RNC del emisor viene por más de un sitio (emisor.rnc, emisor_rnc (consulta)) y no coincide. Declare una sola empresa por petición."
}
}
El mismo 422 aparece si manda emisor_rnc junto a un identificador cuyo prefijo es otro RNC; el texto nombra el
sitio del identificador:
{
"status": "error",
"message": "La petición declara más de una empresa.",
"data": {
"detalle": "El RNC del emisor viene por más de un sitio (identificador (ruta), emisor_rnc (consulta)) y no coincide. Declare una sola empresa por petición."
}
}
422 — en el modo XML falta <RNCEmisor> o el XML no se puede leer (nunca es un 500). Si falta el nodo:
{
"status": "error",
"message": "Falta indicar la empresa.",
"data": {
"detalle": "Falta el RNC del emisor: el e-CF no trae el nodo <RNCEmisor> con el RNC (9 dígitos) o la cédula (11 dígitos) de la empresa. El token identifica a quien llama, que puede atender a varias empresas; el RNC de la petición dice a cuál se refiere."
}
}
Si el XML está mal formado, el mensaje es «No se pudo leer el RNC de la empresa.»
422 — el cuerpo JSON no se puede leer (una coma de más, una llave sin cerrar). No se confunde con «Falta indicar la empresa.»: se dice que el JSON está mal formado.
{
"status": "error",
"message": "El cuerpo de la petición no es un JSON válido.",
"data": {
"detalle": "No se pudo leer el cuerpo como JSON. Revise que esté bien formado (comillas, comas, llaves) y vuelva a enviarlo."
}
}
403 — esa empresa no está habilitada para el token:
{
"status": "error",
"message": "Este token no puede actuar sobre esa empresa.",
"data": {
"detalle": "La empresa indicada no está habilitada para este token. Compruebe el RNC; si es correcto, solicite que habiliten esa empresa para su cuenta."
}
}
Si recibe este 403, esa empresa no está habilitada para ese token: revise el RNC y, si es el correcto, pida la empresa a quien le entregó la credencial. Se contesta igual si la empresa no existe, si no es de las suyas o si su habilitación está suspendida: a propósito, para que nadie pueda averiguar por tanteo qué RNC son clientes.
Por qué importa el RNC del comprobante¶
Como el RNC del emisor es el que decide con qué empresa se actúa, el comprobante sale siempre bajo el
RNC que declara, y sólo si su token está vinculado a ese RNC. Un sistema que atiende a varias empresas
no puede, por descuido, emitir bajo una empresa con los datos de otra, gastando números de la secuencia
que no toca (algo que no se recupera): el RNC que pone en emisor.rnc es el de la empresa cuyo
certificado firma, cuyos números se consumen y a cuyo nombre queda el comprobante.
Las empresas de su token: GET /api/v1/companies¶
Lista las empresas que su token tiene habilitadas: de ahí sale el RNC que va en emisor.rnc /
emisor_rnc. Exige el permiso companies:read y no declara empresa (no habla de una en concreto).
{
"status": "success",
"message": "Consulta exitosa.",
"data": [
{
"rnc": "101672919",
"razon_social": "ACME, SRL",
"nombre_comercial": "Acme",
"ambiente": "testecf",
"habilitada": true
},
{
"rnc": "133802074",
"razon_social": "EJEMPLO COMERCIAL, SRL",
"nombre_comercial": null,
"ambiente": "testecf",
"habilitada": false
}
]
}
| Campo | Qué es |
|---|---|
rnc |
RNC de la empresa: el valor para emisor.rnc (o emisor_rnc). |
razon_social |
Nombre legal, como consta en el registro. |
nombre_comercial |
Nombre comercial, o null si no tiene. |
ambiente |
Ambiente de la DGII en el que emite la empresa: testecf (pre-certificación), certecf (certificación) o ecf (producción). |
habilitada |
true si el vínculo de su token con esa empresa está activo. Con false la empresa existe para su token pero ese vínculo está suspendido: usarla da el 403 de arriba. Sólo dice del vínculo con su token, no del estado de la empresa en sí. |
Salen ordenadas por razón social y sólo aparecen las que son de su token: nunca las de otros. Una
lista vacía ("data": []) significa que todavía no tiene ninguna habilitada: pídala a quien le entregó la
credencial.
3. Los permisos¶
Cada token declara qué puede hacer. La misma credencial que un ERP pega en su configuración para facturar no tiene por qué poder también cambiar el certificado con el que se firma ni anular rangos de secuencias: son las dos operaciones más caras de la API y casi ningún integrador las necesita.
Los diez permisos¶
Se leen dominio:acción, y son predecibles: si existe documents:read, existe documents:issue.
| Permiso | Qué permite |
|---|---|
companies:read |
Listar las empresas que su token tiene habilitadas (GET /api/v1/companies). |
documents:issue |
Emitir comprobantes. Los transmite a la DGII, con el e-NCF que usted envía. Ver la nota sobre el reenvío de un folio, más abajo. |
documents:read |
Consultar los comprobantes propios y su estado ante la DGII. |
received:read |
Ver los comprobantes recibidos de terceros y su validez fiscal. |
received:approve |
Aceptar o rechazar comercialmente un comprobante recibido. |
certificate:read |
Consultar el certificado instalado (titular y vigencia; nunca el fichero). |
certificate:write |
Subir el certificado con el que se firma. ⚠️ |
sequences:read |
Consultar las anulaciones de secuencias realizadas. |
sequences:void |
Anular rangos de e-NCF no utilizados. ⚠️ Irreversible. |
directory:read |
Consultar el directorio de facturadores de la DGII. |
Leer y escribir van siempre separados. Poder consultar el certificado no abre la puerta a cambiarlo; poder ver las anulaciones no permite anular. Es deliberado.
Una excepción que conviene conocer: el reenvío de un folio. Si un token con
documents:issuereenvía un e-NCF que ya existe, la respuesta lleva el detalle de ese documento (estado,track_id, importes, errores), igual queGET /api/v1/documents/{identificador}(sin los archivos), aunque el token no tengadocuments:read. Es a propósito: la idempotencia lo necesita. Quien no sabe si su envío llegó (un corte, un timeout) reenvía el mismo folio y tiene que poder saber qué pasó, en vez de recibir un 403 y usar otro número, que duplicaría el comprobante. Si necesita una credencial que no pueda ver nada de lo emitido, tenga presente que quien puede emitir puede, reenviando un folio de esa empresa, ver su detalle (cuándo aparece eltrack_id, en estados.md).
Los dos marcados con ⚠️ merecen atención al concederlos:
certificate:writecambia con qué se firma todo lo que emita esa empresa a partir de ese momento.sequences:voiddestruye consecutivos autorizados. La DGII no admite reutilizar después una secuencia anulada, así que un rango anulado por error son números perdidos.
Los permisos son del token, no de la empresa: valen igual para todas las empresas que el token tenga habilitadas.
Perfiles¶
Nadie reparte permisos de uno en uno. Se concede un perfil:
| Perfil | Incluye | Para quién |
|---|---|---|
integration |
companies:read, documents:issue, documents:read, received:read, certificate:read, directory:read |
Un ERP que factura. Es el que se entrega por defecto. |
read-only |
los seis :read (companies, documents, received, certificate, sequences, directory) |
Un panel de consulta, una auditoría, un contador externo. |
full |
los diez | Quien administra la integración por completo. |
fullno es lo mismo que*: si mañana se añade un permiso nuevo, un token con*lo hereda sin que nadie lo decida; uno confullno.
Qué endpoint pide qué¶
| Endpoint | Permiso | Declara la empresa |
|---|---|---|
GET /api/v1/companies |
companies:read |
no |
GET /api/v1/statuses |
ninguno | no |
POST /api/v1/documents/{tipo} |
documents:issue (ver la nota sobre el reenvío de un folio) |
sí |
GET /api/v1/documents |
documents:read |
sí |
GET /api/v1/documents/{identificador} (el detalle, con sus archivos) |
documents:read |
sí, por el identificador |
GET /api/v1/documents/{identificador}/dgii-status |
documents:read |
sí, por el identificador |
GET /api/v1/documents/{identificador}/track-ids |
documents:read |
sí, por el identificador |
GET /api/v1/received-documents |
received:read |
sí |
GET /api/v1/received-documents/{eNCF}/dgii-status |
received:read |
sí |
POST /api/v1/received-documents/{eNCF}/commercial-approval |
received:approve |
sí |
GET /api/v1/certificate |
certificate:read |
sí |
POST /api/v1/certificate |
certificate:write |
sí |
GET /api/v1/voided-sequences |
sequences:read |
sí |
POST /api/v1/voided-sequences |
sequences:void |
sí |
GET /api/v1/directory, /directory/{rnc} |
directory:read |
sí |
statuses no pide permiso a propósito: es una tabla de referencia idéntica para todos. Exigir un
permiso para leerla daría trabajo sin proteger nada.
Los tres recursos de recepción se describen completos en recepcion.md (el listado y la aprobación comercial) y en consultas-dgii.md (la validez fiscal de un recibido).
4. Cuando algo falla¶
Cinco respuestas distintas, que se distinguen por el código y por el cuerpo. Se comprueban en este
orden: primero el token, luego la empresa, luego el permiso y, al final, los datos del comprobante. Las
cuatro primeras explican el porqué en data.detalle; la validación de un e-CF, en document.errors
(cómo leer cualquier respuesta).
| Código | Significa | Qué hacer |
|---|---|---|
| 401 | El token falta, está mal copiado, fue revocado, o la integración dejó de estar activa. | Revise la cabecera Authorization; si es correcta, solicite un token nuevo. |
| 422 con «Falta indicar la empresa.», «El RNC de la empresa no es válido.» o «La petición declara más de una empresa.» | La petición no trae el RNC del emisor (emisor.rnc, RNCEmisor o emisor_rnc), está mal formado o hay dos distintos. |
Declárelo una sola vez, con el RNC (9 dígitos) o la cédula (11). |
| 403 con «Este token no puede actuar sobre esa empresa.» | El token vale, pero esa empresa no está habilitada para él. | Revise el RNC declarado (o el prefijo del identificador); si es correcto, pida que habiliten la empresa. |
403 con permisos_requeridos |
El token vale y la empresa es suya, pero no tiene el permiso para esta operación. | Solicite una credencial que lo incluya. |
422 con document.errors |
Todo lo anterior está bien, pero los datos del comprobante no pasan la validación. No se crea nada: su e-NCF sigue libre. | Corrija los errores que se listan. (La validación de lo que no es un e-CF —un filtro, una anulación— responde con data.detalle y data.errores.) |
401: token ausente o inválido¶
Dice cuál de las dos cosas —para distinguir una cabecera olvidada de un token mal copiado— sin revelar
si el token existió alguna vez, y lleva WWW-Authenticate: Bearer. No hace falta pedir
Accept: application/json para recibirlo (conviene enviarlo igualmente):
{
"status": "error",
"message": "No se pudo autenticar la petición.",
"data": {
"detalle": "Falta el token. Envíelo en la cabecera «Authorization: Bearer {token}»."
}
}
Si el token se envía pero no vale (mal copiado o revocado), el detalle es: «El token enviado no es válido: puede estar mal
copiado o haber sido revocado. Solicite uno nuevo.» Un token no caduca: vale hasta que se revoca.
403: falta un permiso¶
La respuesta dice exactamente qué falta —para que quien integra sepa qué credencial pedir, en vez de recibir un «prohibido» mudo:
{
"status": "error",
"message": "El token no tiene permiso para esta operación.",
"data": {
"detalle": "Esta operación exige el permiso «documents:issue». Solicite una credencial que lo incluya.",
"permisos_requeridos": ["documents:issue"],
"permisos_del_token": ["documents:read", "received:read"]
}
}
5. Si venía de una versión anterior¶
Antes, cada token pertenecía a una sola empresa y la API la deducía de él. Ahora el token es de la integración y la empresa se indica en cada llamada, con el RNC que la propia petición declara:
- Si su token sigue funcionando pero recibe
422«Falta indicar la empresa.»: declare el RNC de la empresa enemisor.rnc(cuerpo JSON),<RNCEmisor>(XML) oemisor_rnc(consultas). Si su integración indicaba la empresa en una cabecera HTTP, esa cabecera ya no se lee: el emisor del comprobante, que ya iba en el cuerpo, es lo único que hace falta. - Si consultaba un documento por su e-NCF (con
emisor_rnc) y ahora recibe404: la consulta de un documento suyo se hace por su identificador —el RNC del emisor delante del e-NCF, p. ej.133802074E310000000001—, y esa ruta ya no pideemisor_rnc. Los archivos (XML y PDF) vienen en el propio detalle, en base64: ya no hay recursos aparte para descargarlos con una URL temporal. Detalle en «El identificador del documento». - Si recibe
401con un token que antes funcionaba: ese token era de los de una sola empresa y ya no vale. Solicite uno nuevo a quien le entregó la credencial y pídale que le habilite sus empresas. - Los tokens con el comodín
*satisfacen cualquier permiso, incluidocompanies:read. Para acotar uno basta solicitar un token con el perfil que corresponda y sustituirlo: reduce lo que un token filtrado permite hacer.
6. Pedir o cambiar un token¶
Los tokens no se crean desde la API: se solicitan a quien le entregó la credencial. Al pedirlo, indique el
perfil que necesita —o, si prefiere, los permisos sueltos—, las empresas que debe atender y un
nombre que identifique de dónde se va a usar (erp-produccion, pos-sucursal-norte): es lo que permite
revocar uno sin tocar los demás.
Conviene un token por sistema, y no uno compartido: si mañana hay que revocar el del punto de venta, la facturación del ERP no se entera. Para habilitar otra empresa, o suspender una, también se pide: la misma credencial pasa a atenderla (o deja de hacerlo) sin cambiar el token.
Revocación. Pídalo y se invalida; tiene efecto inmediato, porque el token se comprueba contra nuestra base en cada petición. Si sospecha que se filtró, hágalo sin esperar: no caducan solos.
Rotación. Se emite el nuevo y se le da un margen para desplegarlo antes de revocar el anterior, de forma que no haya un hueco sin servicio.