Saltar a contenido

Mis documentos: el detalle con sus archivos y el listado

Dos endpoints de sólo lectura sobre los e-CF que usted ya emitió. No disparan nada contra la DGII —no consultan su estado en vivo, para eso está consultas-dgii.md— sólo leen lo que ya tenemos registrado y lo que ya se generó.

Endpoint Para qué
GET /api/v1/documents/{identificador} El detalle de un e-CF y, cuando ya tiene validez fiscal, su XML firmado y su PDF en base64
GET /api/v1/documents Listado paginado de sus e-CF emitidos, con filtros

Los dos exigen el permiso documents:read (ver tokens-y-permisos.md), lo incluyen los perfiles integration y read-only, y no hay nada más que pedir para bajar los archivos: no existen recursos aparte para el XML ni el PDF, ni URL firmadas, ni un segundo paso. Quién es la empresa se declara distinto en cada uno:

  • el detalle se pide por el identificador del documento (el RNC del emisor seguido del e-NCF) y la empresa es su prefijo: no lleva emisor_rnc;
  • el listado no habla de un documento concreto y lleva el parámetro emisor_rnc con el RNC de la empresa cuyos documentos se quieren ver.

En los dos, su token tiene que estar habilitado para esa empresa.

Pensados para un portal de sólo consulta o para una pantalla de "Mis documentos" dentro de su propio sistema: sirve el mismo token con el que ya emite, no necesita uno nuevo.

El listado y el detalle son de la empresa: incluyen todos sus e-CF, los haya enviado su sistema u otro que también la atienda.

El detalle de un e-CF, con sus archivos

GET /api/v1/documents/133802074E310000000001
Authorization: Bearer <su-token>
curl -s "https://endpoint-test.emision.do/api/v1/documents/133802074E310000000001" \
  -H "Authorization: Bearer <su-token>" \
  -H "Accept: application/json"

Es la misma respuesta que da el seguimiento del documento (estado, TrackID, errores, entrega al receptor, correo al comprador…) más el identificador y la lista archivos:

{
  "status": "success",
  "message": "Consulta exitosa.",
  "document": {
    "document_id": 1234,
    "ecf_type_code": "31",
    "e_ncf": "E310000000001",
    "identificador": "133802074E310000000001",
    "track_id": "a1b2c3d4-...",
    "status_code": 4,
    "status": "accepted_dgii",
    "status_description": "La DGII aceptó el e-CF: tiene validez fiscal.",
    "is_valid_fiscally": true,
    "…": "…",
    "archivos": [
      {
        "tipo": "pdf",
        "nombre": "133802074E310000000001.pdf",
        "mime": "application/pdf",
        "contenido_base64": "JVBERi0xLjQK"
      },
      {
        "tipo": "xml",
        "nombre": "133802074E310000000001.xml",
        "mime": "application/xml",
        "contenido_base64": "PD94bWwgdmVyc2lvbj0iMS4wIj8+"
      },
      {
        "tipo": "respuesta_dgii",
        "nombre": "133802074E310000000001-respuesta-dgii.json",
        "mime": "application/json",
        "contenido_base64": "eyJjb2RpZ28iOjEsImVzdGFkbyI6IkFjZXB0YWRvIn0="
      }
    ]
  }
}

(Los contenido_base64 del ejemplo están abreviados, y "…": "…" resume el resto del detalle, que es el de siempre.)

El track_id sigue las mismas reglas que en toda la API: null mientras el documento no se haya transmitido, el asignado al transmitir —el documento entra en in_process_dgii con él— y conservado en los estados finales; en la factura de consumo (32) por debajo de RD$250.000 es null para siempre, porque el resumen no tiene TrackID (estados.md).

Qué archivos puede traer

Cada elemento de archivos tiene los mismos cuatro campos: tipo, nombre (el nombre con que guardarlo), mime y contenido_base64 (el contenido del archivo, codificado en base64). Salen siempre en este orden, y sólo los que existan:

