Saltar al contenido principal

Quickstart

En cinco minutos vas a mandar un POST /v1/documents real, recibir una factura emitida y leer su timeline de eventos. Usamos Colombia (DIAN) como ejemplo; la misma forma funciona para MX, EC y PE con un bloque country_specific distinto.

Qué necesitás

Bearer tokenUn access token OAuth2 / JWT con el scope invoicing.document.write. Pedíselo a tu admin de Quetzal o leé Authentication.
Tenant IDEl slug del tenant al que pertenecés (ej. mattilda). Se manda en el header X-Tenant-Id.
Un issuer profileUn IssuerProfile registrado para el país en el que vas a facturar. El profile guarda tu certificado fiscal y rango de numeración — sin él, el request falla en la etapa de gatekeeping.
curl y jqLo mantenemos en shell. Cualquier cliente HTTP sirve.

1. Configurá tu entorno

export QUETZAL_API="https://quetzal-api.mattilda.io" # o .net para staging
export QUETZAL_TOKEN="eyJhbGciOiJI..." # tu bearer token
export TENANT="mattilda"

2. Hacé tu primer POST

El request mínimo necesita tenant_id, country, document_type, intent, un issuer, un receiver, al menos un entry en line_items[] y un bloque country_specific. SUBMIT le dice a Quetzal que mande el documento a la autoridad tributaria inmediatamente; usá DRAFT para dejarlo armado sin emitir.

IDEMPOTENCY_KEY=$(uuidgen)

curl -sS -X POST "$QUETZAL_API/v1/documents" \
-H "Authorization: Bearer $QUETZAL_TOKEN" \
-H "X-Tenant-Id: $TENANT" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "mattilda",
"country": "CO",
"document_type": "INVOICE",
"intent": "SUBMIT",
"issuer": {
"tax_id": "901722162",
"tax_id_type": "NIT",
"name": "Mattilda Colombia S.A.S."
},
"receiver": {
"tax_id": "1566021566",
"tax_id_type": "GENERIC_ID",
"name": "Acme S.A.S.",
"email": "ar@acme.example"
},
"line_items": [
{
"description": "Mensualidad escolar",
"unit_price": { "amount": "1200000.00", "currency": "COP" },
"quantity": 1
}
],
"country_specific": {
"type": "co_dian.v2024_11",
"co_dian.v2024_11": {
"numerator_prefix": "SETT",
"municipality_code": "11001",
"department_code": "11"
}
}
}' | jq

Te va a devolver un recurso Document. Guardate su id:

DOC_ID=$(curl ... | jq -r '.id')

3. Qué acaba de pasar

La respuesta incluye el status actual del documento. Inmediatamente después de un SUBMIT lo normal es ver PENDING_SUBMISSION — Quetzal aceptó el request y está llamando a la autoridad tributaria en background. Estados terminales comunes:

StatusSignificado
EMITTEDLa autoridad tributaria lo aceptó. fiscal_id está poblado. Listo.
FAILEDLa autoridad tributaria lo rechazó (o hubo un error no-retryable). Inspeccioná failure.classification y failure.reason.
CANCELLEDAlguien llamó POST /v1/documents/{id}/cancel.

Sondeá el recurso hasta que salga de la familia PENDING_*:

curl -sS "$QUETZAL_API/v1/documents/$DOC_ID" \
-H "Authorization: Bearer $QUETZAL_TOKEN" \
-H "X-Tenant-Id: $TENANT" | jq '{ status, fiscal_id, failure }'

Para casos de producción, no sondees — suscribite a un webhook (próxima sección).

4. Leé el timeline de eventos

Cada transición de estado queda registrada. Pedí el timeline completo:

curl -sS "$QUETZAL_API/v1/documents/$DOC_ID/events" \
-H "Authorization: Bearer $QUETZAL_TOKEN" \
-H "X-Tenant-Id: $TENANT" | jq '.items'

Esa es tu pista de auditoría. Los eventos son append-only y ordenados cronológicamente.

5. Idempotencia — qué hace el Idempotency-Key

Mandaste un UUID como Idempotency-Key en el paso 2. Si reenviás el mismo request (mismo body) con el mismo key, Quetzal te devuelve la respuesta original en lugar de crear un documento duplicado. Generá siempre un UUID fresco para cada intención nueva — nunca lo reutilices entre documentos distintos.

Esto importa porque los retries están en todas partes: la red se cae, tu worker se reinicia, tu queue redeliver. Sin un idempotency key, cada uno de esos retries crea un documento nuevo. Con él, solo el primero queda y los demás se deduplican.

Siguientes pasos

  • Otros países: cambiá el campo country y el bloque country_specific. La forma exacta por país está en el API Reference.
  • Auth en profundidad: Authentication cubre scopes, semántica de headers y los 401 / 403 más comunes.
  • Webhooks: dejá de sondear — registrá un endpoint y dejá que Quetzal te empuje los cambios de estado.
  • Cancelar un documento: POST /v1/documents/{id}/cancel con una razón.

Para la forma completa del request (todos los campos opcionales, schemas por país, respuestas de error), navegá el API Reference.