Skip to content

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-resourcePatternUsage
/exportsAsync, body-discriminatedExport jobs requiring processing (queue a worker, return 202 + Location)
/exports/blobs/{type}Sync, path-discriminatedPre-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

MethodPathDescription
GET/exportsLists the exports of the authenticated tenant
POST/exportsCreates an export job (returns 202 + Location)
POST/exports?dryRun=trueEstimates the result without creating state (only type=reports)
GET/exports/{id}Status of an individual export
GET/exports/{id}/fileFinal file download
GET/exports/sourcesAvailable sources for reports
GET/exports/blobs/{type}Sync stream of pre-existing files (logs, daily, monthly)
GET/exports/sseSSE of progress events for the tenant's exports

Permissions table

EndpointRequired permission
GET /exportsReadExport + 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}/filePermission for the export type + tenant ownership
GET /exports/sourcesExportReports
GET /exports/blobs/{type}ReadExport + ReadReceivables
GET /exports/sseReadExport + ReadOrder

GET /exports

Lists the exports of the authenticated tenant. Supports pagination via offset/limit.

Query parameters

ParameterTypeDefaultDescription
offsetint0Page offset
limitint50Page size (max 200)
typestringFilters 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-cache

Body: 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"
fi

GET /exports/sources

Returns the available sources to create an export of type=reports.

Query parameters

ParameterTypeDefaultDescription
typestringOnly 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

ParameterValuesDescription
typelogs | daily | monthlyType 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_ID

Event 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 headers

Error codes

StatusCodeWhen emitted
400 Bad RequestBIND_ERROR, EMPTY_COLUMNS, INVALID_COLUMN, INVALID_STATUS, INVALID_DATE_RANGE, DATE_RANGE_TOO_LARGE, INVALID_FORMATInvalid body or filters outside allowed limits
401 UnauthorizedToken missing or invalid
403 ForbiddenUser missing the required permission (ReadExport, ExportReports, etc.)
404 Not FoundNOT_FOUNDExport does not exist or belongs to another tenant
410 GoneEXPORT_FILE_EXPIREDFile expired (>30 days after creation)
429 Too Many RequestsTenant rate limit exceeded
500 Internal Server ErrorUnexpected error
501 Not ImplementedDRY_RUN_NOT_SUPPORTEDdryRun=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 returns 410 Gone and the file is purged.
  • Ownership: an export created by tenant T1 is not accessible by T2 (404 Not Found, not 403, 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.

Next steps