Transaction Reports (S2S)
Programmatically export card, APM, payout and refund reports as CSV or XLS
Export transaction reports for a date range as CSV or XLS, backend-to-backend. This is the server-to-server equivalent of the Export Transactions feature in the Exirom Merchant Portal. Use it to automate reconciliation, accounting and other downstream integrations on a schedule instead of exporting by hand.
Reports are generated asynchronously:
POST /api/v1/report-generator/s2s/async— submit a report request and receive itsid.GET /api/v1/report-generator/s2s/async/{id}— poll untilstatusisREADY(orFAILED).GET /api/v1/report-generator/s2s/async/{id}/download-url— get a temporary download link for the file.
Base URL. Contact your Exirom account manager for the correct sandbox or production base URL. The examples use the sandbox host
https://sandbox.api.exirom.com/api.
Deprecated:
POST /api/v1/report-generator/s2s. The synchronous endpoint holds the connection open until the file is ready and then streams it. It keeps working and accepts the same request body, but it is deprecated and will be removed. Use the async endpoints for all new integrations — see Synchronous endpoint (deprecated).
#Authentication
Every report endpoint uses merchant key/secret headers — not the Bearer token used by the rest of the API. Keep the secret confidential; it is for backend use only.
| Header | Required | Description |
|---|---|---|
x-merchant-key | Yes | Your public merchant API key |
x-merchant-secret | Yes | Your private merchant API secret |
Content-Type | Yes (POST) | Must be application/json |
A report is visible only to the merchant that created it. Polling or downloading another merchant's report ID returns 404.
#IP whitelisting
Report endpoints accept requests only from whitelisted IP addresses. Before your first call, send the public IP addresses of the servers that will request reports to your Exirom account manager, separately for sandbox and production. Requests from any other IP are rejected, even with valid credentials. Tell your account manager before your outbound IPs change.
#1. Submit a report
POST https://sandbox.api.exirom.com/api/v1/report-generator/s2s/async
#Request body
| Field | Type | Required | Description |
|---|---|---|---|
dateFrom | string | Yes | Start of range: YYYY-MM-DD HH:MM:SS, or YYYY-MM-DD for 00:00:00 of that day |
dateTo | string | Yes | End of range, inclusive. Same format. YYYY-MM-DD means 00:00:00 of that day, so add 23:59:59 to include the whole day. The range can span at most 10 days. Any other format is rejected with 400 |
sourceType | string | No | Which records to export (see Report types). Default CARD_PAYMENT. Any other value is rejected with 400 |
dateSource | string | No | Which timestamp the range filters on: createdDate (default) or lastModifiedDate |
timezone | string | No | Timezone of dateFrom/dateTo and of the platform timestamps in the file (created and last modified date; orderDate is shown as sent). IANA name (UTC, Europe/London, Asia/Dubai) or offset (+05:00). Default: platform time, Europe/Nicosia. Case-sensitive: an unrecognised value (utc, PST) is rejected with 400 |
format | string | No | CSV (default) or XLS |
#Report types
sourceType | Contents |
|---|---|
CARD_PAYMENT | Card payments (default) |
CARD_PAYOUT | Card payouts |
CARD_REFUND | Card refunds |
APM_PAYMENT | Alternative payment method payments |
APM_PAYOUT | Alternative payment method payouts |
Each report covers one type. To export several types, send a separate request for each.
#Date source
createdDate— rows created in the window. Use it for daily sales exports.lastModifiedDate— rows that changed in the window, such as status updates, captures and late declines. Use it for incremental reconciliation, so you pick up transactions created earlier whose state changed since your last run.
#Timezone
Exirom stores timestamps in platform time (Europe/Nicosia, UTC+2 / UTC+3 in summer). With timezone set, the platform:
- interprets
dateFromanddateToin your timezone, and - renders the platform timestamps in the file (created date, last modified date) in that same timezone.
orderDateis shown exactly as you sent it.
For example, "timezone": "UTC" with "dateFrom": "2026-07-01 00:00:00" and "dateTo": "2026-07-01 23:59:59" returns exactly the UTC day of 1 July, and the file shows UTC times. Without timezone, both the range and the file use Europe/Nicosia.
{
"dateFrom": "2026-07-01 00:00:00",
"dateTo": "2026-07-01 23:59:59",
"sourceType": "APM_PAYMENT",
"dateSource": "lastModifiedDate",
"timezone": "UTC",
"format": "CSV"
}#Response 200 OK
| Field | Type | Description |
|---|---|---|
id | string | Report ID — use it to poll and download |
sourceType | string | Requested report type |
status | string | IN_PROGRESS on creation |
fileName | string | Generated file name |
fileSize | string | null | File size in bytes; null until ready |
createdAt | string | YYYY-MM-DDTHH:MM:SS.sss, in platform time (Europe/Nicosia) whatever timezone is |
updatedAt | string | null | Last status change, in platform time; null until the status changes |
{
"id": "4821",
"sourceType": "APM_PAYMENT",
"status": "IN_PROGRESS",
"fileName": "report_APM_PAYMENT_20260702080000.csv",
"fileSize": null,
"createdAt": "2026-07-02T08:00:00.000",
"updatedAt": null
}#2. Poll the status
GET https://sandbox.api.exirom.com/api/v1/report-generator/s2s/async/{id}
Returns the same object as the submit call. Watch status:
status | Meaning |
|---|---|
IN_PROGRESS | Still generating — poll again |
READY | Done — fetch the download URL |
FAILED | Generation failed — submit a new request |
Poll every 5–10 seconds. Small ranges are usually ready in seconds; large ranges can take a few minutes.
#3. Download the file
GET https://sandbox.api.exirom.com/api/v1/report-generator/s2s/async/{id}/download-url
Call this once status is READY; before that it returns 400. It returns a temporary pre-signed URL that you download with a plain GET (no auth headers needed). The link expires, so download right away and request a new URL if needed.
{ "url": "https://reports.example.com/report_APM_PAYMENT_20260702080000.csv?X-Amz-Signature=..." }#Full example
BASE='https://sandbox.api.exirom.com/api/v1/report-generator/s2s/async'
AUTH=(-H "x-merchant-key: $MERCHANT_KEY" -H "x-merchant-secret: $MERCHANT_SECRET")
set -euo pipefail
# 1. Submit
ID=$(curl -fsS "$BASE" "${AUTH[@]}" -H 'Content-Type: application/json' \
--data '{"dateFrom":"2026-07-01 00:00:00","dateTo":"2026-07-01 23:59:59","sourceType":"CARD_PAYMENT","timezone":"UTC"}' \
| jq -er .id)
# 2. Poll
STATUS=IN_PROGRESS
while [ "$STATUS" = "IN_PROGRESS" ]; do
sleep 5
STATUS=$(curl -fsS "$BASE/$ID" "${AUTH[@]}" | jq -er .status)
done
[ "$STATUS" = "READY" ] || { echo "Report $ID is $STATUS" >&2; exit 1; }
# 3. Download
URL=$(curl -fsS "$BASE/$ID/download-url" "${AUTH[@]}" | jq -er .url)
curl -fsS -o report.csv "$URL"#Synchronous endpoint (deprecated)
POST /api/v1/report-generator/s2s (deprecated) takes the same body (including sourceType, dateSource and timezone) and returns the file directly as a binary stream with a Content-Disposition: attachment header. If the report fails or is not ready in time, it returns 422. Large ranges can hit HTTP timeouts on your side or ours. To migrate:
- Send the same body to
POST /api/v1/report-generator/s2s/async. - Poll
GET …/async/{id}untilREADY. - Download from the URL returned by
GET …/async/{id}/download-url.
#Errors
| HTTP | When |
|---|---|
400 | Invalid x-merchant-key / x-merchant-secret, missing or malformed dateFrom/dateTo, a range longer than 10 days, or unsupported format, sourceType, dateSource or timezone |
400 | download-url called before the report is READY |
404 | Report ID not found or created by another merchant |
422 | Deprecated synchronous endpoint only: the report failed or was not ready in time |
{ "error": "Invalid merchant credentials" }#Best Practices
- Automate it. Run the export from a scheduled job (for example nightly reconciliation), not interactively.
- Prefer
lastModifiedDatefor incremental syncs. Each run, request the window since your previous run so status changes are not missed. - Pin a
timezone. PassUTC(or your accounting timezone) so day boundaries and file dates match your books. - Match the portal. Report columns mirror the Merchant Portal's Export Transactions download for the same report type.
- Keep credentials server-side. Never embed
x-merchant-secretin client apps.
#Related
- Transaction Status — status values shown in report rows
- Webhook Best Practices — pair reports with real-time webhook data