GET /v1/transactions
Returns every on-chain transaction associated with your account — deposits, sweeps, gas top-ups, and withdrawals — newest first. Cursor-paginated.
| Method & path | GET /v1/transactions |
| Required permission | list_transactions |
| Auth | HMAC — see Authentication |
Query parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
direction | string | optional | — | Filter by direction. One of deposit, withdrawal, sweep, gas_topup. |
currency | string | optional | — | Filter by currency symbol. |
network | string | optional | — | Filter by network code. |
status | string | optional | — | Filter by status (see status table below). |
cursor | integer | optional | — | Pagination cursor. Pass the previous response's next_cursor value. Omit for the first page. |
limit | integer | optional | 50 | Page size, 1–200. |
Response
{
"success": true,
"message": "OK",
"data": [
{
"id": 99,
"tx_hash": "0x9a8...",
"direction": "deposit",
"status": "confirmed",
"currency": "USDT",
"network": "ETH",
"from_address": "0xsender...",
"to_address": "0xabc...def",
"amount_decimal": "100.000000",
"confirmations": 12,
"block_number": 21345678,
"created_at": "2026-05-16T19:30:00+00:00",
"confirmed_at": "2026-05-16T19:35:30+00:00"
}
],
"data_count": 1,
"next_cursor": null,
"has_more": false
}
The single-row example above is a terminal page — has_more is false, so next_cursor is null. On a full page (data_count equal to your limit), has_more is true and next_cursor is the id of the last (oldest) row, which you pass back as cursor.
Top-level fields
| Field | Type | Description |
|---|---|---|
data | array | Up to limit transactions, newest first. |
data_count | integer | Number of rows in data. |
next_cursor | integer | null | Pass on the next call as cursor=… to fetch the next page. null when no more pages. |
has_more | boolean | true while more pages remain. When false, next_cursor is null. |
Row fields
| Field | Type | Description |
|---|---|---|
id | integer | Chain transaction ID. Stable; safe to use as a primary key in your store. |
tx_hash | string | On-chain transaction hash. |
direction | string | deposit (inbound to one of your deposit addresses), sweep (an internal move from a deposit address to your receiving wallet), gas_topup (an internal transfer that funds a token sweep), or withdrawal (outbound to a destination you supplied). |
status | string | See status table below. |
currency | string | Currency symbol. |
network | string | Network code. |
from_address | string | On-chain sender. |
to_address | string | On-chain recipient. |
amount_decimal | string | Amount in human units (decimal string). |
confirmations | integer | Latest known confirmation count. |
block_number | integer | Block in which this tx was first seen. 0 for transactions the gateway has initiated but not yet observed on-chain. |
created_at | ISO-8601 UTC | When the row was inserted (detected or initiated). |
confirmed_at | ISO-8601 UTC | null | When confirmations crossed network.min_confirmations. null while still pending. |
status by direction
| Direction | Possible statuses |
|---|---|
deposit | detected → confirmed → sweep_pending → swept, or failed at any step. |
sweep | detected → confirmed, or failed. |
gas_topup | detected → confirmed, or failed. |
withdrawal | detected → confirmed, or failed. |
Errors
Standard middleware errors, plus schema validation on the query parameters.
| Status | Cause |
|---|---|
401 / 403 / 429 | See Errors page. |
422 | Invalid direction (must be one of deposit / withdrawal / sweep / gas_topup) or limit outside 1–200. message names the offending field. |
Code samples
Examples assume sign_request(...) from the Authentication page.
- curl + bash
- Python
- Node.js
- PHP
METHOD="GET"
ENDPOINT="/v1/transactions"
QUERY="direction=deposit&limit=50"
BODY=""
# ... sign as on the Authentication page ...
curl -sS "${BASE_URL}${ENDPOINT}?${QUERY}" \
-H "X-CPG-Key: ${API_KEY}" \
-H "X-CPG-Timestamp: ${TS}" \
-H "X-CPG-Signature: ${SIG}"
def list_all(direction: str | None = None):
"""Walk every page; yield each row. Re-signs per request because each
page changes the cursor and therefore the canonical query string."""
cursor: int | None = None
while True:
parts = [f"limit=200"]
if direction:
parts.append(f"direction={direction}")
if cursor is not None:
parts.append(f"cursor={cursor}")
query = "&".join(parts)
headers = sign_request("GET", "/v1/transactions", query=query)
resp = requests.get(f"{BASE_URL}/v1/transactions?{query}", headers=headers, timeout=10)
resp.raise_for_status()
body = resp.json()
for row in body["data"]:
yield row
if not body["has_more"]:
return
cursor = body["next_cursor"]
for tx in list_all(direction="deposit"):
print(tx["id"], tx["status"], tx["amount_decimal"])
async function* listAll({ direction } = {}) {
let cursor = null;
for (;;) {
const parts = ['limit=200'];
if (direction) parts.push(`direction=${direction}`);
if (cursor !== null) parts.push(`cursor=${cursor}`);
const query = parts.join('&');
const headers = signRequest('GET', '/v1/transactions', '', query);
const resp = await fetch(`${BASE_URL}/v1/transactions?${query}`, { headers });
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
const body = await resp.json();
for (const row of body.data) yield row;
if (!body.has_more) return;
cursor = body.next_cursor;
}
}
for await (const tx of listAll({ direction: 'deposit' })) {
console.log(tx.id, tx.status, tx.amount_decimal);
}
function list_all(string $direction = null): Generator {
global $baseUrl;
$cursor = null;
while (true) {
$parts = ['limit=200'];
if ($direction !== null) $parts[] = "direction=$direction";
if ($cursor !== null) $parts[] = "cursor=$cursor";
$query = implode('&', $parts);
$headers = sign_request('GET', '/v1/transactions', '', $query);
$ch = curl_init("$baseUrl/v1/transactions?$query");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($body['data'] as $row) yield $row;
if (!$body['has_more']) return;
$cursor = $body['next_cursor'];
}
}
foreach (list_all('deposit') as $tx) {
echo "{$tx['id']} {$tx['status']} {$tx['amount_decimal']}" . PHP_EOL;
}
Notes
- Cursor pagination, not offset. The cursor is the
idof the oldest row in the previous page; the server returns rows withid < cursor. New rows inserted during pagination won't shift your pages — you'll just see them on a fresh first-page call. Don't try to "go back" — there's noprevious_cursor. If you need to re-read recent history, start a fresh walk from cursor=null. - Sign every page separately. The canonical string includes the query string, so a request with
cursor=99has a different signature than one withcursor=42. The loops above re-callsign_request()on every iteration — don't try to cache headers. - Combine filters freely.
?direction=withdrawal&status=failed¤cy=USDTis a valid combination and applied as AND. - Sweep and gas_topup rows are operational telemetry. Most integrators only care about
direction=depositanddirection=withdrawal. The other two are useful for debugging stuck balances or unexplained dust at deposit addresses.