tipo Qué es nombre mime Cuándo está
pdf La representación impresa del e-CF, con su código QR. {identificador}.pdf application/pdf Se genera unos segundos después de la aceptación (ver abajo).
xml El e-CF completo, firmado: lo que usted está obligado a conservar. {identificador}.xml application/xml Siempre, en un documento válido.
resumen_rfce El resumen que se transmitió a la DGII. Sólo en la factura de consumo (32) por debajo de RD$250.000. {identificador}-resumen.xml application/xml Siempre, en ese caso.
respuesta_dgii La respuesta final de la DGII, tal cual (su codigo, estado y mensajes): la de la consulta de resultado o, en el resumen de la factura de consumo (que la DGII resuelve en la misma llamada), la que contestó al recibirlo. {identificador}-respuesta-dgii.json application/json Siempre que conste la respuesta de la DGII, que en un documento aceptado es lo normal.

Los nombres siguen el estándar de la DGII para los adjuntos de un e-CF (RNC del emisor + e-NCF), que es justo el identificador: puede guardar cada archivo con el nombre que viene y queda donde la DGII lo espera.

Para la factura de consumo (32) por debajo del umbral, el xml es el e-CF completo (con el detalle de ítems) que usted debe conservar, y el resumen_rfce es lo que de verdad viajó a la DGII — vea ecf-32.md.

Cuándo aparecen: sólo con validez fiscal

archivos sólo se llena cuando el e-CF tiene validez fiscal: accepted_dgii o conditionally_accepted_dgii (estados.md). En cualquier otro estado —recibido, en proceso, rechazado, sin resolución…— la lista va vacía ("archivos": []), aunque el XML ya exista internamente: un comprobante que la DGII no aceptó no es un e-CF que usted deba conservar ni entregar. Un documento rechazado nunca trae archivos; si le hace falta ver por qué, están sus errors.

Eso hace barata la consulta mientras espera: sondee el detalle hasta que el estado sea final y, en cuanto sea válido, recoja los archivos de esa misma respuesta.

El PDF llega segundos después de la aceptación

La representación impresa se genera tras la aceptación, en un segundo paso. Consultado justo cuando el documento pasa a accepted_dgii, el detalle puede traer el xml y todavía no el pdf. No es un error: no se marca como faltante. Vuelva a consultar a los pocos segundos. Si la generación falla de forma pasajera, se reintenta sola.

Si un archivo no se pudo leer: archivos_no_disponibles

Los archivos se guardan en nuestro almacenamiento. Si uno que debería estar no se puede leer en ese momento —el almacenamiento no respondió, o el archivo no está—, la consulta no falla: el detalle es lo importante, y se devuelve sin ese archivo, nombrándolo en archivos_no_disponibles:

{
  "status": "success",
  "message": "Consulta exitosa.",
  "document": {
    "…": "…",
    "archivos": [
      { "tipo": "pdf", "nombre": "133802074E310000000001.pdf", "mime": "application/pdf", "contenido_base64": "JVBERi0xLjQK" }
    ],
    "archivos_no_disponibles": ["xml"]
  }
}
  • La clave sólo aparece cuando falta algo: sin ella, no falta nada de lo que debía estar (el pdf que todavía no se generó no cuenta como falta, ver arriba).
  • Es un fallo pasajero del lado nuestro: repita la consulta. Si persiste, contáctenos.
  • El xml y el resumen_rfce tienen que estar en un documento válido, y por eso se nombran si faltan. El pdf que todavía no se generó no se nombra (ver arriba); sí uno que consta como generado y no se pudo leer. La respuesta_dgii, que es opcional, tampoco se nombra si no consta.

Guardar los archivos

El contenido viene en base64 estándar. Para volcarlo a un archivo con curl y jq:

curl -s "https://endpoint-test.emision.do/api/v1/documents/133802074E310000000001" \
  -H "Authorization: Bearer <su-token>" -H "Accept: application/json" \
  | jq -r '.document.archivos[] | select(.tipo == "xml") | .contenido_base64' \
  | base64 -d > 133802074E310000000001.xml

En código, es decodificar contenido_base64 y escribir los bytes con el nombre indicado. Conserve el xml: es el comprobante fiscal firmado.

Tamaño de la respuesta

El base64 pesa un tercio más que el archivo, y la respuesta lleva, además del detalle, de dos a cuatro archivos: un e-CF con muchas líneas pesa más, y su PDF también. No hay paginación ni forma de pedir un solo archivo, así que:

  • No sondee un documento ya aceptado. Mientras no es válido la respuesta es pequeña; cuando lo es, trae los archivos. Guárdelos y deje de preguntar.
  • Si sólo necesita el estado de muchos documentos, use el listado (abajo): no lleva archivos.

