Saltar al contenido principal

Authentication

Cada request a Quetzal Invoicing lleva cuatro piezas de identidad. Tres son headers, una es el body. Si cualquiera está mal, el request falla cerrado.

Headers requeridos

HeaderRequerido enQué hace
Authorization: Bearer <token>Todos los endpointsAccess token OAuth2 / JWT. Lleva la identidad del caller, el tenant para el que puede actuar, y los scopes que tiene.
X-Tenant-Id: <slug>Todos los endpointsContexto explícito del tenant del request. Tiene que matchear el claim tenant de tu token. Se manda por separado para que los tokens multi-tenant (raros) sigan siendo inequívocos por request.
Idempotency-Key: <uuid>POST, PATCHUn UUID que vos generás. Quetzal guarda la huella del request bajo este key por 24h. Reintentar el mismo (método, path, key) devuelve la respuesta original.
X-Flow-Id: <id> (opcional)TodosUn correlation ID. Si mandás uno, Quetzal lo registra en cada evento del request — útil para tracing entre sistemas.

Scopes del token

El bearer token lleva scopes. Cada endpoint requiere uno o más de:

ScopeHabilita
invoicing.document.readGET /v1/documents, GET /v1/documents/{id}, GET /v1/documents/{id}/events, GET /v1/documents/{id}/evidence
invoicing.document.writePOST /v1/documents, PATCH /v1/documents/{id}, POST /v1/documents/{id}/cancel, POST /v1/documents/{id}/void
invoicing.party.read / .writeLas operaciones correspondientes bajo /v1/parties
invoicing.issuer_profile.read / .writeLas operaciones correspondientes bajo /v1/issuer_profiles
invoicing.webhook.read / .writeLas operaciones correspondientes bajo /v1/webhook_subscriptions

Un token puede llevar múltiples scopes. Pedí el set más estrecho que necesites.

Cómo obtener un token

Quetzal no emite tokens por sí mismo — depende del identity provider de tu organización. Hablá con tu admin de Quetzal para:

  1. Registrar un cliente OAuth2 (o service account) para tu integración.
  2. Obtener un client_id y client_secret.
  3. Pedir los scopes apropiados para tu workload.

El endpoint exacto del provider y el formato del token dependen del deployment. El contrato runtime es fijo: cualquier JWT compliant con RFC 8725, con los scopes correctos y un claim tenant, va a validar.

Por qué Idempotency-Key es obligatorio en writes

Reintentos de red, redelivery de queues, restarts de workers — todo sistema de producción reintenta. Si cada retry creara un documento nuevo, terminarías con emisiones fiscales duplicadas, lo cual significa declaraciones tributarias duplicadas, lo cual significa una conversación con auditoría que no querés.

Idempotency-Key resuelve esto. Generá un UUID por intención (un documento que querés emitir), incluilo en el request, y:

  • El primer request que llega gana. Quetzal crea el documento y se acuerda del key.
  • Cualquier request posterior con el mismo key — incluso con un body distinto — devuelve el documento original. El body no se re-procesa.
  • El key tiene scope a tu tenant + endpoint. Mandar el mismo key en POST /v1/parties no choca con uno en POST /v1/documents.
  • Los keys se recuerdan por 24 horas. Después se libera el slot.

Siempre generá un UUID fresco por documento nuevo. Reutilizar un key entre documentos distintos te va a devolver el equivocado en silencio.

Errores comunes de autenticación

StatusCuándoQué revisar
401 UnauthorizedFalta el header Authorization, o el token está malformado / vencido / firmado por el issuer equivocado.El claim exp del token. La signing key del lado de Quetzal.
403 ForbiddenEl token es válido pero no lleva el scope que necesita el endpoint.Comparar el claim scope de tu token con la tabla de scopes arriba.
403 Forbidden (tenant mismatch)X-Tenant-Id no matchea el claim tenant del token.El header y el claim tienen que ser el mismo slug.
409 ConflictEl Idempotency-Key se reutiliza con un body distinto.O reutilizás el key con el mismo body exacto (replay intencional), o generás un UUID nuevo.
422 Unprocessable EntityIdempotency-Key no es un UUID válido.Generá uno con uuidgen, crypto.randomUUID(), etc.

Todas las respuestas de error son Problem JSON (RFC 7807) — application/problem+json con un type machine-readable, un title humano, un detail y un instance.

Armándolo todo

Un POST completo se ve así:

POST /v1/documents HTTP/1.1
Host: quetzal-api.mattilda.io
Authorization: Bearer eyJhbGciOiJIUzI1NiI...
X-Tenant-Id: mattilda
Idempotency-Key: 9f8a1c4e-2e6a-4f9c-b3a5-1d4e5f6a7b8c
X-Flow-Id: req-0193a8c2-f4d1
Content-Type: application/json

{ ... }

Configurálo bien una vez en tu cliente HTTP; de ahí en adelante es invisible.

Siguiente

  • Quickstart — probá un request completo de punta a punta.
  • API Reference — cada endpoint con los scopes que requiere.
  • Principios — por qué el contrato se ve así.