Skip to main content

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.csv streams the file directly, for windows of ≤ 31 days and ≤ 10,000 rows.
  • Asynchronous: POST /v1/exports enqueues 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

ParameterRequiredDescription
startYesFirst day, YYYY-MM-DD (UTC, inclusive)
endYesLast day, YYYY-MM-DD (UTC, inclusive)
payment_statusNoFilter by payment status
qNoFree-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​

POST /v1/exports

Body: same filters as the sync query string, plus the dataset kind:

FieldRequiredDescription
kindYestransactions
start / endYesYYYY-MM-DD (UTC, inclusive)
payment_status, qNoFilters, 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​

GET /v1/exports/{job_id}

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.

StatusMeaning
200The CSV file
409The job is not done yet (body says the current status)
404No such job on your account
410The 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.