Z Zise Developers 简体中文

Declare a member deposit

POST /v1/deposits scope: deposits:write
On behalf of a member · x-on-behalf-of required Requires x-idempotency-key Moves funds · Increases member available balance and merchant.custody by the same amount, recording the liability we track for you.
This endpoint moves funds

See the response table below for failure handling. Retry timeouts (504) with the same idempotency key — we may already have processed the request; use a new key after a business failure; the same key replays that failure.

The only endpoint in the system that creates member balance from a declaration. All six gates are required:

  1. Mandatory signature, even when this Key has signature_required=false. Missing x-signature / x-timestamp / x-nonce always returns 401. Timestamp tolerance is ±300 seconds, and nonces cannot be replayed.
  2. The idempotency key must carry a tenant prefix, and the merchant must supply a business reference (reference). Reporting the same on-chain receipt twice would otherwise double-credit it.
  3. Per-transaction and daily cumulative limits, configured per merchant by central administration.
  4. The asset must be allowed for the merchant; unknown assets are rejected rather than left pending.
  5. Platform risk controls and eligibility still apply; deposit declaration does not bypass them.
  6. Full audit trail and visibility in the merchant portal.

The journal has two legs: member.available credit W / merchant.custody debit W. merchant.available does not participate; declaring a deposit is not a business cost.

Two keys: do not confuse them

  • x-idempotency-key, a UUID in the header, protects against network retries: the same key and body replay the original result for 24 hours; the same key with a different body returns 409.
  • reference in the body protects against reporting one on-chain receipt twice: it forms the ledger idempotency key mdep:<商户>:<reference>, regardless of whether you change the request idempotency key. reference must therefore be a stable identifier for that receipt, such as txid + vout or your deposit order number. Do not generate a new one on every call.

A duplicate returns 200 + replayed: true; the first successful posting returns 201. replayed tells you the deposit was already recorded without comparing balances.

On-chain provenance: four optional fields, persisted when supplied

network / txid / from_address / to_address document the funds' origin. Under merchant_hosted, we do not observe the chain, so only you can supply them. Without them, our ledger's sole trace for the newly created member balance is your reference. This blocks three situations: a member denies depositing while you say they did, leaving us unable to substantiate either account; a regulator asks for the source and we have no txid; or the deposit needs an originating address and there is no from_address to attach.

  • All four are optional: offline deposits, such as fiat receipts or internal transfers, genuinely have none.
  • However, txid or either address requires network, otherwise returns 400 invalid_fields. A txid without a known chain is effectively no txid: it cannot be checked in an explorer or used to answer an inquiry, despite appearing to provide provenance. The same 0x… string on BSC / ETH / Polygon identifies three different accounts.
  • network has no allowlist: this chain is part of your own receiving infrastructure. Restricting it to our chain catalog would prevent truthful declarations until we added each new chain. It is automatically uppercased.
  • On repeated declarations with the same reference, provenance follows the same rule as amounts: the first submission wins. Later values do not overwrite it. The response echoes the stored values, not the latest input; echoing input would falsely suggest a new txid was accepted.

⚠ txid is not deduplicated, and should not be. One chain transaction can contain multiple deposits, such as several UTXO vout outputs, ERC-20 batch transfers, or exchange consolidations. Deduplication always uses reference.

Allowlist semantics

No rows means the merchant has not enabled an allowlist, so the platform asset catalog applies. Once any row exists, each asset must match its own enabled row. An absent asset row means rejected, not no configured limit.

Handling failures

  • On 504 / network timeout, retry with the same x-idempotency-key; we may already have posted the deposit.
  • On a definite 4xx, use a new key; the same key replays the failure unchanged.
  • On limit_exceeded, inspect limit_type: single for per-transaction or daily for daily cumulative limits. Retrying cannot resolve it; check limits in the merchant portal.

Prerequisites

  • The request is signed; mandatory here regardless of the Key's signature_required setting.
  • The asset is in the platform catalog and enabled; if the merchant has an allowlist, the asset must also have a matching enabled row.
  • The member is not banned, blocklisted, or frozen; platform eligibility checks still apply.
FieldTypeRequiredDescription
x-on-behalf-of string Required The member to credit with this deposit.
x-idempotency-key string Required UUID. An invalid format immediately returns 400 idempotency_key_invalid.
x-signature string Required HMAC signature. Signature enforcement cannot be disabled for this endpoint, regardless of the Key's signature_required setting.
x-timestamp string Required Unix seconds. Tolerance ±300 seconds; outside the window returns timestamp_out_of_range.
x-nonce string Required Single-use random string. Reuse within the replay-protection window returns nonce_reused.

Request Body

FieldTypeRequiredDescription
asset string Required Asset code. Automatically uppercased, so usdt and USDT are equivalent. Assets outside the catalog/allowlist always return asset_not_allowed.
amount string Required Amount to credit, required to be > 0. Fixed-point string using the asset's ledger_scale. If there are more decimal places than scale and the excess digits are not all 0, returns 400 immediately. We do not round; you must decide the amount yourself.For USDT (ledger_scale=6), for example 1500.000000.
reference string Required Your stable business reference for this deposit, ≤ 120 characters. It forms the ledger idempotency key and is the sole basis for crediting the same funds only once. ⚠ Generating a new value on each call completely defeats this protection. ⚠ It is also the path parameter of GET /v1/deposits/{id}. For references containing /, use GET /v1/deposits?reference= instead. Slashes delimit path segments, and escaping them is unreliable.
network string Optional Machine-readable chain code, such as BSC / TRON / ETH. Optional, automatically uppercased, ≤ 32 characters, with no allowlist validation. ⚠ Required if txid or either address is supplied.
txid string Optional On-chain transaction hash, ≤ 128 characters. Optional. Multiple deposits may share a txid, such as multiple UTXO vout outputs or batch transfers.
from_address string Optional Originating address identifying where the funds came from, ≤ 200 characters. Optional. This is where the deposit source address belongs in our model. There is no separate registration endpoint, because it inherently belongs to this deposit.
to_address string Optional The receiving address on your side, ≤ 200 characters. Optional.