Errores

Código Cuándo
404 El identificador no corresponde a un e-CF de esa empresa: «No se encontró el documento.», con el texto largo en data.detalle. Se responde igual si no existe y si es de otra empresa: nunca se distingue entre "no existe" y "no es suyo". Un identificador mal formado también es un 404, pero de ruta: «La ruta solicitada no existe.»
403 El token no tiene documents:read (el cuerpo dice qué permiso falta), o la empresa del prefijo del identificador no está habilitada para él.
422 Además del identificador viene emisor_rnc (o emisor.rnc) y es otra empresa: «La petición declara más de una empresa.»

Todos llevan el mismo envelope que el resto de la API, con la explicación en data.detalle (cómo leerlo):

{
  "status": "error",
  "message": "No se encontró el documento.",
  "data": {
    "detalle": "No se encontró un e-CF con el identificador [133802074E310000009999] para este emisor."
  }
}

Listado

GET /api/v1/documents?status=accepted_dgii&from=2026-08-01&to=2026-08-31&per_page=25&emisor_rnc=133802074
Authorization: Bearer <su-token>
{
  "status": "success",
  "message": "Consulta exitosa.",
  "data": [
    {
      "document_id": 1234,
      "ecf_type_code": "31",
      "e_ncf": "E310000000001",
      "identificador": "133802074E310000000001",
      "track_id": "a1b2c3d4-...",
      "status_code": 4,
      "status": "accepted_dgii",
      "status_description": "La DGII aceptó el e-CF: tiene validez fiscal.",
      "issued_at": "27-07-2026",
      "total_amount": "1180.00",
      "total_itbis": "180.00",
      "buyer_name": "CLIENTE SRL"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total": 137,
    "last_page": 6
  }
}

track_id va null mientras el documento no se haya transmitido y siempre en las facturas de consumo (32) por debajo de RD$250.000: se transmiten como resumen, que no tiene TrackID (ecf-32.md). En los demás tipos llega relleno al transmitirse (in_process_dgii) y los estados finales lo conservan (estados.md). Para ver juntos todos los recursos de consulta, vaya a «Consultar qué pasó con un documento».

Es el detalle justo para un listado, sin archivos: cada elemento trae su identificador, que es lo que se usa para pedir el documento puntual con GET /api/v1/documents/{identificador} (código de seguridad, entrega al receptor, mensajes de error y, si es válido, el XML y el PDF).

Filtros (todos opcionales, combinables)

Parámetro Formato Filtra por
from aaaa-MM-dd issued_at desde esta fecha (inclusive)
to aaaa-MM-dd issued_at hasta esta fecha (inclusive)
status slug de estados.md (p. ej. accepted_dgii) estado interno exacto
type 31…47 tipo de e-CF exacto
buyer texto libre coincide contra la razón social o el RNC/Cédula del comprador

Un valor inválido (una fecha que no cumple el formato, un status o type que no existe en nuestros catálogos) responde 422 en el mismo envelope que el resto de la API (vea README.md), nunca un error de servidor. Como un filtro no habla de ningún e-CF, no lleva document: lleva data, con detalle (el primer error y cuántos más hay) y errores, los mensajes por campo:

{
  "status": "error",
  "message": "Los datos enviados no son válidos.",
  "data": {
    "detalle": "El campo fecha desde no corresponde al formato Y-m-d. (y 1 error más: vea «errores».)",
    "errores": {
      "from": ["El campo fecha desde no corresponde al formato Y-m-d."],
      "per_page": ["El campo elementos por página debe ser un número entero."]
    }
  }
}

A diferencia de la validación de emitir un e-CF (que responde con document.errors, porque ese comprobante tiene un estado fiscal: rejected_validation), aquí no hay documento de por medio. Un cliente que ya lee el envelope de la API sólo necesita saber dónde mirar: data.detalle para el texto y data.errores para el campo.

Paginación

page y per_page (por defecto 25, tope 100); pedir más de 100 también responde 422. El orden es siempre por issued_at descendente: lo más reciente primero.

meta.total y meta.last_page son del total filtrado, no de todos sus documentos.