Saltar a contenido

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:

Authorization: Bearer 7|EJEMPLOxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

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.rnc ya 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 ejemplo emisor.rnc y emisor_rnc), responde 422 sin 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 ; también se acepta «emisor_rnc» como campo del cuerpo o de la consulta. El token identifica a quien llama, que puede atender a varias empresas; el RNC de la petición dice a cuál se refiere.»

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).

GET /api/v1/companies HTTP/1.1
Authorization: Bearer <su-token>
{
  "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:issue reenvía un e-NCF que ya existe, la respuesta lleva el detalle de ese documento (estado, track_id, importes, errores), igual que GET /api/v1/documents/{identificador} (sin los archivos), aunque el token no tenga documents: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 el track_id, en estados.md).

Los dos marcados con ⚠️ merecen atención al concederlos:

  • certificate:write cambia con qué se firma todo lo que emita esa empresa a partir de ese momento.
  • sequences:void destruye 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.

full no es lo mismo que *: si mañana se añade un permiso nuevo, un token con * lo hereda sin que nadie lo decida; uno con full no.

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 en emisor.rnc (cuerpo JSON), <RNCEmisor> (XML) o emisor_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 recibe 404: 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 pide emisor_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 401 con 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, incluido companies: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.