ChainZap Documentation API
EN
Get API access
Contents
Get API access

Search stays in your browser. Use ↑ ↓ to choose a result and Enter to open it.

ChainZap / Documentation

Transaction Safety API

Check a transaction before confirming a payment. Integrate TRON checks, understand the results and handle retries safely.

Transaction Safety API

Check a transaction before recording a payment as received. ChainZap combines transaction facts, risk signals and optional expected recipient, token and amount matching into a single response.

What you can check

TRON is available in v1. Ethereum and TON are planned; they are not accepted by the API. Access is invite-only and subject to your organization's enabled networks and limits. Availability of a network is separate from live service status.

Use POST /v1/transaction/check for a minimal KYT check or add the expected_* fields for enhanced verification. Use GET /v1/usage for your organization's quota and recent usage. Authentication uses X-API-Key from your server.

Interpret the whole response

A successful HTTP response does not mean a payment is safe or confirmed. Read transaction_state, overall_state, coverage, matching flags, freshness and reasons together. null means unknown or not requested, depending on the field. Missing evidence must never be treated as a clean result.

Start with the quickstart, then review coverage and idempotency.

Your first transaction check

Get access

Ask for an invitation through ChainZap support. Sign in through your identity provider, open API keys, create a named key and save it in your server's secret store. The full key is shown once. Commercial terms are agreed with your organization; no public price is assumed.

Send your first request

This example uses a fictional key and transaction hash. Replace them on your server. All amounts are decimal strings. Keep your idempotency key with the payment operation, including across process restarts.

