Saltar al contenido principal

POST /v1/invoices

Emite un e-CF firmado y lo envía a DGII de forma asíncrona.

Request

curl -X POST https://sandbox.api.erply.pro/v1/invoices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @invoice.json

Headers obligatorios

HeaderNotas
AuthorizationBearer <jwt> obtenido en /v1/auth/token.
Idempotency-KeyUUID v4. TTL 24 h.
Content-Typeapplication/json; charset=utf-8.

Body

El cuerpo canónico vive en tests/fixtures/quickstart_invoice_request.json y se valida contra el modelo InvoicePayload en cada commit (ver tests/unit/test_quickstart_fixture.py):

{
"type": "31",
"encf": "E310000000001",
"client_request_id": "req-2026-05-01-001",
"company": { "rnc": "131000001", "name": "ERPly Pro Demo S.R.L." },
"customer": { "rnc": "131000002", "name": "Cliente Demo S.R.L." },
"lines": [
{
"line_number": 1,
"description": "Servicio de consultoria",
"quantity": "1",
"unit_price": "5000.00",
"subtotal": "5000.00"
}
],
"totals": { "subtotal": "5000.00", "tax": "900.00", "grand_total": "5900.00" }
}

Notas sobre el contrato

  • tenantId no va en el cuerpo. El handler lo deriva de las claims del JWT (tenant_id, environment) vía TenantContext.from_event. BUG-EP-03 corrigió la deriva entre el OpenAPI publicado y la implementación: el campo nunca estuvo en el cuerpo aceptado por el Lambda.
  • Decimales como string. quantity, unit_price, subtotal, totals.* se aceptan como string para evitar errores de redondeo en el cliente; el servidor valida que Σ lines[].subtotal == totals.subtotal ± 0.01 y que totals.subtotal + totals.tax == totals.grand_total.
  • client_request_id (8–64 chars) es la clave de idempotencia a nivel de cuerpo; el header Idempotency-Key cubre el reintento de red. Ambos coexisten — ver errores §replay.
  • encf y type deben corresponder. El segundo y tercer carácter de encf (E**...) deben coincidir con type.

Respuestas

HTTPSignificado
202 AcceptedAceptado, en cola hacia DGII.
200 OKReplay idempotente.
{
"docId": "01HW9X4G…",
"trackId": "20260501-DGII-9988",
"status": "pending",
"_links": {
"self": "/v1/invoices/01HW9X4G…",
"status": "/v1/invoices/01HW9X4G…/status"
}
}

Errores posibles

HTTPSlugCausa
400malformed-jsonBody no es JSON.
400unsupported-ecf-typeTipo fuera del catálogo.
400validation-errorPydantic falló.
401unauthorisedToken inválido.
422mathematical-discrepancyTotales no cuadran.
422tenant-config-missingFalta RNC/PFX.
422dgii-rejected (+ código DGII)Rechazo DGII.
504dgii-unavailableTimeout DGII.

Consulta el diccionario completo de errores DGII para los códigos numéricos.