Response

200Idempotency hit: this deposit was already recorded; it was not posted again on this call. The four chain fields contain the stored original values, not the values submitted this time.
{
  "id": "dep_0xa3f1c2e9-0",
  "reference": "0xa3f1c2e9-0",
  "external_member_id": "u_10023",
  "asset": "USDT",
  "amount": "1500.000000",
  "ledger_scale": 6,
  "status": "credited",
  "network": "BSC",
  "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
  "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
  "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "replayed": true
}
201Credited for the first time.
{
  "id": "dep_0xa3f1c2e9-0",
  "reference": "0xa3f1c2e9-0",
  "external_member_id": "u_10023",
  "asset": "USDT",
  "amount": "1500.000000",
  "ledger_scale": 6,
  "status": "credited",
  "network": "BSC",
  "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
  "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
  "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "replayed": false
}
400invalid_request: missing fields, invalid amount, or reference longer than 120 characters. invalid_fields: chain fields too long, or txid/address supplied without network. asset_not_allowed: asset outside the catalog or merchant allowlist. limit_exceeded: limit reached; inspect limit_type = single or daily and limit_scope = merchant. request_rejected: member blocked by platform eligibility checks; reason is not disclosed. insufficient_balance: a balance CHECK constraint failed during posting. idempotency_key_required / idempotency_key_invalid: idempotency-key issue.
401signature_required: signature missing. invalid_signature: signature mismatch. timestamp_out_of_range: timestamp outside the allowed window. nonce_reused: nonce replay.
409idempotency_key_reused: same key with a different request body. idempotency_in_progress: the original request is still processing; retry later with the same key.
Request
curl -X POST 'https://api.zinfra.vip/v1/deposits' \
  -H 'x-auth-token: Bearer $TOKEN' \
  -H 'x-on-behalf-of: $MEMBER_ID' \
  -H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "asset": "USDT",
    "amount": "1500.000000",
    "reference": "0xa3f1c2e9-0",
    "network": "BSC",
    "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
    "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
    "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  }'
const res = await fetch("https://api.zinfra.vip/v1/deposits", {
  method: "POST",
  headers: {
    "x-auth-token": "Bearer $TOKEN",
    "x-on-behalf-of": "$MEMBER_ID",
    "x-idempotency-key": "$IDEMPOTENCY_KEY",
    "content-type": "application/json",
  },
  body: JSON.stringify({
    "asset": "USDT",
    "amount": "1500.000000",
    "reference": "0xa3f1c2e9-0",
    "network": "BSC",
    "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
    "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
    "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
  }),
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();
import requests

res = requests.post(
    "https://api.zinfra.vip/v1/deposits",
    headers={
        "x-auth-token": "Bearer $TOKEN",
        "x-on-behalf-of": "$MEMBER_ID",
        "x-idempotency-key": "$IDEMPOTENCY_KEY",
        "content-type": "application/json",
    },
    json={
      "asset": "USDT",
      "amount": "1500.000000",
      "reference": "0xa3f1c2e9-0",
      "network": "BSC",
      "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
      "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
      "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
    },
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zinfra.vip/v1/deposits",
    strings.NewReader(`{
  "asset": "USDT",
  "amount": "1500.000000",
  "reference": "0xa3f1c2e9-0",
  "network": "BSC",
  "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
  "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
  "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
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/deposits"))
    .header("x-auth-token", "Bearer $TOKEN")
    .header("x-on-behalf-of", "$MEMBER_ID")
    .header("x-idempotency-key", "$IDEMPOTENCY_KEY")
    .header("content-type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "asset": "USDT",
  "amount": "1500.000000",
  "reference": "0xa3f1c2e9-0",
  "network": "BSC",
  "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
  "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
  "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}
"""))
    .build();
// Use String / BigDecimal for amounts, not double.
$ch = curl_init('https://api.zinfra.vip/v1/deposits');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
    'x-on-behalf-of: $MEMBER_ID',
    'x-idempotency-key: $IDEMPOTENCY_KEY',
    'content-type: application/json',
  ],
  CURLOPT_POSTFIELDS => <<<'JSON'
{
  "asset": "USDT",
  "amount": "1500.000000",
  "reference": "0xa3f1c2e9-0",
  "network": "BSC",
  "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
  "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
  "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
}
JSON,
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
200
{
  "id": "dep_0xa3f1c2e9-0",
  "reference": "0xa3f1c2e9-0",
  "external_member_id": "u_10023",
  "asset": "USDT",
  "amount": "1500.000000",
  "ledger_scale": 6,
  "status": "credited",
  "network": "BSC",
  "txid": "0xa3f1c2b7d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
  "from_address": "0x9C3f1B7a5E2d4C6b8A0f2E4d6C8b0A2f4E6d8C0b",
  "to_address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "replayed": true
}