CSV Exports
Export your transaction history as CSV from the Reporting API (https://api.tensorrail.com, bearer-token auth).
There are two paths:
- Synchronous:
GET /v1/exports/transactions.csvstreams the file directly, for windows of ≤ 31 days and ≤ 10,000 rows. - Asynchronous:
POST /v1/exportsenqueues a job for anything larger; poll its status, then download the finished file. Files are retained for 7 days.
Scope: exports need payments:read.
Synchronous export
GET /v1/exports/transactions.csv. This /v1 surface is gateway-built, not generated from the engine's schema, so the reference operation carries no parameter list of its own; the parameters are documented here.
Query parameters
| Parameter | Required | Description |
|---|---|---|
start | Yes | First day, YYYY-MM-DD (UTC, inclusive) |
end | Yes | Last day, YYYY-MM-DD (UTC, inclusive) |
payment_status | No | Filter by payment status |
q | No | Free-text search |
curl -o transactions_july.csv \
"https://api.tensorrail.com/v1/exports/transactions.csv?start=2026-07-01&end=2026-07-31" \
-H "Authorization: Bearer rail_full_live_xxx"
The response streams as text/csv with a Content-Disposition: attachment filename.
When the window is too large the endpoint returns 413 with a Retry-With: POST /v1/exports header and a structured body. Switch to the async path:
{
"error": {
"code": "ERR_EXPORT_SYNC_TOO_LARGE",
"message": "date window is 62 days; the synchronous export allows ≤31 days. Use POST /v1/exports for larger windows.",
"retry_with": "POST /v1/exports"
}
}
Asynchronous export jobs
1. Enqueue
Body: same filters as the sync query string, plus the dataset kind:
| Field | Required | Description |
|---|---|---|
kind | Yes | transactions |
start / end | Yes | YYYY-MM-DD (UTC, inclusive) |
payment_status, q | No | Filters, as above |
curl -X POST "https://api.tensorrail.com/v1/exports" \
-H "Authorization: Bearer rail_full_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "kind": "transactions", "start": "2026-01-01", "end": "2026-06-30" }'
Response 202 Accepted
{
"job_id": "exp_9f2a…",
"kind": "transactions",
"filters": { "start": "2026-01-01", "end": "2026-06-30" },
"status": "queued",
"row_count": 0,
"file_size_bytes": 0,
"created_at": "2026-08-03T10:00:00Z"
}
2. Poll
Returns the same job object. status moves queued → running → done (or failed, with error_message). When done, row_count and file_size_bytes describe the rendered file, and started_at / completed_at are set. Jobs are typically picked up within seconds.
3. Download
GET /v1/exports/{job_id}/download
Streams the CSV when status is done.
| Status | Meaning |
|---|---|
200 | The CSV file |
409 | The job is not done yet (body says the current status) |
404 | No such job on your account |
410 | The file has expired past the 7-day retention and is gone |
curl -o transactions_h1.csv \
"https://api.tensorrail.com/v1/exports/exp_9f2a…/download" \
-H "Authorization: Bearer rail_full_live_xxx"
Retention
Finished export files (and their job records) are deleted 7 days after creation. Exports can contain customer PII (the transactions CSV includes the customer email column), so they are not kept around. Download what you need within the window; after that, enqueue a fresh job.
Columns
The transactions export carries: payment_id, payment_attempt_id, created_at, amount_minor, currency, payment_status, transaction_type, connector, psp_transaction_id, payment_method, payment_method_type, commission_earned_minor, interchange_amount_minor, scheme_fee_amount_minor, acquirer_markup_amount_minor, fee_status, error_code, error_message, psp_error_code, psp_decline_code, retry_attempt, description, email, live_mode, network_transaction_id, refund_status, refunded_amount_minor, reconcile_key.
Changed 2026-09-09: the settlement_status column and its matching filter were removed. The value was fixed at pending on every row from the moment the payment was written and nothing ever changed it, so it could not tell you whether anything had settled. Sending settlement_status to the transactions CSV or to the async POST /v1/exports now returns 400 rather than being ignored. Every column that used to follow it has moved one position left.
The last five are appended at the end of the row, so a parser that reads by column position keeps working. Three of them matter for reconciliation: live_mode says which environment the row belongs to (an empty value means the row predates mode stamping, which is not the same statement as "test"), network_transaction_id is the reference that appears on your bank statement (the UTR for a UPI collection, the scheme network id for a card), and reconcile_key is the column to join on.
Added 2026-09-19: reconcile_key. payment_attempt_id in column two identifies one attempt at a payment and is the strongest key available, but it is empty on rows written before 19 September 2026 and cannot be filled in afterwards. reconcile_key gives you one column that works across that date: it is the attempt id where the row has one and the payment id where it does not. Join on it rather than on payment_attempt_id, or a query written today silently matches none of your earlier activity.
Amounts are in minor units throughout; timestamps are UTC RFC 3339.
Related
- Reporting API: base URL and auth
- Authentication: the scope table
- Balances: the balance these transactions settle into
- Understanding amounts: minor units