cURL
curl --include --max-time 15 'https://api.chainzap.io/v1/transaction/check' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: cz_live_000000000000.fictional_example_only' \
  -H 'Idempotency-Key: merchant-order-1001-check-1' \
  -H 'X-Request-ID: merchant-order-1001-attempt-1' \
  --data '{"network":"tron","tx_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}'

Inspect X-Request-ID in the response headers and request_id in the JSON. Preserve this identifier for support. The fictional hash may return not_found; see transaction states.

Verify expected payment details

JSON
{
  "network": "tron",
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "expected_recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "expected_token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "expected_amount": "1250.000001"
}

The example addresses are public sample addresses, not payment instructions. Match an exact token contract, not a symbol. Read all three match flags; they refer to the same selected transfer. See matching.

Handle uncertainty

If the connection closes before you receive a response, retry the same body and same Idempotency-Key. Never invent a new key to bypass an uncertain result. Only retry a received error when error.retryable is true; honor Retry-After and cap retries. A stored error can be replayed unchanged: retryable does not force a new provider check.

See Python and TypeScript examples for bounded retries and usage for quota inspection.

Authentication

Server API keys

Send X-API-Key over HTTPS on every business API request. Never place a key in a URL, browser storage, analytics, support message or source control. Keys grant access to one organization; requests do not accept a caller-selected tenant ID.

The developer portal sign-in session is separate from API keys. Owner, admin and developer members can create, rotate and revoke keys. Billing and viewer members can inspect usage and key metadata but cannot manage keys or run checks.

Key lifecycle

Give each integration a descriptive label. The full key is shown only on creation or rotation; lists show a prefix. Rotation immediately revokes the old key and creates its replacement atomically. Update your server's secret store after rotation. To avoid an outage during a planned migration, create a separate key, update and verify your integration, then explicitly revoke the previous key.

An invalid or revoked key returns 401 invalid_api_key. A suspended organization returns 403 tenant_suspended. A network outside the organization's entitlements returns 403 network_not_enabled. Do not retry these errors without correcting access.

POST /v1/transaction/check

Request

The request is a strict JSON object: unknown fields are rejected. network is tron. tx_hash is a 64-character hexadecimal transaction identifier. Optional expected_recipient and expected_token_contract are checksum-valid TRON addresses. expected_amount is a nonnegative decimal string; exponent notation and JSON numbers are rejected.

Add Idempotency-Key for a logical check operation and X-Request-ID for correlation. Business request bodies are limited to 4 KiB. Send Content-Type: application/json. See the generated API reference for exact required fields, enums and response schemas.

Response

Read transaction_state independently from overall_state. The response includes confirmations, normalized transfers, the selected transfer, risk level, sanctions status, token authenticity, matching flags, coverage, freshness and 2 to 5 prioritized reasons. Transfer and exposure amounts are decimal strings. Nullable fields must be handled explicitly.

transfers_truncated=true and AMBIGUOUS_TRANSFER mean the response requires attention. Do not infer a full list from a truncated response or combine matching flags from different transfers.

A response with partial coverage is not billed. Complete coverage consumes one unit of monthly quota, including a complete high-risk result. Replaying the same idempotent operation never bills twice.

Match a transfer to a payment

Use the expected_* fields to verify a payment's recipient, token contract and amount. Send all three fields or only the comparisons you need.

One transfer, three comparisons

A transaction can contain several transfers. ChainZap first looks for a transfer that satisfies every supplied condition. If none does, it selects the transfer with the most matches. Ties are resolved consistently using the transfer index.

recipient_match, token_match and amount_match all describe that same transfer. If several transfers satisfy every condition, the response includes AMBIGUOUS_TRANSFER.

Result Meaning
true The value matches the one you supplied.
false The value could be checked but does not match.
null You did not request this comparison, or there was not enough data to make it.

How values are compared

If you provide an expected value but its transfer data is unavailable, the corresponding match field is null, coverage is partial, and the reasons include EXPECTED_DATA_UNAVAILABLE. Missing token decimals, for example, do not prove an amount mismatch. Do not treat null as a successful match.

Addresses are validated with TRON checksums. Tokens are compared by exact contract, never by name or symbol. Amounts are converted from decimal strings to raw integer units using known token decimals and compared exactly; no floating-point tolerance is applied. An amount with more nonzero precision than the asset supports returns 422 invalid_expected_amount.

Mismatch example

If you expect "10.000001" but the selected transfer contains "10", the response includes amount_match=false and EXPECTED_AMOUNT_MISMATCH.

A different recipient produces EXPECTED_RECIPIENT_MISMATCH; a different token contract produces EXPECTED_TOKEN_MISMATCH. A matching amount does not prove that the recipient and token also match. Before accepting a payment, check every required comparison, the transaction state and the overall assessment.

GET /v1/usage

Request

cURL
curl --include --max-time 15 'https://api.chainzap.io/v1/usage?days=30' \
  -H 'X-API-Key: cz_live_000000000000.fictional_example_only' \
  -H 'X-Request-ID: merchant-usage-1001'

days is an integer from 1 to 90, default 30. No idempotency key is needed for this read. Inspect the response X-Request-ID header. A usage request is rate-limited but does not consume transaction quota. Retry only errors marked retryable, respecting Retry-After.

Quota and statistics

period.start and period.end delimit the current UTC calendar month. used counts billable completed checks, reserved counts outstanding reservations, and remaining = max(0, limit - used - reserved). A reservation is released when a nonbillable request is finalized.

recent counts completed logical checks during the requested rolling period. Idempotent HTTP replays are not new check records. It includes billable, error and LIMITED_DATA counts, average latency, networks and overall-state distribution. It contains no transaction hashes or provider details.

The portal adds UTC daily buckets, 1/7/30-day counters and p50/p95 when at least 20 completed latency samples exist in the last 30 days. Successful-response counts include partial responses; limited coverage is a subset, not an additional total.

Integration examples

Python: idempotent retry

This server-side example uses httpx. The key is fictional. In a real integration, load the API key from your server's secret store and persist operation_key and body in your own operation ledger before the first attempt. A process restart must reuse them.

Python
import random
import time
import httpx

api_key = "cz_live_000000000000.fictional_example_only"
operation_key = "merchant-order-1001-check-1"
body = {"network": "tron", "tx_hash": "a" * 64}


def check():
    with httpx.Client(timeout=15, follow_redirects=False) as client:
        for attempt in range(4):
            try:
                response = client.post(
                    "https://api.chainzap.io/v1/transaction/check",
                    headers={
                        "X-API-Key": api_key,
                        "Idempotency-Key": operation_key,
                        "X-Request-ID": f"order-1001-attempt-{attempt + 1}",
                    },
                    json=body,
                )
            except httpx.TransportError:
                if attempt == 3:
                    raise RuntimeError("unresolved_transport_outcome") from None
                time.sleep(2 ** attempt + random.uniform(0, 1))
                continue
            print("X-Request-ID:", response.headers.get("X-Request-ID"))
            try:
                result = response.json()
            except ValueError:
                raise RuntimeError("unresolved_gateway_response") from None
            if response.is_success:
                return result
            error = result.get("error", {})
            if error.get("retryable") is not True:
                raise RuntimeError(error.get("code", "unresolved_response"))
            if response.headers.get("Idempotent-Replayed") == "true":
                raise RuntimeError("stored_error_requires_reconciliation")
            try:
                instructed = int(response.headers.get("Retry-After", "0"))
            except ValueError:
                raise RuntimeError("invalid_retry_after") from None
            delay = max(instructed, 2 ** attempt) + random.uniform(0, 1)
            if attempt == 3 or delay > 30:
                raise RuntimeError("schedule_retry_with_same_operation_key")
            time.sleep(delay)

This handles retryable 409/429/503/504 and network uncertainty without creating another logical operation. Treat returned business states according to your payment workflow. Do not log body or the entire result.

JavaScript / TypeScript: server request

Use this code on your server, never in a browser with a live key. The operation key and body must be persisted before calling the function, just as in the Python example.

TypeScript
type PublicResult = {
  request_id?: string;
  error?: { code: string; message: string; retryable: boolean };
  [field: string]: unknown;
};

const apiKey = "cz_live_000000000000.fictional_example_only";
const operationKey = "merchant-order-1001-check-1";
const body = { network: "tron", tx_hash: "a".repeat(64) };
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

async function check(): Promise<PublicResult> {
  for (let attempt = 0; attempt < 4; attempt++) {
    let response: Response;
    try {
      response = await fetch("https://api.chainzap.io/v1/transaction/check", {
        method: "POST", redirect: "error", signal: AbortSignal.timeout(15000),
        headers: {
          "Content-Type": "application/json", "X-API-Key": apiKey,
          "Idempotency-Key": operationKey,
          "X-Request-ID": `order-1001-attempt-${attempt + 1}`,
        },
        body: JSON.stringify(body),
      });
    } catch {
      if (attempt === 3) throw new Error("unresolved_transport_outcome");
      await wait((2 ** attempt + Math.random()) * 1000);
      continue;
    }
    console.info("X-Request-ID:", response.headers.get("X-Request-ID"));
    const result = await response.json() as PublicResult;
    if (response.ok) return result;
    if (result.error?.retryable !== true) {
      throw new Error(result.error?.code ?? "unresolved_response");
    }
    if (response.headers.get("Idempotent-Replayed") === "true") {
      throw new Error("stored_error_requires_reconciliation");
    }
    const instructed = Number(response.headers.get("Retry-After") ?? "0");
    if (!Number.isFinite(instructed) || instructed < 0) {
      throw new Error("invalid_retry_after");
    }
    const delay = Math.max(instructed, 2 ** attempt) + Math.random();
    if (attempt === 3 || delay > 30) {
      throw new Error("schedule_retry_with_same_operation_key");
    }
    await wait(delay * 1000);
  }
  throw new Error("retry_budget_exhausted");
}

Response scenarios

The following synthetic fixtures illustrate valid response shapes, not live checks. Every sample has a corresponding X-Request-ID header equal to its request_id. Inspect null values and coverage as well as the overall state.

Confirmed transaction

View full JSON response
JSON
{
  "aml_risk_level": "low",
  "amount": "1250.000001",
  "amount_match": true,
  "confirmations": 27,
  "coverage": "complete",
  "decimals": 6,
  "freshness": {
    "checked_at": "2026-09-03T12:00:00Z",
    "max_source_age_seconds": 4,
    "risk_data_at": "2026-09-03T12:00:00Z",
    "token_data_at": "2026-09-03T12:00:00Z",
    "transaction_at": "2026-09-03T11:59:00Z"
  },
  "key_exposure": [],
  "network": "tron",
  "overall_state": "CLEAR",
  "reasons": [
    {
      "category": "transaction",
      "code": "TX_FINALIZED",
      "severity": "info"
    },
    {
      "category": "risk",
      "code": "AML_LOW",
      "severity": "info"
    }
  ],
  "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "recipient_match": true,
  "request_id": "example-confirmed",
  "required_confirmations": 19,
  "sanctions_hit": false,
  "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "token_authenticity": "official",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "token_match": true,
  "token_symbol": "USDT",
  "transaction_category": "trc20_transfer",
  "transaction_state": "confirmed",
  "transaction_type": "TriggerSmartContract",
  "transfers": [
    {
      "amount": "1250.000001",
      "asset_type": "trc20",
      "decimals": 6,
      "index": 0,
      "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
      "token_id": null,
      "token_symbol": "USDT"
    }
  ],
  "transfers_truncated": false,
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Awaiting confirmation

View full JSON response
JSON
{
  "aml_risk_level": "low",
  "amount": "1250.000001",
  "amount_match": true,
  "confirmations": 3,
  "coverage": "complete",
  "decimals": 6,
  "freshness": {
    "checked_at": "2026-09-03T12:00:00Z",
    "max_source_age_seconds": 4,
    "risk_data_at": "2026-09-03T12:00:00Z",
    "token_data_at": "2026-09-03T12:00:00Z",
    "transaction_at": "2026-09-03T11:59:00Z"
  },
  "key_exposure": [],
  "network": "tron",
  "overall_state": "REVIEW",
  "reasons": [
    {
      "code": "TX_PENDING_CONFIRMATIONS",
      "category": "transaction",
      "severity": "warning"
    },
    {
      "code": "AML_LOW",
      "category": "risk",
      "severity": "info"
    }
  ],
  "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "recipient_match": true,
  "request_id": "example-pending",
  "required_confirmations": 19,
  "sanctions_hit": false,
  "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "token_authenticity": "official",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "token_match": true,
  "token_symbol": "USDT",
  "transaction_category": "trc20_transfer",
  "transaction_state": "pending",
  "transaction_type": "TriggerSmartContract",
  "transfers": [
    {
      "amount": "1250.000001",
      "asset_type": "trc20",
      "decimals": 6,
      "index": 0,
      "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
      "token_id": null,
      "token_symbol": "USDT"
    }
  ],
  "transfers_truncated": false,
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Failed transaction

View full JSON response
JSON
{
  "aml_risk_level": "low",
  "amount": null,
  "amount_match": null,
  "confirmations": 27,
  "coverage": "complete",
  "decimals": 6,
  "freshness": {
    "checked_at": "2026-09-03T12:00:00Z",
    "max_source_age_seconds": 4,
    "risk_data_at": "2026-09-03T12:00:00Z",
    "token_data_at": "2026-09-03T12:00:00Z",
    "transaction_at": "2026-09-03T11:59:00Z"
  },
  "key_exposure": [],
  "network": "tron",
  "overall_state": "REVIEW",
  "reasons": [
    {
      "code": "TX_REVERTED",
      "category": "transaction",
      "severity": "warning"
    },
    {
      "code": "TX_FACTS_VERIFIED",
      "category": "transaction",
      "severity": "info"
    }
  ],
  "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "recipient_match": null,
  "request_id": "example-failed",
  "required_confirmations": 19,
  "sanctions_hit": false,
  "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "token_authenticity": "official",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "token_match": null,
  "token_symbol": "USDT",
  "transaction_category": "trc20_transfer",
  "transaction_state": "failed",
  "transaction_type": "TriggerSmartContract",
  "transfers": [],
  "transfers_truncated": false,
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Transaction not found

JSON
{
  "aml_risk_level": "unknown",
  "amount": null,
  "amount_match": null,
  "confirmations": null,
  "coverage": "complete",
  "decimals": null,
  "freshness": {
    "checked_at": "2026-09-03T12:00:00Z",
    "max_source_age_seconds": 4,
    "risk_data_at": "2026-09-03T12:00:00Z",
    "token_data_at": "2026-09-03T12:00:00Z",
    "transaction_at": "2026-09-03T11:59:00Z"
  },
  "key_exposure": [],
  "network": "tron",
  "overall_state": "REVIEW",
  "reasons": [
    {
      "code": "TX_NOT_FOUND",
      "category": "transaction",
      "severity": "warning"
    },
    {
      "code": "TX_FACTS_VERIFIED",
      "category": "transaction",
      "severity": "info"
    }
  ],
  "recipient": null,
  "recipient_match": null,
  "request_id": "example-not-found",
  "required_confirmations": 19,
  "sanctions_hit": null,
  "sender": null,
  "token_authenticity": "unknown",
  "token_contract": null,
  "token_match": null,
  "token_symbol": null,
  "transaction_category": "unknown",
  "transaction_state": "not_found",
  "transaction_type": "unknown",
  "transfers": [],
  "transfers_truncated": false,
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Insufficient data

View full JSON response
JSON
{
  "aml_risk_level": "unknown",
  "amount": "1250.000001",
  "amount_match": true,
  "confirmations": 27,
  "coverage": "partial",
  "decimals": 6,
  "freshness": {
    "checked_at": "2026-09-03T12:00:00Z",
    "max_source_age_seconds": 4,
    "risk_data_at": "2026-09-03T12:00:00Z",
    "token_data_at": "2026-09-03T12:00:00Z",
    "transaction_at": "2026-09-03T11:59:00Z"
  },
  "key_exposure": [],
  "network": "tron",
  "overall_state": "LIMITED_DATA",
  "reasons": [
    {
      "code": "AML_UNAVAILABLE",
      "category": "risk",
      "severity": "warning"
    },
    {
      "code": "SANCTIONS_UNAVAILABLE",
      "category": "sanctions",
      "severity": "warning"
    }
  ],
  "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "recipient_match": true,
  "request_id": "example-limited-data",
  "required_confirmations": 19,
  "sanctions_hit": null,
  "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "token_authenticity": "official",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "token_match": true,
  "token_symbol": "USDT",
  "transaction_category": "trc20_transfer",
  "transaction_state": "confirmed",
  "transaction_type": "TriggerSmartContract",
  "transfers": [
    {
      "amount": "1250.000001",
      "asset_type": "trc20",
      "decimals": 6,
      "index": 0,
      "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
      "token_id": null,
      "token_symbol": "USDT"
    }
  ],
  "transfers_truncated": false,
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Sanctions match

View full JSON response
JSON
{
  "aml_risk_level": "low",
  "amount": "1250.000001",
  "amount_match": true,
  "confirmations": 27,
  "coverage": "complete",
  "decimals": 6,
  "freshness": {
    "checked_at": "2026-09-03T12:00:00Z",
    "max_source_age_seconds": 4,
    "risk_data_at": "2026-09-03T12:00:00Z",
    "token_data_at": "2026-09-03T12:00:00Z",
    "transaction_at": "2026-09-03T11:59:00Z"
  },
  "key_exposure": [],
  "network": "tron",
  "overall_state": "HIGH_RISK",
  "reasons": [
    {
      "code": "SANCTIONS_HIT",
      "category": "sanctions",
      "severity": "critical"
    },
    {
      "code": "TX_FINALIZED",
      "category": "transaction",
      "severity": "info"
    }
  ],
  "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "recipient_match": true,
  "request_id": "example-sanctions-hit",
  "required_confirmations": 19,
  "sanctions_hit": true,
  "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "token_authenticity": "official",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "token_match": true,
  "token_symbol": "USDT",
  "transaction_category": "trc20_transfer",
  "transaction_state": "confirmed",
  "transaction_type": "TriggerSmartContract",
  "transfers": [
    {
      "amount": "1250.000001",
      "asset_type": "trc20",
      "decimals": 6,
      "index": 0,
      "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
      "token_id": null,
      "token_symbol": "USDT"
    }
  ],
  "transfers_truncated": false,
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Payment details do not match

View full JSON response
JSON
{
  "aml_risk_level": "low",
  "amount": "1250.000001",
  "amount_match": false,
  "confirmations": 27,
  "coverage": "complete",
  "decimals": 6,
  "freshness": {
    "checked_at": "2026-09-03T12:00:00Z",
    "max_source_age_seconds": 4,
    "risk_data_at": "2026-09-03T12:00:00Z",
    "token_data_at": "2026-09-03T12:00:00Z",
    "transaction_at": "2026-09-03T11:59:00Z"
  },
  "key_exposure": [],
  "network": "tron",
  "overall_state": "REVIEW",
  "reasons": [
    {
      "code": "EXPECTED_AMOUNT_MISMATCH",
      "category": "expected",
      "severity": "warning"
    },
    {
      "code": "TX_FINALIZED",
      "category": "transaction",
      "severity": "info"
    }
  ],
  "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "recipient_match": true,
  "request_id": "example-expected-mismatch",
  "required_confirmations": 19,
  "sanctions_hit": false,
  "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
  "token_authenticity": "official",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "token_match": true,
  "token_symbol": "USDT",
  "transaction_category": "trc20_transfer",
  "transaction_state": "confirmed",
  "transaction_type": "TriggerSmartContract",
  "transfers": [
    {
      "amount": "1250.000001",
      "asset_type": "trc20",
      "decimals": 6,
      "index": 0,
      "recipient": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "sender": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
      "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
      "token_id": null,
      "token_symbol": "USDT"
    }
  ],
  "transfers_truncated": false,
  "tx_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Transaction states

State Meaning Integration behavior
confirmed Chain facts report confirmation. Also require sufficient confirmations and examine the overall state and coverage.
pending The transaction is visible but finality is incomplete. Do not record final settlement; schedule a separately identified later observation after resolving the current check.
failed Execution failed or reverted. Do not treat the expected transfer as received.
not_found The queried transaction was not found in the available evidence. Check the network and hash; absence is not proof of a completed payment.
unknown The available transaction facts cannot establish a state. Treat as incomplete evidence.

TRON v1 uses a 19-confirmation finality policy together with finalized chain evidence. A transaction state alone does not establish risk coverage. Response examples illustrate these states.

Overall states

State Meaning
CLEAR Applicable checks completed without an identified issue and finality requirements were met. This is not a guarantee against future risk.
REVIEW The result contains a condition requiring review, such as pending finality, failed execution, elevated risk or an expected-payment mismatch.
HIGH_RISK A high or critical finding exists, such as a sanctions match, high AML risk or a fake token.
LIMITED_DATA Evidence is incomplete, unsupported or contradictory. Never treat this as CLEAR.

Coverage and overall state are independent: a known high-risk finding may coexist with partial coverage. Conflicting transaction facts take priority as LIMITED_DATA. Always retain and inspect the actual returned reasons; the list is prioritized and bounded, not an exhaustive evidence dump.

Data completeness

Complete and partial

complete means the applicable evidence checks met their requirements for this response. It does not mean the transaction is safe. partial means one or more required sources, asset mappings, token checks or transaction facts are missing, unsupported or inconsistent.

Unavailable sanctions data is sanctions_hit=null, never false. Unknown risk is aml_risk_level="unknown". An unverified token is token_authenticity="unknown"; its ticker does not prove authenticity. LIMITED_DATA is a normal business response with HTTP 200, not necessarily a transport error.

Freshness

Read freshness.checked_at, the evidence timestamps and max_source_age_seconds. Nullable source timestamps remain unknown. An idempotent replay returns the original result and original request ID; it is not a fresh observation. A new observation is a new logical operation after you have resolved the previous response.

Billing

Only complete checks are billable. Partial coverage, transport errors and idempotent replays do not consume an additional billable unit. The playground uses the same rule and the same organization's quota.

Reason codes

Each reason has code, category and severity (info, warning, high, critical). Responses include 2 to 5 prioritized reasons. Consumers must tolerate future codes and must not infer that omitted reasons mean a check was performed.

Code Meaning
TX_FACTS_VERIFIED Transaction facts meet verification requirements.
TX_FACTS_PARTIAL Required transaction facts are incomplete.
TX_FACTS_DISAGREE Transaction evidence is contradictory.
TX_FINALIZED Transaction finality requirements are met.
TX_PENDING_CONFIRMATIONS Finality or confirmations are insufficient.
TX_REVERTED Transaction execution failed.
TX_NOT_FOUND The transaction was not found.
TRANSFERS_TRUNCATED Transfer evidence was truncated.
AMBIGUOUS_TRANSFER More than one transfer matches the supplied expectations.
AML_UNSUPPORTED_ASSET The applicable asset lacks supported AML coverage.
AML_UNAVAILABLE Applicable AML evidence is missing.
AML_LOW Available AML assessment reports low risk.
AML_ELEVATED Available AML assessment reports moderate risk.
AML_HIGH Available AML assessment reports high or severe risk.
SANCTIONS_HIT A sanctions match is present.
SANCTIONS_CLEAR Available sanctions screening found no match.
SANCTIONS_UNAVAILABLE Sanctions screening is unavailable; this is not a clear result.
TOKEN_AUTH_UNAVAILABLE Token authentication evidence is unavailable.
TOKEN_OFFICIAL Token contract is recognized as official.
TOKEN_FAKE Token is identified as fake.
TOKEN_UNKNOWN Token authenticity is unknown.
EXPECTED_RECIPIENT_MISMATCH The selected transfer does not match the expected recipient.
EXPECTED_TOKEN_MISMATCH The selected transfer does not match the expected contract.
EXPECTED_AMOUNT_MISMATCH The selected transfer does not match the expected raw amount.
EXPECTED_DATA_UNAVAILABLE A requested comparison could not be completed because transfer data is missing. Its match field is null.

Idempotency

Persist a key per logical operation

Idempotency-Key accepts 1 to 128 printable ASCII characters. Keys are scoped to your organization and the normalized request body. Preserve the same key and body across retries, including retries from another server or after API key rotation. Never include credentials or personal information in it.

A completed operation is replayed byte-for-byte with Idempotent-Replayed: true and its original X-Request-ID. A different body with the same key returns nonretryable 409 idempotency_conflict. A still-processing operation returns retryable 409 idempotency_in_progress with Retry-After.

Retention and errors

The default retention is 24 hours; confirm any contracted retention change with support. After retention expires, the key can represent a new billable execution. Bound automatic retries to the retention window and keep your own payment-operation ledger.

Stored errors, including quota and provider failures finalized for an operation, are replayed too. retryable=true describes the failure; it does not erase a stored response. Stop automatic retrying when Idempotent-Replayed: true accompanies a stored error. Once you have definitively received and reconciled that result, explicitly create a new logical check if your workflow needs a later observation. Never use a new key merely because the original network outcome is unknown.

Rate limits, quota and concurrency

Organization limits

Limits come from your current versioned plan or a custom entitlement assignment. Inspect /v1/usage and the portal's Plan and limits page. No public numeric entitlement or price is promised by the examples.

X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset describe your organization's request capacity. X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset describe monthly billable quota; reset headers use Unix seconds. All API keys and requests made in the portal share the same quota. Monthly periods follow UTC, regardless of your browser's timezone.

Backpressure

Rate exhaustion returns 429 rate_limit_exceeded. Too many in-flight checks returns 429 concurrency_limit_exceeded. A full quota returns 429 quota_exceeded. Honor Retry-After, which may be long for a monthly quota. Replays still pass through access, rate and concurrency controls, but do not bill again.

Use a bounded request queue and per-organization concurrency on your server. Plan changes affect future entitlements and do not rewrite historical usage.

Retries and timeouts

Bounded retries

Use a client deadline longer than the 12-second server hard deadline, for example 15 seconds per attempt. The latency target is p95 at most 5 seconds; it is a release gate, not an unconditional guarantee. Account for network time outside the server budget.

For a received error, retry only when error.retryable=true. Honor Retry-After, use exponential backoff with jitter, limit attempts and cap the overall workflow duration. If the instructed wait exceeds your interactive budget, schedule a retry rather than shorten the wait.

For a network exception, retry the same stored body and Idempotency-Key. Preserve both across restarts. Stop on an idempotent replay of a stored error and reconcile the result. 409 idempotency_conflict requires an integration fix; 409 idempotency_in_progress, 429, 503 and 504 may be retried under these rules.

Record only the request ID and public error code in ordinary logs. Do not log request bodies, API keys, wallet values or provider payloads. Executable examples follow these rules.

Error reference

Public envelope

JSON
{"request_id":"merchant-order-1001-attempt-1","error":{"code":"provider_unavailable","message":"Transaction data is temporarily unavailable","retryable":true}}

The response header X-Request-ID identifies the same response. Branch on the stable code and retryable; do not parse the human-readable message. Gateway failures may not have this envelope: treat an invalid or missing envelope as an unresolved transport outcome and preserve the original idempotency key.

HTTP Codes Action
401 invalid_api_key Correct server credentials; do not retry blindly.
403 tenant_suspended, network_not_enabled Resolve access with your organization.
409 idempotency_conflict Fix the operation/body association; nonretryable.
409 idempotency_in_progress Wait for Retry-After and retry the same operation.
422 malformed_tx_hash, invalid_network, invalid_tron_address, invalid_expected_amount, invalid_idempotency_key, payload_too_large, validation_error Correct the invalid request.
429 rate_limit_exceeded, quota_exceeded, concurrency_limit_exceeded Honor Retry-After; reduce concurrency or wait for quota reset.
503 provider_unavailable, rate_limiter_unavailable, api_disabled, api_not_configured Retry only when retryable and within your retry budget; retain the same key.
504 request_timeout Retry the same key and body after the instructed delay.
500 internal_error Use the bounded retry policy and contact support with X-Request-ID.

Check Idempotent-Replayed: a stored error will not refresh itself on replay. See idempotency. Never assume that an HTTP error means no request was accepted.

Security recommendations

Protect credentials

Call the API from your backend. Store API keys in a secret manager, limit access to the integration service and rotate after suspected exposure. Never paste a key into a support ticket, URL, issue tracker, analytics event, browser console or frontend bundle.

Keep your identity provider protected with its available strong authentication policies. Accept invitations only from a trusted organization administrator. Sign out on shared devices; use Organization settings to sign out everywhere.

Handle data deliberately

Validate incoming payment requests on your server. Bind checks to your own payment-operation IDs and reconcile ambiguous outcomes. Use exact token contracts and decimal strings, and handle null and unknown states explicitly. Escape organization names, key labels and response values before displaying them in your own UI.

Retain only data your workflow needs. Restrict access to response details. Send support the response X-Request-ID and public error code; omit keys, balances and raw payloads.

API reference

Download OpenAPI. Field names and types below come directly from the public API contract. A required field may still accept null when the type allows it. Read the guides above for each field’s meaning.

POST /v1/transaction/check

Verify one TRON transaction. Send a JSON body and your server’s X-API-Key. Use Idempotency-Key to retry an operation safely.

HTTP Response
200 Result returned. Inspect the business state and data coverage.
401 API key is invalid or has been revoked.
403 Your organization or network does not have access.
409 The operation is still running, or the key was reused with a different body.
422 Correct the request fields.
429 A rate, concurrency or monthly quota limit was reached.
503 API access or a required service is unavailable.
504 The request exceeded the server time limit.
500 Internal error. Retain X-Request-ID for support.

GET /v1/usage

Read your organization’s limits and usage. The optional days parameter accepts 1-90 and defaults to 30.

HTTP Response
200 Result returned. Inspect the business state and data coverage.
422 Correct the request fields.
401 API key is invalid or has been revoked.
403 Your organization or network does not have access.
429 A rate, concurrency or monthly quota limit was reached.
500 Internal error. Retain X-Request-ID for support.
503 API access or a required service is unavailable.
504 The request exceeded the server time limit.

ErrorDetail

Field Type / values Required
code string Yes
message string Yes
retryable boolean Yes

ErrorResponse

Field Type / values Required
request_id string Yes
error ErrorDetail Yes

ExposureResponse

Field Type / values Required
category string Yes
exposure_type string Yes
hops integer Yes
percent string / null Yes

FreshnessResponse

Field Type / values Required
checked_at string Yes
transaction_at string / null Yes
risk_data_at string / null Yes
token_data_at string / null Yes
max_source_age_seconds integer Yes

ReasonResponse

Field Type / values Required
code string Yes
category "transaction", "risk", "sanctions", "token", "expected" Yes
severity "info", "warning", "high", "critical" Yes

TransactionCheckRequest

Field Type / values Required
tx_hash string Yes
network "tron" Yes
expected_recipient string / null No
expected_token_contract string / null No
expected_amount string / null No

TransactionCheckResponse

Field Type / values Required
request_id string Yes
tx_hash string Yes
network "tron" Yes
transaction_type string Yes
transaction_category "trx_transfer", "trc10_transfer", "trc20_transfer", "smart_contract_call", "smart_contract_deploy", "staking", "voting", "governance", "resource_operation", "account_operation", "exchange", "market", "permission_update", "other", "unknown" Yes
transaction_state "confirmed", "pending", "failed", "not_found", "unknown" Yes
confirmations integer / null Yes
required_confirmations integer Yes
sender string / null Yes
recipient string / null Yes
token_contract string / null Yes
token_symbol string / null Yes
decimals integer / null Yes
amount string / null Yes
transfers TransferResponse[] Yes
transfers_truncated boolean Yes
aml_risk_level "low", "moderate", "high", "severe", "unknown" Yes
sanctions_hit boolean / null Yes
key_exposure ExposureResponse[] Yes
token_authenticity "official", "fake", "unknown" Yes
recipient_match boolean / null Yes
token_match boolean / null Yes
amount_match boolean / null Yes
coverage "complete", "partial" Yes
freshness FreshnessResponse Yes
overall_state "CLEAR", "REVIEW", "HIGH_RISK", "LIMITED_DATA" Yes
reasons ReasonResponse[] Yes

TransferResponse

Field Type / values Required
index integer Yes
asset_type string Yes
sender string / null Yes
recipient string / null Yes
token_contract string / null Yes
token_id string / null Yes
token_symbol string / null Yes
decimals integer / null Yes
amount string / null Yes

UsageLimitsResponse

Field Type / values Required
rate_limit_per_minute integer Yes
max_concurrent_requests integer Yes
enabled_networks string[] Yes

UsagePeriodResponse

Field Type / values Required
start string Yes
end string Yes
used integer Yes
reserved integer Yes
limit integer Yes
remaining integer Yes

UsagePlanResponse

Field Type / values Required
code string Yes
version integer Yes
name string Yes

UsageRecentResponse

Field Type / values Required
days integer Yes
requests integer Yes
billable integer Yes
limited_data integer Yes
errors integer Yes
average_latency_ms integer / null Yes
by_network object Yes
by_overall_state object Yes

UsageResponse

Field Type / values Required
tenant_id string Yes
plan UsagePlanResponse / null Yes
limits UsageLimitsResponse Yes
period UsagePeriodResponse Yes
recent UsageRecentResponse Yes

Changelog and versioning

v1.0.0

TRON transaction checks and organization usage reporting. The developer portal provides access by invitation, API key management, plan details and a page for testing requests within your organization.

Ethereum and TON remain planned and are not accepted by the public request schema. No wallet API, webhooks, monitoring or payment execution is part of this version.

Contract evolution

The public endpoint prefix is /v1. This documentation is versioned at /docs/v1/. The downloadable OpenAPI is generated from the same request and response models as the API and checked against a committed snapshot in CI.

Integrations should tolerate additional response fields while validating the fields they consume. Do not assume an unknown reason code means a clean result. Breaking changes require a versioned migration guide; documentation links for the existing major version remain stable.

Service status and support

Access status

The public status indicator reports whether business access is enabled. It does not run a live provider check and is not an uptime guarantee. An available network describes supported functionality; individual organizations may have narrower entitlements.

A 503 api_disabled response means business checks have not been enabled or have been deliberately paused. A 503 provider_unavailable response means required transaction data could not be retrieved for that request. Preserve the request ID and follow the public retry guidance.

Contact

Contact the ChainZap team for invitations, commercial terms, incident questions and support. Include X-Request-ID, the public error code and the UTC time. Never include API keys, identity tokens or raw transaction/provider payloads.

Networks: TRON: supported. Ethereum: planned. TON: planned.

API access

Loading…

Overview