Errors and status codes
All error responses are JSON with a short machine-readable code:
{ "error": "invalid_key" }
Some errors include extra fields (documented inline below, e.g. ca_gate_failed returns balance and minBalance). 429 responses always include a Retry-After header in seconds.
Full code table
Authentication / authorization
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 401 | missing_key | No Authorization: Bearer … header on the request. | Add the header. |
| 401 | invalid_key | Key prefix isn't ws_pub_ / ws_sec_, key doesn't exist, or key belongs to a different tracking page. | Verify the key string and the tracking page ID in the URL match. |
| 401 | revoked_key | Key has been revoked in the dashboard. | Create a new key and swap it in. |
| 403 | wrong_tier | A secret-only endpoint was called with a public key. (No public endpoints emit this — included for completeness.) | Use a secret key. |
| 401 | missing_origin | Public-key request with no Origin or Referer header. | Ensure the browser sends Origin; for cURL, pass -H 'Origin: https://your-site.example'. |
| 403 | origin_not_allowed | Origin was sent but isn't on the key's allowlist. | Add the origin in the dashboard. |
See Authentication for the full auth model.
Embedding flags
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 403 | embed_disabled | Tracking page has isPublicEnabled or embedEnabled turned off. | Enable both in the page's settings. |
| 404 | not_found | Tracking page doesn't exist (or its backing data couldn't be built). | Check the ID. |
| 404 | page_not_found | Same meaning, emitted only by POST /submissions during policy checks. | Check the ID. |
Submission payload validation
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_json | Request body wasn't valid JSON. | Fix the body. Set Content-Type: application/json. |
| 400 | missing_fields | Required fields missing or wrong type on POST /siws/nonce or POST /submissions. | See the endpoint reference for required fields. |
| 400 | missing_tracking_page_id | GET /session/submissions was called without the trackingPageId query parameter. | Add it. |
| 400 | invalid_wallet_address | walletAddress isn't a valid base58 Solana address. | Use a real address. |
| 400 | invalid_nickname_length | Nickname is empty or longer than 32 characters after normalization. | Trim to 1–32 chars. |
| 400 | invalid_nickname_chars | Nickname contains disallowed characters. | Use only letters, numbers, spaces, and . _ -. |
SIWS / nonce / signature
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_nonce | Nonce unknown, already consumed, or expired. | Request a fresh nonce and retry. |
| 400 | nonce_binding_mismatch | Nonce was issued for a different page, wallet, or API key. | Make sure the same key mints the nonce and submits it, and the wallet/page match. |
| 401 | invalid_signature | Ed25519 signature didn't verify against the exact SIWS message returned by the nonce endpoint. | Sign the message string verbatim (UTF-8 bytes). Don't reconstruct it. |
Submissions policy / state
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 409 | duplicate | This wallet already has a pending, approved, or auto_approved submission on this page. | Don't resubmit. Use GET /session/submissions to inspect state. |
| 422 | ca_gate_failed | Submitter's token balance is below the page's CA-gate minimum. Body: { error, balance, minBalance }. | Acquire more of the gated token, or ask the owner to relax the policy. |
Rate limits and cooldowns
All 429 responses include a Retry-After header in seconds. Honor it.
| HTTP | Code | Meaning |
|---|---|---|
| 429 | rate_limit_key | Per-API-key rate limit exceeded. |
| 429 | rate_limit_ip | Per-IP rate limit exceeded. |
| 429 | rate_limit_session | Session-scoped limit (nonce issuance or submissions). |
| 429 | cooldown_active | Wallet on cooldown after a prior rejection. |
| 429 | queue_full | Page has hit its pending-queue cap. |
See Cooldowns and rate limits for the full model.
Internal
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 500 | internal | Unexpected server error. | Retry with backoff. If persistent, report to the Wallet Sleuth team. |
Endpoint → codes it can emit
| Endpoint | Codes |
|---|---|
GET /api/embed/tracking/{id} | missing_key, invalid_key, revoked_key, missing_origin, origin_not_allowed, embed_disabled, not_found, rate_limit_key, rate_limit_ip |
POST /api/embed/siws/nonce | invalid_json, missing_fields, invalid_wallet_address, plus all auth codes, plus rate_limit_key, rate_limit_ip, rate_limit_session |
POST /api/embed/submissions | All auth codes, invalid_json, missing_fields, invalid_wallet_address, invalid_nickname_length, invalid_nickname_chars, invalid_nonce, nonce_binding_mismatch, invalid_signature, embed_disabled, page_not_found, duplicate, ca_gate_failed, cooldown_active, queue_full, and all rate-limit codes |
GET /api/embed/session/submissions | missing_tracking_page_id, all auth codes, rate_limit_key, rate_limit_ip |
Tips for robust error handling
- Branch on
error, not on status. Two endpoints can return the same status with different codes (e.g. 429 withrate_limit_keyvs.cooldown_active), and the right recovery action differs. - Respect
Retry-Afterstrictly. The server returns the exact window remaining. Backing off less just burns another attempt. - Treat 5xx as transient. Retry with exponential backoff and jitter. Cap the retry count — some 500s indicate a bug worth reporting.
- Don't swallow 401s. A previously-working key that starts returning
invalid_keyorrevoked_keyalmost always means a key rotation or revocation. Page someone, don't just retry. - Reveal errors to your UI carefully. Codes like
ca_gate_failedare user-actionable ("hold at least N tokens to submit"). Codes likenonce_binding_mismatchare developer-actionable; don't leak them to end users.
Related
- Authentication
- Cooldowns and rate limits
- POST /submissions — the busiest error surface.