Appearance
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-recurso | Patrón | Uso |
|---|---|---|
/exports | Async, body-discriminado | Jobs de export que requieren procesamiento (encolan worker, retornan 202 + Location) |
/exports/blobs/{type} | Sync, path-discriminado | Archivos 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étodo | Ruta | Descripción |
|---|---|---|
GET | /exports | Lista los exports del tenant autenticado |
POST | /exports | Crea un job de export (retorna 202 + Location) |
POST | /exports?dryRun=true | Estima el resultado sin crear estado (solo type=reports) |
GET | /exports/{id} | Estado de un export individual |
GET | /exports/{id}/file | Descarga del archivo final |
GET | /exports/sources | Sources disponibles para reports |
GET | /exports/blobs/{type} | Stream sync de archivos pre-existentes (logs, daily, monthly) |
GET | /exports/sse | SSE de eventos de progreso de los exports del tenant |
Tabla de permisos
| Endpoint | Permiso requerido |
|---|---|
GET /exports | ReadExport + 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}/file | Permiso del tipo de export + ownership de tenant |
GET /exports/sources | ExportReports |
GET /exports/blobs/{type} | ReadExport + ReadReceivables |
GET /exports/sse | ReadExport + ReadOrder |
GET /exports
Lista los exports del tenant autenticado. Soporta paginación por offset/limit.
Parámetros de query
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
offset | int | 0 | Desplazamiento de la página |
limit | int | 50 | Tamaño de página (máximo 200) |
type | string | — | Filtra 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-cacheCuerpo: 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"
fiGET /exports/sources
Retorna las sources disponibles para crear un export de type=reports.
Parámetros de query
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
type | string | — | Solo 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ámetro | Valores | Descripción |
|---|---|---|
type | logs | daily | monthly | Tipo 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_IDFormato 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 streamCódigos de error
| Status | Code | Cuándo se emite |
|---|---|---|
400 Bad Request | BIND_ERROR, EMPTY_COLUMNS, INVALID_COLUMN, INVALID_STATUS, INVALID_DATE_RANGE, DATE_RANGE_TOO_LARGE, INVALID_FORMAT | Body inválido o filtros fuera de los límites permitidos |
401 Unauthorized | — | Token ausente o inválido |
403 Forbidden | — | Usuario sin el permiso requerido (ReadExport, ExportReports, etc.) |
404 Not Found | NOT_FOUND | Export no existe o pertenece a otro tenant |
410 Gone | EXPORT_FILE_EXPIRED | Archivo expirado (>30 días después de creado) |
429 Too Many Requests | — | Rate limit del tenant excedido |
500 Internal Server Error | — | Error inesperado |
501 Not Implemented | DRY_RUN_NOT_SUPPORTED | dryRun=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
finishedAtdel job. Después se devuelve410 Goney el archivo se purga. - Ownership: un export creado por el tenant T1 no es accesible por T2 (
404 Not Found, no403, 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.