Skip to content

API de Exports

Referencia técnica de la familia REST canónica /exports. Reemplaza a las 28 rutas legacy bajo /export/* y /reports/export/* (deprecadas, ver guía de migración).

Visión general

La familia /exports se divide en dos sub-recursos por la semántica del recurso:

Sub-recursoPatrónUso
/exportsAsync, body-discriminadoJobs de export que requieren procesamiento (encolan worker, retornan 202 + Location)
/exports/blobs/{type}Sync, path-discriminadoArchivos pre-generados por el sistema (logs, daily, monthly) — se stream inline con 200

El type vive en el body para /exports porque es un atributo del job creado. Vive en el path para /exports/blobs/{type} porque representa la identidad del archivo (no un atributo de un job).

Endpoints canónicos

MétodoRutaDescripción
GET/exportsLista los exports del tenant autenticado
POST/exportsCrea un job de export (retorna 202 + Location)
POST/exports?dryRun=trueEstima el resultado sin crear estado (solo type=reports)
GET/exports/{id}Estado de un export individual
GET/exports/{id}/fileDescarga del archivo final
GET/exports/sourcesSources disponibles para reports
GET/exports/blobs/{type}Stream sync de archivos pre-existentes (logs, daily, monthly)
GET/exports/sseSSE de eventos de progreso de los exports del tenant

Tabla de permisos

EndpointPermiso requerido
GET /exportsReadExport + permiso por dominio (ReadReceivables, ReadOrder, ReadChildEntity)
POST /exports (type=receivables, installments, etc.)ReadExport + permiso por dominio
POST /exports (type=reports)ExportReports
POST /exports?dryRun=true (type=reports)ExportReports
GET /exports/{id}Permiso del tipo de export + ownership de tenant
GET /exports/{id}/filePermiso del tipo de export + ownership de tenant
GET /exports/sourcesExportReports
GET /exports/blobs/{type}ReadExport + ReadReceivables
GET /exports/sseReadExport + ReadOrder

GET /exports

Lista los exports del tenant autenticado. Soporta paginación por offset/limit.

Parámetros de query

ParámetroTipoDefaultDescripción
offsetint0Desplazamiento de la página
limitint50Tamaño de página (máximo 200)
typestringFiltra por tipo de export

Respuesta 200 OK

json
{
  "status": "success",
  "data": {
    "exports": [
      {
        "id": "01H8...",
        "type": "receivables",
        "status": "done",
        "createdAt": "2026-05-12T14:30:00Z",
        "finishedAt": "2026-05-12T14:31:42Z",
        "expiresAt": "2026-06-11T14:31:42Z",
        "fileID": "9c2...",
        "filename": "receivables-2026-05-12.csv"
      }
    ],
    "total": 134
  }
}

Ejemplos curl

bash
# Lista todos los exports
curl -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports"

# Lista con paginación
curl -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports?offset=0&limit=20"

# Filtra por tipo
curl -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports?type=receivables"

# Página siguiente
curl -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports?offset=20&limit=20"

POST /exports

Crea un export job. El servidor encola el procesamiento y retorna 202 Accepted con Location apuntando al recurso del job.

Cuerpo de la petición

json
{
  "type": "receivables",
  "after": "2026-01-01T00:00:00Z",
  "before": "2026-04-30T23:59:59Z",
  "filters": { }
}

Para type=reports el body incluye campos extra:

json
{
  "type": "reports",
  "after": "2026-01-01T00:00:00Z",
  "before": "2026-04-30T23:59:59Z",
  "sourceKeys": ["datacredito-acierta", "transunion-cdv"],
  "statuses": ["done", "failed"],
  "format": "consolidated",
  "columns": ["date", "name", "idNumber", "status", "parsedPayload"]
}

Respuesta 202 Accepted

http
HTTP/1.1 202 Accepted
Location: /v1/exports/01H8XYZ...
Content-Type: application/json

{
  "status": "success",
  "data": {
    "orderID": "01H8XYZ..."
  }
}

Ejemplos curl

bash
# Crea export de receivables
curl -X POST "https://api.kuenta.co/v1/exports" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     -H "Content-Type: application/json" \
     -d '{
       "type": "receivables",
       "after": "2026-01-01T00:00:00Z",
       "before": "2026-04-30T23:59:59Z"
     }'

# Crea export de reports (bulk)
curl -X POST "https://api.kuenta.co/v1/exports" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     -H "Content-Type: application/json" \
     -d '{
       "type": "reports",
       "after": "2026-01-01T00:00:00Z",
       "before": "2026-04-30T23:59:59Z",
       "format": "consolidated",
       "columns": ["date", "name", "idNumber", "status"]
     }'

# Crea export de debtors
curl -X POST "https://api.kuenta.co/v1/exports" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     -H "Content-Type: application/json" \
     -d '{
       "type": "debtors",
       "after": "2026-01-01T00:00:00Z",
       "before": "2026-04-30T23:59:59Z"
     }'

# Crea export de datacredito
curl -X POST "https://api.kuenta.co/v1/exports" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     -H "Content-Type: application/json" \
     -d '{
       "type": "datacredito",
       "after": "2026-01-01T00:00:00Z",
       "before": "2026-04-30T23:59:59Z"
     }'

POST /exports?dryRun=true

Estima el resultado sin crear estado. Solo soportado para type=reports en MVP.

http
POST /v1/exports?dryRun=true
Content-Type: application/json

{
  "type": "reports",
  "after": "2026-01-01T00:00:00Z",
  "before": "2026-04-30T23:59:59Z",
  "format": "consolidated",
  "columns": ["date", "name", "status"]
}

Respuesta 200 OK:

json
{
  "status": "success",
  "data": {
    "itemCount": 12453,
    "overflowed": false,
    "softWarn": false
  }
}

Si type != "reports", retorna 501 Not Implemented:

json
{
  "status": "fail",
  "data": {
    "code": "DRY_RUN_NOT_SUPPORTED",
    "supportedTypes": ["reports"]
  }
}

GET /exports/{id}

Retorna el estado de un export individual. {id} es el order_id del job (no el file_id).

Respuesta 200 OK

json
{
  "status": "success",
  "data": {
    "id": "01H8XYZ...",
    "type": "reports",
    "status": "done",
    "createdAt": "2026-05-12T14:30:00Z",
    "finishedAt": "2026-05-12T14:31:42Z",
    "expiresAt": "2026-06-11T14:31:42Z",
    "fileID": "9c2...",
    "filename": "reports-export.zip",
    "filters": {
      "after": "2026-01-01T00:00:00Z",
      "before": "2026-04-30T23:59:59Z"
    }
  }
}

Ejemplos curl

bash
# Estado de un export específico
curl -H "Authorization: Bearer $TOKEN" \
     -H "Config-Organization-ID: $ORG_ID" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/01H8XYZ..."

# Polling de un job (loop manual)
while true; do
  STATUS=$(curl -s -H "Authorization: Bearer $TOKEN" \
                -H "Organization-ID: $ORG_ID" \
                "https://api.kuenta.co/v1/exports/01H8XYZ..." | jq -r '.data.status')
  echo "Status: $STATUS"
  [ "$STATUS" = "done" ] && break
  sleep 5
done

# 404 cuando no existe (o pertenece a otro tenant)
curl -i -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/00000000-...-not-real"

# Con tenant equivocado retorna 404 (no 403 — no se filtra existencia)
curl -i -H "Authorization: Bearer $WRONG_TENANT_TOKEN" \
     -H "Organization-ID: $WRONG_ORG_ID" \
     "https://api.kuenta.co/v1/exports/01H8XYZ..."

GET /exports/{id}/file

Descarga el archivo final del export. Retorna 200 + file stream cuando el job está done y no ha expirado.

Respuesta 200 OK

http
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="receivables-2026-05-12.csv"
Cache-Control: no-cache

Cuerpo: el CSV/ZIP del export.

Respuesta 410 Gone (expirado)

Después de 30 días el archivo se purga del storage:

json
{
  "status": "fail",
  "data": {
    "error": "El archivo expiró el 2026-06-11 y ya no está disponible para descarga.",
    "code": "EXPORT_FILE_EXPIRED",
    "expiredAt": "2026-06-11T14:31:42Z"
  }
}

Ejemplos curl

bash
# Descarga directa a archivo
curl -OJ \
     -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/01H8XYZ.../file"

# Descarga a stdout (para pipe)
curl -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/01H8XYZ.../file" | head -10

# Verifica expiración (410 Gone)
curl -i -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/01EXPIRED.../file"

# Descarga con verificación de status code
HTTP_CODE=$(curl -s -o report.csv -w "%{http_code}" \
                -H "Authorization: Bearer $TOKEN" \
                -H "Organization-ID: $ORG_ID" \
                "https://api.kuenta.co/v1/exports/01H8XYZ.../file")
if [ "$HTTP_CODE" = "200" ]; then
  echo "Descarga OK"
elif [ "$HTTP_CODE" = "410" ]; then
  echo "Archivo expirado"
fi

GET /exports/sources

Retorna las sources disponibles para crear un export de type=reports.

Parámetros de query

ParámetroTipoDefaultDescripción
typestringSolo reports está implementado hoy

Respuesta 200 OK

json
{
  "status": "success",
  "data": {
    "sources": [
      "datacredito-acierta",
      "datacredito-cifin",
      "transunion-cdv",
      "midas"
    ]
  }
}

Ejemplos curl

bash
# Lista sources disponibles
curl -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/sources?type=reports"

# Sin sources configuradas retorna lista vacía
curl -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/sources?type=reports"
# {"status":"success","data":{"sources":[]}}

# Sin permiso ExportReports retorna 403
curl -i -H "Authorization: Bearer $TOKEN_SIN_PERMISO" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/sources?type=reports"

# 400 si type no soportado
curl -i -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/sources?type=unsupported"

GET /exports/blobs/{type}

Stream sync de archivos pre-existentes generados por el sistema. Soporta logs, daily, monthly.

Path parameters

ParámetroValoresDescripción
typelogs | daily | monthlyTipo de blob a descargar

Respuesta 200 OK

http
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="logs.csv"

Cuerpo: stream CSV del blob.

Ejemplos curl

bash
# Logs
curl -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/blobs/logs"

# Daily
curl -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/blobs/daily"

# Monthly
curl -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/blobs/monthly"

# Filtros por query (varía por tipo de blob)
curl -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     "https://api.kuenta.co/v1/exports/blobs/daily?date=2026-05-01"

GET /exports/sse

Server-Sent Events stream con eventos del progreso de los exports del tenant. Útil para actualizar UIs sin polling.

Headers

http
GET /v1/exports/sse HTTP/1.1
Accept: text/event-stream
Authorization: Bearer $TOKEN
Organization-ID: $ORG_ID

Formato del evento

event: export.updated
data: {"id":"01H8XYZ...","type":"reports","status":"done","finishedAt":"2026-05-12T14:31:42Z"}

event: export.created
data: {"id":"01H8ABC...","type":"receivables","status":"running","createdAt":"2026-05-12T14:32:00Z"}

Ejemplos curl

bash
# Stream directo a stdout
curl -N -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     -H "Accept: text/event-stream" \
     "https://api.kuenta.co/v1/exports/sse"

# Stream con timeout (10 minutos)
curl -N --max-time 600 \
     -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     -H "Accept: text/event-stream" \
     "https://api.kuenta.co/v1/exports/sse"

# Filtrar eventos por tipo (con jq, asumiendo eventos JSON line)
curl -N -H "Authorization: Bearer $TOKEN" \
     -H "Organization-ID: $ORG_ID" \
     -H "Accept: text/event-stream" \
     "https://api.kuenta.co/v1/exports/sse" | \
     grep '^data:' | sed 's/^data: //' | \
     jq 'select(.type == "reports")'

# Reconexión automática vía EventSource (browser)
# El servidor envía 'retry: 3000' en los headers del stream

Códigos de error

StatusCodeCuándo se emite
400 Bad RequestBIND_ERROR, EMPTY_COLUMNS, INVALID_COLUMN, INVALID_STATUS, INVALID_DATE_RANGE, DATE_RANGE_TOO_LARGE, INVALID_FORMATBody inválido o filtros fuera de los límites permitidos
401 UnauthorizedToken ausente o inválido
403 ForbiddenUsuario sin el permiso requerido (ReadExport, ExportReports, etc.)
404 Not FoundNOT_FOUNDExport no existe o pertenece a otro tenant
410 GoneEXPORT_FILE_EXPIREDArchivo expirado (>30 días después de creado)
429 Too Many RequestsRate limit del tenant excedido
500 Internal Server ErrorError inesperado
501 Not ImplementedDRY_RUN_NOT_SUPPORTEDdryRun=true para tipos diferentes de reports

Body de error

json
{
  "status": "fail",
  "data": {
    "error": "Mensaje descriptivo",
    "code": "CODIGO_ESTABLE"
  }
}

Compliance y retención

  • PII handling: los exports pueden contener datos personales. Los archivos viven en storage privado con ownership cross-tenant.
  • Retención: 30 días desde el finishedAt del job. Después se devuelve 410 Gone y el archivo se purga.
  • Ownership: un export creado por el tenant T1 no es accesible por T2 (404 Not Found, no 403, para no filtrar existencia).
  • Auditoría: descargas se loguean con entity_id, user_id, order_id, file_id, ip, user_agent.

Rutas legacy (deprecated)

Las 28 rutas bajo /export/* y /reports/export/* siguen funcionando con headers Deprecation: true y Sunset: Sat, 01 Aug 2026 00:00:00 GMT. Ver la guía de migración para el mapping completo.

Próximos pasos