API conventions: auth, error codes, rate limits and pagination
Behavior shared by every Xanguard API, collected in one place.
Response Envelope
Every response from a valid REST endpoint is wrapped in a standard envelope. On success, ok is true and the payload is in data. On error, ok is false, the human-readable message is in error, and a stable machine-readable code tells you what went wrong. Some errors add a hint (what to do next) and retry_after (seconds until a retry can succeed; on B2B and CA Search also sent as the Retry-After header).
Requests the server can't parse (bad JSON, wrong field types, body over 64 KB, unknown path) get only the HTTP status with a plain-text or empty body. The /twitter/* twitterapi.io-compatible routes use that service's shape instead (see B2B REST).
{
"ok": true,
"data": { ... }
}
{
"ok": false,
"error": "Your subscription expired on 01 Oct 2026 10:12 UTC. Renew in @B2B_Xanguard_bot; your key stays the same.",
"code": "key_expired"
}Match on code, not on the error text: messages may be reworded, codes will not change.
Authentication
All REST APIs authenticate with a Bearer token in the Authorization header:
Authorization: Bearer <prefix>_your_api_key_here
Each product issues keys with its own prefix (xg_, dt_, cw_, …) — see the API overview table for the full list and issuing bots. Keys are product-scoped, with one exception: a B2B dt_ key also works on the Tweet Alerts endpoints (/v1/accounts…, /v1/ws). B2B (/v1/dt/*), /twitter/* and CA Search additionally accept the twitterapi.io-compatible X-API-Key header. Keys are never accepted in the URL query string on REST endpoints (B2B returns 401 key_in_query; the other APIs return a generic 401).
Error Codes
Common HTTP status codes across the APIs:
| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid field |
401 | Authentication failed — missing, malformed, or invalid API key |
403 | Not allowed — plan limit reached, feature not enabled on this key, or no active paid plan |
404 | Resource not found (or no data collected yet) |
409 | Conflict — resource already exists |
429 | Rate limit or daily allowance exhausted — back off and retry |
500 | Temporary server-side failure — retry |
503 | Service temporarily unavailable — retry with backoff |
Error code values. Every enveloped error carries one; when no specific code applies it is derived from the status (bad_request, unauthorized, forbidden, not_found, conflict, rate_limited, server_error, service_unavailable). Which API sends which codes: the key_* codes come from B2B (/v1/dt/* and the B2B WebSockets) only; ip_blocked from the B2B WebSockets only; handle_limit_reached from B2B, Community Watch and Convergence Tracker; daily_limit_reached from CA Search. Tweet Alerts (/v1, /v1/ws) and the other product APIs return status-derived codes only (any key problem is 401 unauthorized; no paid plan or a full plan is 403 forbidden), and product WebSockets reject a bad key with a plain-text HTTP 401 before the upgrade.
code | Status | What it means / what to do |
|---|---|---|
key_missing | 401 | No key was sent, or it was empty. Check the variable or config that holds your key on that machine. |
key_in_query | 401 | Key sent as ?api_key= on a REST endpoint. Send it in the Authorization: Bearer (or X-API-Key) header. |
key_malformed | 401 | Not a key for this product (wrong prefix). Copy the full key from the product's bot. |
key_unknown | 401 | Key not recognised: a typo, a cut-off key, or an old key you regenerated. |
key_expired | 401 | Subscription expired (the message gives the date). Renew in the bot; the key stays the same. |
key_deactivated | 401 | Key deactivated. Contact support. |
ip_blocked | 429 | 5 failed logins (missing, wrong or expired key) from your IP within 60 seconds blocked it for 1 hour. Fix the key; the block lifts by itself at the time given. retry_after / Retry-After = seconds left. |
handle_limit_reached | 403 | Your plan's handle ceiling is full. Remove a handle or upgrade. |
rate_limited | 429 | Too many requests. Wait retry_after seconds. |
daily_limit_reached | 429 | Daily allowance used up; resets at 00:00 UTC (retry_after = seconds until then). |
service_unavailable / server_error | 503 / 5xx | A temporary problem on our side, not with your request. Retry in about 30 seconds. |
WebSocket disconnect codes (login_timeout, too_many_connections, …) are listed on the B2B WebSocket page. B2B Monitoring and CA Search document per-endpoint error tables in their own sections: B2B API and CA Search.
Pagination
Two conventions exist. B2B list endpoints paginate with limit/offset query params (e.g. /v1/dt/followers/{handle}). CA Search uses an opaque cursor: pass the previous response's next_cursor as cursor to continue a truncated result set. Consumer API lists are unpaginated and return the full set.
Rate Limits
| Surface | Limit |
|---|---|
Consumer REST (/v1) | Per account (all your keys share it), 1-second windows, scales with your plan; excess returns 429 rate_limited with retry_after: 1 |
B2B REST (/v1/dt) | Not per-second rate-limited — 64 KB request-body cap; 5 concurrent WebSocket connections per key |
CA Search (/v1/search) | 10 requests/sec and 20 requests/min per Telegram account (all your keys share it), plus the daily plan allowance |
Product APIs (/v1/cw, /v1/ct, /v1/eca, /v1/et, /v1/trending, /v1/pf, /v1/sa) | No per-second limit; plan limits (handles, entries, queries) apply |
Consumer WebSocket (/v1/ws) | 10 inbound messages/sec per connection |