Appearance
Exports API
Technical reference for the canonical /exports REST family. Replaces the 28 legacy routes under /export/* and /reports/export/* (deprecated, see migration guide).
Overview
The /exports family splits into two sub-resources based on the resource semantics:
| Sub-resource | Pattern | Usage |
|---|---|---|
/exports | Async, body-discriminated | Export jobs requiring processing (queue a worker, return 202 + Location) |
/exports/blobs/{type} | Sync, path-discriminated | Pre-generated system files (logs, daily, monthly) — streamed inline with 200 |
The type lives in the body for /exports because it is an attribute of the created job. It lives in the path for /exports/blobs/{type} because it represents the file identity (not an attribute of a job).
Canonical endpoints
| Method | Path | Description |
|---|---|---|
GET | /exports | Lists the exports of the authenticated tenant |
POST | /exports | Creates an export job (returns 202 + Location) |
POST | /exports?dryRun=true | Estimates the result without creating state (only type=reports) |
GET | /exports/{id} | Status of an individual export |
GET | /exports/{id}/file | Final file download |
GET | /exports/sources | Available sources for reports |
GET | /exports/blobs/{type} | Sync stream of pre-existing files (logs, daily, monthly) |
GET | /exports/sse | SSE of progress events for the tenant's exports |
Permissions table
| Endpoint | Required permission |
|---|---|
GET /exports | ReadExport + domain permission (ReadReceivables, ReadOrder, ReadChildEntity) |
POST /exports (type=receivables, installments, etc.) | ReadExport + domain permission |
POST /exports (type=reports) | ExportReports |
POST /exports?dryRun=true (type=reports) | ExportReports |
GET /exports/{id} | Permission for the export type + tenant ownership |
GET /exports/{id}/file | Permission for the export type + tenant ownership |
GET /exports/sources | ExportReports |
GET /exports/blobs/{type} | ReadExport + ReadReceivables |
GET /exports/sse | ReadExport + ReadOrder |
GET /exports
Lists the exports of the authenticated tenant. Supports pagination via offset/limit.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
offset | int | 0 | Page offset |
limit | int | 50 | Page size (max 200) |
type | string | — | Filters by export type |
200 OK response
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
}
}curl examples
bash
# List all exports
curl -H "Authorization: Bearer $TOKEN" \
-H "Config-Organization-ID: $ORG_ID" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports"
# List with pagination
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"
# Filter by type
curl -H "Authorization: Bearer $TOKEN" \
-H "Config-Organization-ID: $ORG_ID" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports?type=receivables"
# Next page
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
Creates an export job. The server enqueues the processing and returns 202 Accepted with a Location header pointing to the job resource.
Request body
json
{
"type": "receivables",
"after": "2026-01-01T00:00:00Z",
"before": "2026-04-30T23:59:59Z",
"filters": { }
}For type=reports the body includes extra fields:
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"]
}202 Accepted response
http
HTTP/1.1 202 Accepted
Location: /v1/exports/01H8XYZ...
Content-Type: application/json
{
"status": "success",
"data": {
"orderID": "01H8XYZ..."
}
}curl examples
bash
# Create receivables export
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"
}'
# Create reports export (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"]
}'
# Create debtors export
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"
}'
# Create datacredito export
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
Estimates the result without creating state. Only supported for type=reports in 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"]
}200 OK response:
json
{
"status": "success",
"data": {
"itemCount": 12453,
"overflowed": false,
"softWarn": false
}
}If type != "reports", returns 501 Not Implemented:
json
{
"status": "fail",
"data": {
"code": "DRY_RUN_NOT_SUPPORTED",
"supportedTypes": ["reports"]
}
}GET /exports/{id}
Returns the status of an individual export. {id} is the job's order_id (not the file_id).
200 OK response
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"
}
}
}curl examples
bash
# Status of a specific export
curl -H "Authorization: Bearer $TOKEN" \
-H "Config-Organization-ID: $ORG_ID" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/01H8XYZ..."
# Polling a job (manual loop)
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 when not found (or belongs to another tenant)
curl -i -H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/00000000-...-not-real"
# Wrong tenant returns 404 (not 403 — existence is not leaked)
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
Downloads the export's final file. Returns 200 + file stream when the job is done and has not expired.
200 OK response
http
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="receivables-2026-05-12.csv"
Cache-Control: no-cacheBody: the export's CSV/ZIP.
410 Gone response (expired)
After 30 days the file is purged from 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"
}
}curl examples
bash
# Direct download to file
curl -OJ \
-H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/01H8XYZ.../file"
# Download to stdout (for piping)
curl -H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/01H8XYZ.../file" | head -10
# Verify expiration (410 Gone)
curl -i -H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/01EXPIRED.../file"
# Download with status code check
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 "Download OK"
elif [ "$HTTP_CODE" = "410" ]; then
echo "File expired"
fiGET /exports/sources
Returns the available sources to create an export of type=reports.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
type | string | — | Only reports is implemented today |
200 OK response
json
{
"status": "success",
"data": {
"sources": [
"datacredito-acierta",
"datacredito-cifin",
"transunion-cdv",
"midas"
]
}
}curl examples
bash
# List available sources
curl -H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/sources?type=reports"
# Without sources configured returns empty list
curl -H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/sources?type=reports"
# {"status":"success","data":{"sources":[]}}
# Without ExportReports permission returns 403
curl -i -H "Authorization: Bearer $TOKEN_WITHOUT_PERM" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/sources?type=reports"
# 400 if type is unsupported
curl -i -H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/sources?type=unsupported"GET /exports/blobs/{type}
Sync stream of pre-existing files generated by the system. Supports logs, daily, monthly.
Path parameters
| Parameter | Values | Description |
|---|---|---|
type | logs | daily | monthly | Type of blob to download |
200 OK response
http
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="logs.csv"Body: CSV stream of the blob.
curl examples
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"
# Filters via query (varies by blob type)
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 with progress events for the tenant's exports. Useful for UI updates without polling.
Headers
http
GET /v1/exports/sse HTTP/1.1
Accept: text/event-stream
Authorization: Bearer $TOKEN
Organization-ID: $ORG_IDEvent format
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"}curl examples
bash
# Stream directly to 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 with timeout (10 minutes)
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"
# Filter events by type (with jq, assuming line-delimited JSON events)
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")'
# Automatic reconnection via EventSource (browser)
# The server emits 'retry: 3000' in the stream headersError codes
| Status | Code | When emitted |
|---|---|---|
400 Bad Request | BIND_ERROR, EMPTY_COLUMNS, INVALID_COLUMN, INVALID_STATUS, INVALID_DATE_RANGE, DATE_RANGE_TOO_LARGE, INVALID_FORMAT | Invalid body or filters outside allowed limits |
401 Unauthorized | — | Token missing or invalid |
403 Forbidden | — | User missing the required permission (ReadExport, ExportReports, etc.) |
404 Not Found | NOT_FOUND | Export does not exist or belongs to another tenant |
410 Gone | EXPORT_FILE_EXPIRED | File expired (>30 days after creation) |
429 Too Many Requests | — | Tenant rate limit exceeded |
500 Internal Server Error | — | Unexpected error |
501 Not Implemented | DRY_RUN_NOT_SUPPORTED | dryRun=true for types other than reports |
Error body
json
{
"status": "fail",
"data": {
"error": "Descriptive message",
"code": "STABLE_CODE"
}
}Compliance and retention
- PII handling: exports may contain personal data. Files live in private storage with cross-tenant ownership.
- Retention: 30 days from the job's
finishedAt. After that the API returns410 Goneand the file is purged. - Ownership: an export created by tenant T1 is not accessible by T2 (
404 Not Found, not403, to avoid leaking existence). - Audit: downloads are logged with
entity_id,user_id,order_id,file_id,ip,user_agent.
Legacy routes (deprecated)
The 28 routes under /export/* and /reports/export/* still work with headers Deprecation: true and Sunset: Sat, 01 Aug 2026 00:00:00 GMT. See the migration guide for the complete mapping.