Error Code Reference
Business code = 0 means success; non-zero codes are module-specific. Every error response uses:
{ "code": <business_code>, "message": "<human readable>" }
This page lists all business error codes returned by Cloud WAF (mirroring pkg/errors/codes.go as the source of truth). Third-party integrators should branch on code and must not parse message text — the text is i18n / version dependent.
HTTP status vs business code
The platform uses a two-layer error model:
| Layer |
Field |
Purpose |
| HTTP |
status code |
Protocol layer (4xx client error, 5xx server error); suitable for L4 LB / gateway logic |
| Business |
code (in JSON body) |
Application-level discriminator; the only reliable "what went wrong" signal |
Conventions:
code = 0 always implies HTTP 200.
code != 0 usually maps to HTTP 4xx / 5xx, but a few idempotent endpoints (e.g. revoking an already-revoked API key) return HTTP 200 with a non-zero code.
- HTTP 401 (credential invalid) deliberately collapses every "not authenticated" cause into one response to avoid side-channel leakage.
- HTTP 403 (authenticated but unauthorized) carries a specific business code (1011 / 1052 / 1053 / 1056 …) for client-side disambiguation.
Common HTTP errors (not business codes)
Returned when a request never reaches business logic (validation, protocol, auth).
| code |
HTTP |
Meaning |
Action |
| 400 |
400 |
Bad request (JSON parse fail / missing required field) |
Fix payload, retry |
| 401 |
401 |
Unauthenticated (missing/invalid/expired token) |
Re-login or rotate API key |
| 403 |
403 |
Authenticated but lacks permission |
Request perm or use another account |
| 404 |
404 |
Resource not found |
Check path / id |
| 409 |
409 |
Conflict (e.g. duplicate create) |
Rename or delete the existing resource first |
| 500 |
500 |
Internal server error |
Retry once; escalate to ops if persistent |
Module code ranges
| Module |
Range |
Notes |
| sys |
1000–1999 |
User / role / OEM / session / API Key |
| guard |
2000–2999 |
Domain / cert / policy / rules / IP set |
| node |
3000–3999 |
Node / node group / IP / ACL policy |
| chart |
4000–4999 |
Analytics chart query & export |
| alert |
5000–5999 |
Alert task / record / contact |
| zdns |
6000–6999 |
DNS domain / record |
| notify |
7000–7999 |
Notify channel / template |
| report |
8000–8999 |
Report task / file |
| assistant |
9000–9999 |
AI assistant / session / KB / MCP tool |
| geoip |
10000–10999 |
GeoIP lookup |
| channel |
11000–11999 |
Channel partner onboarding |
sys module (1000–1999)
Users & authentication (1001–1011)
| code |
HTTP |
Meaning |
Action |
| 1001 |
404 |
User not found |
Check username / id |
| 1002 |
401 |
Invalid password |
Re-enter; repeated failures lock the account |
| 1003 |
403 |
User locked |
Contact admin to unlock |
| 1004 |
400 |
Invalid or expired captcha |
Refresh captcha and retry |
| 1005 |
401 |
Session expired |
Re-login |
| 1006 |
409 |
Username already exists |
Choose another name |
| 1007 |
404 |
Role not found |
Check role id |
| 1008 |
409 |
Role name already exists |
Choose another name |
| 1009 |
403 |
Cannot delete this role (system built-in or in use) |
Detach references first |
| 1010 |
400 |
Old password incorrect (during change-password) |
Re-enter old password |
| 1011 |
403 |
Permission denied |
Request the corresponding perm |
Settings & OEM (1012, 1020–1023)
| code |
HTTP |
Meaning |
Action |
| 1012 |
403 |
This setting is not modifiable via API (CLI / config file only) |
Use ops channel |
| 1020 |
404 |
OEM not found |
Check OEM id |
| 1021 |
409 |
OEM hostname already exists |
Use a different hostname |
| 1022 |
403 |
Cannot delete the default OEM |
Default OEM is protected |
| 1023 |
400 |
Invalid logo file (format / size) |
Upload a compliant image |
Orders & plans (1030–1033, 1040)
| code |
HTTP |
Meaning |
Action |
| 1030 |
404 |
Order not found |
Check order id |
| 1031 |
400 |
Invalid order status transition |
Follow the state machine |
| 1032 |
404 |
Plan not found |
Check plan id |
| 1033 |
409 |
Plan name already exists |
Rename |
| 1040 |
404 |
Announcement not found |
Check announcement id |
API Key sub-module (1050–1059)
| code |
HTTP |
Meaning |
Retryable |
Re-issue needed |
| 1050 |
401 |
API Key invalid / expired / revoked |
No |
Yes |
| 1051 |
404 |
API Key not found or out of visible scope |
No |
N/A |
| 1052 |
400 |
Requested scope exceeds the user's RBAC boundary |
No |
Yes (re-issue with narrower scope) |
| 1053 |
403 |
API Key scope insufficient for the target endpoint |
No |
Maybe (re-issue with broader scope, or use the user-token channel) |
| 1054 |
400 |
expires_in_days exceeds the upper bound of 365 |
No |
N/A |
| 1055 |
400 |
API Key name is required |
No |
N/A |
| 1056 |
403 |
Cross-OEM / cannot operate on another user's API Key |
No |
N/A |
Scope matching: exact string equality, no wildcard expansion. Putting guard.* into a scope matches nothing — list all leaf perm keys explicitly when you want full module access.
guard module (2000–2999)
Domains & policies (2001–2010)
| code |
HTTP |
Meaning |
Action |
| 2001 |
404 |
Domain not found |
Check domain id |
| 2002 |
409 |
Domain already exists |
Rename or delete the existing one |
| 2003 |
404 |
Policy not found |
Check policy id |
| 2004 |
409 |
Policy name already exists |
Rename |
| 2005 |
404 |
Certificate not found |
Check cert id |
| 2006 |
409 |
Certificate name already exists |
Rename |
| 2007 |
400 |
Invalid certificate PEM |
Paste the full PEM (with BEGIN/END markers) |
| 2008 |
409 |
Certificate already bound to this domain |
Skip the bind |
| 2009 |
403 |
Policy is in use, cannot delete |
Detach domains first |
| 2010 |
403 |
Domain has active nodes, cannot delete |
Disable the domain first |
IP sets & rules (2011–2019)
| code |
HTTP |
Meaning |
Action |
| 2011 |
404 |
IP set not found |
Check set id |
| 2012 |
409 |
IP set name already exists |
Rename |
| 2013 |
409 |
IP already in the set |
Skip the add |
| 2014 |
404 |
WAF rule group not found |
Check group id |
| 2015 |
404 |
Forward rule not found |
Check forward id |
| 2016 |
404 |
Schedule group not found |
Check schedule id |
| 2017 |
403 |
Cannot delete the default schedule |
System-protected |
| 2018 |
404 |
CC rule not found |
Check rule id |
| 2019 |
404 |
ACL rule not found |
Check rule id |
node module (3000–3999)
| code |
HTTP |
Meaning |
Action |
| 3001 |
404 |
Node not found |
Check node id |
| 3002 |
409 |
Node name already exists |
Rename |
| 3003 |
404 |
IP address not found |
Check IP id |
| 3004 |
409 |
IP address already exists |
Skip |
| 3005 |
404 |
Node group not found |
Check group id |
| 3006 |
409 |
Node group name already exists |
Rename |
| 3007 |
404 |
ACL policy not found |
Check policy id |
| 3008 |
409 |
ACL policy name already exists |
Rename |
| 3009 |
404 |
ACL rule not found |
Check rule id |
| 3010 |
403 |
ACL policy still has rules, cannot delete |
Clear rules first |
chart module (4000–4999)
| code |
HTTP |
Meaning |
Action |
| 4001 |
500 |
Chart query failed (PG / ES error) |
Retry once; escalate if persistent |
| 4002 |
400 |
Invalid time range (stime > etime / span too wide) |
Narrow the range |
| 4003 |
400 |
Invalid field (not in whitelist / typo) |
Verify field spelling |
| 4004 |
500 |
Export failed |
Reduce data volume and retry |
| 4005 |
429 |
Rate limited |
Back off and retry |
alert module (5000–5999)
| code |
HTTP |
Meaning |
Action |
| 5001 |
404 |
Alert task not found |
Check task id |
| 5002 |
409 |
Alert task name already exists |
Rename |
| 5003 |
404 |
Alert record not found |
Check record id |
| 5004 |
404 |
Alert contact not found |
Check contact id |
| 5005 |
409 |
Alert contact already exists |
Skip |
| 5006 |
404 |
Alert config not found |
Check config id |
| 5007 |
400 |
Invalid alert type |
Use a valid enum value |
| 5008 |
429 |
Alert in cooldown (debounce) |
Wait for cooldown to elapse |
| 5009 |
429 |
Alert quota exceeded |
Raise the limit or wait for the next cycle |
| 5010 |
500 |
Alert toggle failed |
Retry |
zdns module (6000–6999)
| code |
HTTP |
Meaning |
Action |
| 6001 |
404 |
DNS domain not found |
Check domain id |
| 6002 |
409 |
DNS domain already exists |
Rename |
| 6003 |
404 |
DNS record not found |
Check record id |
| 6004 |
404 |
DNS operation not found |
Check op id |
| 6005 |
400 |
Invalid DNS record type |
Use A / AAAA / CNAME / MX / TXT … |
notify module (7000–7999)
| code |
HTTP |
Meaning |
Action |
| 7001 |
404 |
Notify channel not found |
Check channel id |
| 7002 |
409 |
Notify channel name already exists |
Rename |
| 7003 |
404 |
Notify template not found |
Check template id |
| 7004 |
409 |
Notify template name already exists |
Rename |
| 7005 |
500 |
Notification send failed |
Verify channel connectivity, retry |
report module (8000–8999)
| code |
HTTP |
Meaning |
Action |
| 8001 |
404 |
Report task not found |
Check task id |
| 8002 |
409 |
Report task name already exists |
Rename |
| 8003 |
404 |
Report file not found or expired |
Regenerate |
| 8004 |
404 |
Report record not found |
Check record id |
| 8005 |
500 |
Report generation failed |
Narrow time range / fields and retry |
assistant module (9000–9999)
| code |
HTTP |
Meaning |
Action |
| 9001 |
404 |
AI session not found |
Check session id |
| 9002 |
403 |
AI session is archived |
Unarchive or open a new session |
| 9003 |
500 |
Message operation failed |
Retry |
| 9004 |
404 |
MCP tool not found |
Check tool id |
| 9005 |
409 |
Tool name already exists |
Rename |
| 9006 |
404 |
Knowledge base not found |
Check kb id |
| 9007 |
404 |
Document not found |
Check doc id |
| 9008 |
404 |
AI config not found |
Check config id |
| 9009 |
409 |
Knowledge base name already exists |
Rename |
| 9010 |
500 |
Document indexing failed |
Verify document format and retry |
geoip module (10000–10999)
| code |
HTTP |
Meaning |
Action |
| 10001 |
500 |
GeoIP query failed |
Retry once |
| 10002 |
404 |
IP not in GeoIP database (private / unlisted) |
Query a public IP |
channel module (11000–11999)
| code |
HTTP |
Meaning |
Action |
| 11001 |
404 |
Onboard flow not found |
Check onboard id |
| 11002 |
410 |
Link expired |
Request a new link |
| 11003 |
400 |
Invalid link token |
Request a new link |
| 11004 |
409 |
Domain already exists |
Rename or delete the existing one |
| 11005 |
500 |
Domain detect failed |
Verify DNS resolution and retry |
| 11006 |
400 |
Invalid certificate |
Paste the full PEM |
| 11007 |
400 |
Domains not ready for confirmation |
Wait for domain detect / cert install to complete |
Cross-module conventions
- HTTP 401 → credential invalid (single code by design, no side-channel leak)
- HTTP 403 → authenticated but lacks ownership / permission; business code disambiguates (1011 / 1052 / 1053 / 1056 / 1009 / 1022 / 2017 / 3010 …)
- HTTP 404 → resource not found or out of visible scope (e.g. 1001 / 1020 / 2001 / 3001 …)
- HTTP 409 → uniqueness conflict (e.g. 1006 / 1021 / 2002 / 3002 …)
- HTTP 429 → rate limited, includes
Retry-After header (4005 / 5008 / 5009)
- HTTP 5xx → server-side fault, recommend exponential backoff (10ms → 100ms → 1s → 10s)
Client handling pattern
# Python pseudo-code: branch on business code, not HTTP status
import requests
from time import sleep
resp = requests.get(api_url, headers={"Authorization": f"ApiKey {key}"})
body = resp.json()
code = body.get("code", -1)
if code == 0:
return body["data"]
elif code == 1050:
raise Exception("API Key invalid, please re-issue")
elif code == 1052:
raise Exception(f"scope exceeded: {body['message']}")
elif code in (4005, 5008, 5009):
sleep(int(resp.headers.get("Retry-After", "60")))
return retry()
elif code >= 5000 and resp.status_code >= 500:
return exponential_backoff_retry()
else:
raise Exception(f"business error {code}: {body['message']}")
Always branch on code. Never parse message text — it changes with i18n and version.