Reconciliation

What you can and cannot rebuild from the /api/v1/partner reads today, and what has to come from webhooks. Everything here is per customer: every read takes {customerId} in its path, and there is no tenant-wide or unified feed on this API.

What the reads give you

You want to know Read Scope
Every accounting movement on one fiat account GET /api/v1/partner/customers/{customerId}/banking/accounts/{accountId}/transactions accounts:read
Which fiat deposits arrived, including held ones GET /api/v1/partner/customers/{customerId}/banking/deposits deposits:read
Which crypto deposits arrived, with the on-chain hash GET /api/v1/partner/customers/{customerId}/crypto/deposits deposits:read
Fiat withdrawals and their state GET /api/v1/partner/customers/{customerId}/banking/withdrawals withdrawals:read
Crypto withdrawals and their state GET /api/v1/partner/customers/{customerId}/crypto/withdrawals withdrawals:read
Conversions and the rate they filled at GET /api/v1/partner/customers/{customerId}/banking/conversion-requests exchanges:read
The spendable fiat balance right now GET /api/v1/partner/customers/{customerId}/banking/balance balances:read

Paging, ordering and totals

Every list above takes page (1-based; anything below 1 is read as 1) and limit (clamped to 1–500; the default is 100). The pagination object is real: total is the number of matching rows, last_page is ceil(total / per_page) and never below 1, so the loop below terminates and reads everything.

page=1
while :; do
  body=$(curl -s "https://api.kwiikpay.io/api/v1/partner/customers/$CUSTOMER/banking/deposits?page=$page&limit=500" \
           -H "X-Api-Key: kp_live_...")
  # ...store body.data.deposits...
  last=$(echo "$body" | jq '.data.pagination.last_page')
  [ "$page" -ge "$last" ] && break
  page=$((page+1))
done

Rows come newest first with a stable tie-break on id, so a row cannot appear on two pages or on none — unless a new row is created while you page, in which case it shifts everything by one and the first page is the one to re-read. Always deduplicate on id.

The account transaction feed

GET .../banking/accounts/{accountId}/transactions is the ledger of ONE fiat account. Read it as an accountant would, not as a payment history:

Deposits: what arrived, held or not

Both deposit lists return every row regardless of state — pending, held, failed and returned rows are all there. Two fields decide what a row means:

A fiat row carries currency and provider_reference; a crypto row carries asset_code, network_code, transaction_hash and provider_reference. amount is in minor/atomic units on both — divide by 10^asset_scale, which is on the row.

Fiat deposits also expose sender (the provider-reported payer name) and reference (the incoming payment/remittance reference). A missing value is null. provider_reference remains the provider's transaction identifier. These names describe the provider's data; they do not constitute independent verification of the originator's identity. Crypto sender remains null, and its reference is the same on-chain hash as transaction_hash.

Read an individual deposit with deposits:read:

GET /api/v1/partner/customers/{customerId}/banking/deposits/{depositId}
GET /api/v1/partner/customers/{customerId}/crypto/deposits/{depositId}

The response is under data.deposit and uses the same fields as the corresponding list. A parent's key uses its authorized child's id as customerId. An unknown deposit, a deposit of the wrong type, or a deposit outside that customer's scope returns 404. These are v1 routes; the v2 public API does not provide deposit reads.

Provider reconciliation may fill missing descriptive fields on historical deposits without crediting the deposit again. Read the list/detail to recover those values and track updated_at. Previously delivered webhook payloads remain unchanged; replaying an old event returns its original contents and does not create a new receipt with the enriched fields.

What has to come from webhooks

A catch-up procedure after downtime

  1. For each customer, page .../banking/deposits and .../crypto/deposits and upsert on id, keeping status, reason_code and updated_at.
  2. Page .../banking/withdrawals, .../crypto/withdrawals and .../banking/conversion-requests the same way.
  3. If you keep your own copy of the ledger, page .../banking/accounts/{accountId}/transactions for each fiat account and upsert on id (the journal id). Then check that the sum of amount matches the balance you hold; a mismatch means a journal you have not stored.
  4. Re-subscribe or re-enable your webhook endpoint before you start — see Webhooks — so nothing new is missed while you catch up.