Appearance
Bulk reports export
This page describes the bulk reports export feature (type=reports) from the Analyses section. The canonical endpoint is POST /exports with body {type: "reports"}. The legacy /reports/export/* routes still work with deprecation headers until sunset (see migration guide).
User flow
- Navigate to
/analysesfrom the side menu. - Click Export queries (visible only with
ExportReportspermission). - The Export queries modal opens.
- Fill the form and review the estimate.
- Click Export to queue the job.
- The page navigates to
/analyses/exportswhere the job appears with live status via SSE. - When the job is
done, download the ZIP from the list.
Modal form
Date range
- From (
after): inclusive start date. Cannot be in the future. - To (
before): inclusive end date. Must be >=after. - Maximum: 365 days between
afterandbefore. Exceeding returnsDATE_RANGE_TOO_LARGE.
Sources (sourceKeys)
List of bureau identifiers to include. Examples: datacredito-acierta, datacredito-cifin, transunion-cdv, midas.
- Default: all sources checked (when opening the modal without a previous selection).
- Persistence: the last selection is saved in
sessionStorage(reports-export-last-sources). - UX for long lists (>10 sources):
- Select all / Deselect all buttons in the header.
- Search bar (
ion-searchbar) that filters by name/key without losing selection.
- Leaving all unchecked disables the Export button with the message "Select at least one source".
- If
sourceKeysis empty in the API request, it is interpreted as "all sources" (no filter).
Statuses (statuses)
Report statuses to include (whitelist):
donefailedrunningretryinitialunverifiable
Empty means "all statuses".
Format (format)
| Value | Output |
|---|---|
consolidated | A single reports.csv with all selected columns in each row |
separated | A manifest.csv with metadata + payloads-crudos/ and/or payloads-parseados/ folders with individual payloads as files |
Columns (columns)
Closed whitelist of columns. At least one must be selected (EMPTY_COLUMNS otherwise):
| Key | Description | Source |
|---|---|---|
date | Report creation date | report.created_at |
name | Source key | job parameter |
idType | Subject's document type | parsed from report.input |
idNumber | Document number | parsed from report.input |
firstName | First names | parsed from report.input |
lastName | Last names | parsed from report.input |
status | Report status | enum |
error | Error message if failed | report.error |
retries | Retry count | report.retries |
durationMs | Duration in ms | report.finished_in_ms |
id | Report ID | report.id |
rawPayload | Raw bureau payload (XML/JSON) | report.Response |
parsedPayload | Parsed payload (JSON) | report.Output |
rawPayload and parsedPayload are exported as escaped cells when format=consolidated, or as individual files in payloads-* folders when format=separated.
Pre-flight estimate (dry-run)
Before queuing the job, the modal makes an estimate call to show:
itemCount: estimated count of reports matching the filters.overflowed:trueif the count exceeds the tenant hard-cap — in that case the Export button is disabled.softWarn:trueif the count exceeds the soft-warn (typically 100k). The modal shows a warning but allows continuation.
Endpoint: POST /exports?dryRun=true body {type: "reports", ...}. Creates no state.
Required permissions
ExportReportsfor all operations (open modal, create, estimate, list sources, download).- The Export queries button is hidden if the user lacks the permission.
- Downloads validate cross-tenant ownership: a tenant T1 file is not accessible by T2 (
404 Not Found).
ZIP download format
format=consolidated
reports-export-2026-05-12.zip
+- reports.csvreports.csv contains one row per report with the selected columns. UTF-8 encoding without BOM.
format=separated
reports-export-2026-05-12.zip
+- manifest.csv
+- payloads-crudos/
| +- datacredito-acierta/
| | +- 01H8XYZ.xml
| | +- 01H8ABC.xml
| +- transunion-cdv/
| +- 01H8DEF.json
+- payloads-parseados/
+- datacredito-acierta/
| +- 01H8XYZ.json
+- transunion-cdv/
+- 01H8DEF.jsonmanifest.csv acts as an index and includes the selected metadata columns (without rawPayload/parsedPayload). The payloads-crudos/ and payloads-parseados/ folders appear only if the corresponding columns are checked.
Payload file names
- Raw:
<source>/<reportID>.<ext>where<ext>isxmlorjsondepending on the bureau's native format. - Parsed:
<source>/<reportID>.json.
Compliance and PII
- Payloads contain PII: names, document numbers, scores, debts.
- Access restricted by the
ExportReportspermission (notReadAnalysis). - Retention: 30 days from the job's
finishedAt. - After expiration,
GET /exports/{id}/file(and the legacy alias/reports/export/download/:fileid) return410 Gonewithcode: "EXPORT_FILE_EXPIRED". - Each download is logged (entity_id, user_id, order_id, file_id, ip, user_agent).
Error codes
| Status | Code | Cause |
|---|---|---|
400 | EMPTY_COLUMNS | The columns array is empty |
400 | INVALID_COLUMN | A column is not in the whitelist |
400 | INVALID_STATUS | A status is not in the whitelist |
400 | INVALID_DATE_RANGE | after or before empty, or before < after |
400 | DATE_RANGE_TOO_LARGE | before - after > 365 days |
400 | INVALID_FORMAT | format is neither consolidated nor separated |
403 | — | User without ExportReports permission |
404 | — | Order does not exist or belongs to another tenant |
410 | EXPORT_FILE_EXPIRED | File expired (>30 days) |
429 | — | Tenant rate limit exceeded |
curl examples
bash
# Pre-flight estimate
curl -X POST "https://api.kuenta.co/v1/exports?dryRun=true" \
-H "Authorization: Bearer $TOKEN" \
-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 export
curl -X POST "https://api.kuenta.co/v1/exports" \
-H "Authorization: Bearer $TOKEN" \
-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",
"sourceKeys": ["datacredito-acierta"],
"statuses": ["done"],
"format": "separated",
"columns": ["date", "name", "idNumber", "status", "rawPayload", "parsedPayload"]
}'
# Available sources
curl -H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/sources?type=reports"
# Download
curl -OJ \
-H "Authorization: Bearer $TOKEN" \
-H "Organization-ID: $ORG_ID" \
"https://api.kuenta.co/v1/exports/01H8XYZ/file"Operational notes
- The job runs in an async worker; typical times: 30s-2min for <50k reports.
- Status updates via SSE on
GET /exports/sse; the frontend listens and updates the list without polling. - If the job fails,
status="failed"anderrordescribes the cause. You can retry by creating a new export.