Declare a member deposit
x-on-behalf-of required
Requires x-idempotency-key
Moves funds · Self-operated entities only: increases member available and merchant.custody; does not debit merchant prepaid 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.
Only platform-operated entities (platform_hosted) may call this endpoint. External merchant_hosted merchants cannot credit member wallets and must use their actually funded merchant prepaid balance. Even previously approved deposits:write keys receive 403 insufficient_scope. The server determines the custody model; body fields such as custody_model or merchant_id cannot change authorization. This check runs before idempotent response replay. Historical deposits remain accessible through GET.
Self-operated entities must still satisfy mandatory signing, a stable business reference, single and daily limits, the asset allowlist, member risk checks, and auditing. A declaration alone is not proof of platform receipt.
The journal credits member.available and debits merchant.custody. It does not debit merchant.available and is not a merchant prepaid top-up endpoint.
Idempotency
x-idempotency-keyprotects network retries: identical requests replay within 24 hours; different bodies return 409.referenceis the stable deposit identifier inmdep:<merchant>:<reference>and permanently prevents duplicate posting. Do not generate a new reference for retries.- First posting returns 201; a previously posted reference returns 200 with
replayed: true. Amount and chain metadata come from the first saved record.
Provenance and asset allowlists
network, txid, from_address, and to_address are optional. A transaction ID or address requires a network, otherwise 400 invalid_fields. Network names are uppercased without matching the platform chain catalog. Length limits are 32, 128, 200, and 200 respectively. Transaction hashes are not unique because one transaction can contain several deposits; deduplication uses reference. Metadata alone is not verified receipt evidence.
With no allowlist rows, the enabled platform catalog applies. Once any allowlist row exists, the requested asset must explicitly be enabled for the entity or the request returns asset_not_allowed.
Failure handling
For timeouts or 504, check and retry with the original idempotency key. Use a new key after a definitive business failure. For limit_exceeded, inspect limit_type (single/daily). External merchants cannot resolve 403 by retrying or requesting approval; use merchant prepaid funding.
Prerequisites
- Only platform_hosted entities; external merchants are denied even with previously granted permission.
- 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.
Request Headers
| Field | Type | Required | Description |
|---|---|---|---|
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
| Field | Type | Required | Description |
|---|---|---|---|
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
{
"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
}{
"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
}invalid_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.signature_required: signature missing. invalid_signature: signature mismatch.
timestamp_out_of_range: timestamp outside the allowed window. nonce_reused: nonce replay.idempotency_key_reused: same key with a different request body.
idempotency_in_progress: the original request is still processing; retry later with the same key.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.
{
"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
}