Z Zise Developers 简体中文

Obtain an access token

POST /v1/connect/token scope: —
Merchant account

The first stage of the two-stage process. The only endpoint that does not require x-auth-token, and the only one without a scope requirement.

Three differences from common implementations that must be handled correctly:

  • Multiple tokens may coexist. Issuing a new token does not invalidate old ones; integrations with multiple processes need not worry about workers invalidating each other's tokens. Cache and reuse tokens until shortly before expiry rather than obtaining a new one for each call.
  • Issuance is limited to 20 requests/minute/client_id; exceeding it returns rate_limited. Frequent token issuance is itself a sign of an integration problem.
  • Token scopes are informational only. Authorization reads the current Key row for every request; administrative scope changes and Key disabling therefore take effect immediately, without waiting for token expiry.

The returned scopes are expanded: enabling members:write also includes members:read. Expansion happens during authorization rather than when stored, so later rule tightening also applies to existing Keys.

Cross-environment credentials are always rejected, with environment_mismatch rather than Invalid credentials. Comparable upstream APIs return Invalid client id here, which integrators often misdiagnose as a copied-key error. A sandbox Key resolves to a shadow merchant entity, with data separate from live: members and ledger entries created in the sandbox do not exist in live.

Key rotation has an overlap window: the old key can still obtain tokens before prev_valid_until, allowing a gradual transition. ⚠ This is a hard cutoff, not a reminder: it becomes invalid immediately at that time, without a grace period.

Prerequisites

  • The API Key is active and has not passed expires_at.
  • The Key's environment matches the hostname being called (api-sandbox.* for sandbox Keys).
  • Live Keys must have an IP allowlist, and the current outbound IP must match it.
FieldTypeRequiredDescription
x-client-id string Required The API Key's client_id, issued when creating the Key in the merchant portal.
x-api-key string Required The API Key secret. ⚠ This is not the signing key. A Key provides two values: api_key is used only here and stored by us only as a hash; signing_key is used for x-signature. Confusing them causes every signature check to fail.

Response

200Issued
{
  "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expired_at": 1786000800,
  "token_type": "Bearer",
  "scopes": [
    {
      "members": "read"
    },
    {
      "members": "write"
    },
    {
      "merchant": "read"
    }
  ]
}
400environment_mismatch: sandbox credentials used on a live host, or vice versa.
401invalid_credentials: the same response for five cases: missing headers, nonexistent client_id, disabled Key, expired Key, or incorrect secret. Distinguishing them would enable client_id probing.
429rate_limited: 20 requests per client_id per minute.
Request
curl -X POST 'https://api.zinfra.vip/v1/connect/token' \
  -H 'x-auth-token: Bearer $TOKEN'
const res = await fetch("https://api.zinfra.vip/v1/connect/token", {
  method: "POST",
  headers: {
    "x-auth-token": "Bearer $TOKEN",
  },
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();
import requests

res = requests.post(
    "https://api.zinfra.vip/v1/connect/token",
    headers={
        "x-auth-token": "Bearer $TOKEN",
    },
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zinfra.vip/v1/connect/token",
    nil)
req.Header.Set("x-auth-token", "Bearer $TOKEN")
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/connect/token"))
    .header("x-auth-token", "Bearer $TOKEN")
    .method("POST", HttpRequest.BodyPublishers.noBody())
    .build();
// Use String / BigDecimal for amounts, not double.
$ch = curl_init('https://api.zinfra.vip/v1/connect/token');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
  ],
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
200
{
  "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expired_at": 1786000800,
  "token_type": "Bearer",
  "scopes": [
    {
      "members": "read"
    },
    {
      "members": "write"
    },
    {
      "merchant": "read"
    }
  ]
}