Z Zise Developers 简体中文

Create a reconciliation report export task (asynchronous; produces xlsx)

POST /v1/reports scope: merchant:read
Merchant account Requires x-idempotency-key

Why an asynchronous task instead of a synchronous download

A month's entries can easily reach a hundred thousand rows; synchronous generation would exceed CPU limits, appearing to users as a click with no response. This endpoint therefore only creates a task (201 + status: queued). Retrieve the file from GET /v1/reports/{id}/content.

FieldTypeRequiredDescription
x-idempotency-key string Required UUID v4. Within 24 hours, the same key and body replay the original 201 unchanged, with X-Idempotent-Replay: true, without creating another task. ⚠ After 24 hours, the same key really creates a new task, consuming an in-progress slot. This business line has no second idempotency layer. Use a new key for each export.

Request Body

FieldTypeRequiredDescription
kind string Required daily / pnl / statements / line:<line>. Case-sensitive; unrecognized values return invalid_request.
days integer Optional Window length in days, 1..180, default 30. Out-of-range or nonnumeric values silently fall back to 30. Use the returned days as authoritative.

Response

201Task created; status is always queued. days is the effective value.
{
  "id": "rep_4c8a10d2-91f7-4a3e-b0c6-52d8e7f19b34",
  "kind": "statements",
  "status": "queued",
  "days": 30,
  "created_at": "2026-08-13T05:00:00.000Z"
}
400invalid_request: body is not JSON or kind is unrecognized, including an unregistered line:<line>. limit_exceeded: 3 tasks are already in progress (limit_type: report_exports_in_flight · limit_scope: merchant).
403insufficient_scope: missing merchant:read.
409idempotency_key_reused · idempotency_in_progress

Additional Details

Calls advance the queue; scheduled processing recovers stalled tasks

Task creation advances processing once in the background of the request. GET /v1/reports/{id} also advances it once. Small reports often finish before the first poll. A sweep runs every 5 minutes as well, so tasks are eventually completed even without polling, just more slowly.

This business line emits no webhooks; poll for readiness. Inventing a report.ready event would only document an event that nothing emits.

kind: unknown values are rejected immediately

kindMeaning
dailyDaily summaries by date × business line × asset
pnlTotals by business line over the entire window
line:<line>Same as above, restricted to one line
statementsBusiness debits per order

<line> is one of eight values: qrpay · remit · card · earn · exchange · withdraw · transfer · deposit. Unrecognized kind values immediately return 400 invalid_request. Accepting them would create a task only for the generator to fail after a 30-second wait; 400 immediately identifies the invalid name.

days: out-of-range values silently fall back to 30 without an error

Valid range: 1..180. An unbounded range would expose full-table scans. Nonnumeric input, 0, and 200 do not return an error; all use 30. ⚠ Read days in the 201 response for the effective window, not the number you supplied.

Maximum 3 in-progress tasks

When queued + building ≥ 3, returns 400 limit_exceeded with limit_type: report_exports_in_flight and limit_scope: merchant. Each export aggregates the whole window. Without this gate, an incorrect loop could let your report queue consume the Worker's entire CPU budget and impair your own order endpoints.

Why merchant:read rather than a new scope

Exported data is the same data already accessible through this scope, including statements and per-line aggregates, in a different format. A new scope would create two permissions for the same data without a clear way to determine which one is missing when downloads fail. It is a POST that does not move money, following the same criterion as POST /v1/merchant/deposit-addresses.

Request
curl -X POST 'https://api.zinfra.vip/v1/reports' \
  -H 'x-auth-token: Bearer $TOKEN' \
  -H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "kind": "statements",
    "days": 30
  }'
const res = await fetch("https://api.zinfra.vip/v1/reports", {
  method: "POST",
  headers: {
    "x-auth-token": "Bearer $TOKEN",
    "x-idempotency-key": "$IDEMPOTENCY_KEY",
    "content-type": "application/json",
  },
  body: JSON.stringify({
    "kind": "statements",
    "days": 30
  }),
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();
import requests

res = requests.post(
    "https://api.zinfra.vip/v1/reports",
    headers={
        "x-auth-token": "Bearer $TOKEN",
        "x-idempotency-key": "$IDEMPOTENCY_KEY",
        "content-type": "application/json",
    },
    json={
      "kind": "statements",
      "days": 30
    },
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zinfra.vip/v1/reports",
    strings.NewReader(`{
  "kind": "statements",
  "days": 30
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-idempotency-key", "$IDEMPOTENCY_KEY")
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// Decode amount fields as string, not float64.
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.zinfra.vip/v1/reports"))
    .header("x-auth-token", "Bearer $TOKEN")
    .header("x-idempotency-key", "$IDEMPOTENCY_KEY")
    .header("content-type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "kind": "statements",
  "days": 30
}
"""))
    .build();
// Use String / BigDecimal for amounts, not double.
$ch = curl_init('https://api.zinfra.vip/v1/reports');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
    'x-idempotency-key: $IDEMPOTENCY_KEY',
    'content-type: application/json',
  ],
  CURLOPT_POSTFIELDS => <<<'JSON'
{
  "kind": "statements",
  "days": 30
}
JSON,
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
201
{
  "id": "rep_4c8a10d2-91f7-4a3e-b0c6-52d8e7f19b34",
  "kind": "statements",
  "status": "queued",
  "days": 30,
  "created_at": "2026-08-13T05:00:00.000Z"
}