/openapi/v1/acl/rules
List rules
curl -k -H "Authorization: Bearer $KEY" "$WAF/openapi/v1/acl/rules"Response example
Call WAF management endpoints directly with an API key — no panel login required. Ideal for automation, monitoring alerts, and CI/CD. Every request validates PRO status in real time; scopes grant fine-grained access; high-risk operations stay blocked.
https://<WAF-host>:<port>/openapi/v1/<path>
FOXWAF OpenAPI lets PRO users call WAF management endpoints via API key, fully separate from the panel's encrypted channel.
WAF="https://127.0.0.1:8088"
KEY="fwk_your-prefix_your-secret"
curl -k \
-H "Authorization: Bearer $KEY" \
"$WAF/openapi/v1/acl/rules"
-k Skips self-signed cert verification; remove in production after installing a trusted certificate.
fwk_<10-char-prefix>_<32-char-secret>
Example: fwk_lu2LAk9HFj_qL9ioS4cmjVhW7dEfBxzRpYKnuA1G
| Action | Description |
|---|---|
| Create | Set name, scopes, expiry; secret shown only once at creation; quick select all / clear / read-only |
| Disable / enable | Temporarily disable a key without deleting it |
| Revoke | Permanently delete; takes effect immediately; in-flight calls get invalid_key |
| Revoke selected | Revoke multiple selected keys at once |
| Revoke all | Revoke all keys in one action |
Long scope lists collapse automatically; click +N to expand.
/api channel, login required)These endpoints cannot be called with an OpenAPI key — prevents privilege escalation if a key leaks.
| Method | Path | Description |
|---|---|---|
GET | /api/openapi/keys | List keys |
POST | /api/openapi/keys | Create (returns plaintext secret once) |
POST | /api/openapi/keys/:id/toggle | Enable/disable; body {"enabled":true/false} |
DELETE | /api/openapi/keys/:id | Revoke one |
DELETE | /api/openapi/keys | Batch revoke body {"ids":[1,2,3]}; revoke all body {"all":true} |
| Field | Description |
|---|---|
name | Display name |
prefix | Key prefix (shown as fwk_prefix_***) |
scopes | Scope list |
enabled | Enabled |
expire_at | Expiry (Unix seconds, 0 = never) |
last_used_at | Last used (persisted asynchronously every 30s) |
At least one scope is required when creating a key.
write implies read (write grants read)* is a wildcard matching all modulesread; POST / PUT / DELETE require write| Scope | Description |
|---|---|
*:read | All modules read-only |
*:write | All modules read/write (includes read) |
acl:read / acl:write | ACL rules read / read-write |
cc:read / cc:write | CC protection read / read-write |
custom-rules:read / custom-rules:write | Custom rules read / read-write |
site:read / site:write | Site management read / read-write |
settings:read / settings:write | Global settings read / read-write |
Each module has a :write scope; use *:read or *:write if unsure.
| Path prefix | Required module |
|---|---|
/openapi/v1/acl/* | acl |
/openapi/v1/cc/* | cc |
/openapi/v1/custom-rules/* | custom-rules |
/openapi/v1/sites、/openapi/v1/site/*、/openapi/v1/health* | site |
/openapi/v1/attack/* | attack |
/openapi/v1/cache/* | cache |
/openapi/v1/rule*、/openapi/v1/rules* | rule |
/openapi/v1/monitor/*、/openapi/v1/traffic/*、/openapi/v1/stats | monitor / traffic / stats |
/openapi/v1/obs/* | obs |
/openapi/v1/cert-template* | cert-template |
/openapi/v1/plugins/* | plugins |
/openapi/v1/waf/* | waf |
/openapi/v1/settings | settings |
/openapi/v1/feedbacks | feedbacks |
/openapi/v1/letsencrypt/* | letsencrypt |
/openapi/v1/cert/* | cert |
/openapi/v1/realtime/* | realtime |
/openapi/v1/qrcode | qrcode |
/openapi/v1/ping, /openapi/v1/config, /openapi/v1/maintenance-server*, /openapi/v1/captcha/*, /openapi/v1/waf/language | Public (no scope) |
Authorization: Bearer <api_key>
Content-Type: application/json # required for POST / PUT / DELETE
OpenAPI paths map 1:1 to internal panel APIs:
/openapi/v1/<path> → /api/<path>
| OpenAPI path | Internal path |
|---|---|
/openapi/v1/acl/rules | /api/acl/rules |
/openapi/v1/cc/rules/5 | /api/cc/rules/5 |
/openapi/v1/sites | /api/sites |
Paginated endpoints require page and page_size:
curl -k -H "Authorization: Bearer $KEY" \
"$WAF/openapi/v1/attack/logs?page=1&page_size=20"
All errors use this format:
| HTTP | code | Reason |
|---|---|---|
401 | missing_token | Missing Authorization: Bearer header |
401 | invalid_key | Key not found or wrong secret |
403 | https_required | HTTP used instead of HTTPS |
403 | key_disabled | Key manually disabled |
403 | key_expired | Key past configured expiry |
403 | pro_required | PRO license expired or invalid (checked live) |
403 | scope_denied | Key scopes missing required permission |
403 | endpoint_not_exposed | Path not exposed (sensitive module) |
429 | rate_limited | Rate limited (120 requests per key per 60s) |
PRO-gated features (CC, custom rules, observation room, HTTP/3, etc.) also return {"error":"PRO license required","pro_required":true}。
| Item | Value |
|---|---|
| Granularity | Per API key |
| Window | 60-second fixed window |
| Limit | 120 requests / 60 seconds |
| Response when exceeded | HTTP 429 + {"code":"rate_limited"} |
{"message":"…"}; create endpoints include resource IDspro_required: true when PRO expiresWAF="https://127.0.0.1:8088" and KEY="fwk_…"/openapi/v1/acl/rulesList rules/openapi/v1/acl/rulesCreate rule/openapi/v1/acl/rules/:idUpdate rule/openapi/v1/acl/rules/:id/toggleEnable / disable/openapi/v1/acl/rules/:idDelete rule/openapi/v1/acl/rules
List rules
curl -k -H "Authorization: Bearer $KEY" "$WAF/openapi/v1/acl/rules"Response example
/openapi/v1/acl/rules
Create rule
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Required | global or host (per domain) |
rule_type | string | Required | ip, ua, url, header, country, etc. |
pattern | string | Required | Match pattern |
action | string | Required | block or allow |
enabled | bool | Required | Enabled |
host | string | Conditional | Required when type=host |
description | string | Optional | Notes |
# Block IP
curl -k -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"type":"global","rule_type":"ip","pattern":"1.2.3.4","action":"block","enabled":true,"description":"Malicious scan"}' \
"$WAF/openapi/v1/acl/rules"
# Block UA per host
curl -k -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"type":"host","host":"example.com","rule_type":"ua","pattern":"sqlmap","action":"block","enabled":true}' \
"$WAF/openapi/v1/acl/rules"Response
/openapi/v1/acl/rules/:id
Update rule
Request body fields same as create rule.
curl -k -X PUT -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"type":"global","rule_type":"ip","pattern":"1.2.3.4","action":"allow","enabled":true}' \
"$WAF/openapi/v1/acl/rules/10"/openapi/v1/acl/rules/:id/toggle
Enable / disable
Body: {"enabled": false}
/openapi/v1/cc/rulesList CC rules/openapi/v1/cc/rulesCreate CC rule/openapi/v1/cc/rules/:idUpdate/openapi/v1/cc/rules/:idDelete/openapi/v1/cc/statsStats overview/openapi/v1/cc/logsLogs (paginated)/openapi/v1/cc/logsDelete logs/openapi/v1/cc/clear-countersClear counters/openapi/v1/cc/rules
Create CC rule
| Field | Type | Required | Description |
|---|---|---|---|
host | string | Required | Host; * matches all |
path | string | Required | Path prefix |
threshold | int | Required | Max requests in window |
window | int | Required | Window (seconds) |
ban_duration | int | Required | Ban duration (seconds) |
action | string | Required | block or challenge |
enabled | bool | Required | Enabled |
curl -k -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"host":"*","path":"/api/login","threshold":10,"window":60,"ban_duration":300,"action":"block","enabled":true}' \
"$WAF/openapi/v1/cc/rules"/openapi/v1/cc/stats
CC stats & logs
/cc/logs requires ?page=1&page_size=20{"all": true} or {"ids": [1,2,3]}/cc/stats Response
/cc/logs Response
Write responses
/openapi/v1/custom-rulesList rules/openapi/v1/custom-rulesCreate rule/openapi/v1/custom-rules/:idUpdate/openapi/v1/custom-rules/:id/toggleToggle/openapi/v1/custom-rules/:idDelete/openapi/v1/custom-rules/exportExport ZIP/openapi/v1/custom-rules/importImport ZIP/openapi/v1/custom-rules/reloadHot reload/openapi/v1/custom-rules
Create custom rule
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Required | Rule name |
method | string | Required | HTTP method; any matches all |
action | string | Required | block |
relation | string | Required | and (all) or or (any) |
judges | array | Required | Judge conditions |
enabled | bool | Optional | Enabled |
judges[] fields
| Field | Description |
|---|---|
position | uri、parameter_key、parameter_value、request_header、request_body、any |
keyword | Keyword (one of content / rix) |
content | Content match (string / array) |
rix | Regular expression |
negate | Negate |
curl -k -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"name":"Block sensitive file probes","method":"any","action":"block","enabled":true,"relation":"or",
"judges":[{"position":"uri","keyword":"/.env"},{"position":"uri","keyword":"/.git/config"}]}' \
"$WAF/openapi/v1/custom-rules"Response
GET /custom-rules/export returns application/zipPOST /custom-rules/import form field file (ZIP) → {"message":"Custom rules imported","count":N}POST /custom-rules/reload → {"message":"Custom rules reloaded"}/openapi/v1/sitesList sites/openapi/v1/site/addAdd site/openapi/v1/site/add-with-certAdd with cert/openapi/v1/site/deleteDelete site/openapi/v1/site/:id/certificateStandard certificate/openapi/v1/site/:id/sm2-certsSM2 certificate/openapi/v1/site/statusEnable / disable/openapi/v1/site/httpsToggle HTTPS/openapi/v1/site/updateUpdate site/openapi/v1/site/update-certUpdate site cert/openapi/v1/site/:id/renew-certificateRenew cert/openapi/v1/site/:id/replace-certificateReplace cert/openapi/v1/site/:id/remove-certificateRemove cert/openapi/v1/site/:id/sm2-certsBind SM2 certs/openapi/v1/site/:id/sm2-certsUnbind SM2/openapi/v1/healthAll sites health/openapi/v1/health/:idSingle site health/openapi/v1/sites
List sites
/openapi/v1/site/add
Add site
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Site name |
domain | string | Yes | Domain(s); comma, semicolon, or newline separated |
target_url | string | No | Upstream target URL |
enable_https | bool | No | Enable HTTPS (auto self-signed cert) |
force_https_redirect | bool | No | Force HTTP→HTTPS redirect |
enable_http2_upstream | bool | No | HTTP/2 upstream (default true) |
enable_anti_devtools etc. | bool | No | Pro: anti-DevTools, anti-crawler, UA check, response inspection, cache, LB |
curl -k -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-X POST "$WAF/openapi/v1/site/add" \
-d '{"name":"Main site","domain":"example.com","target_url":"http://127.0.0.1:8080"}'site:write. Free tier: up to 2 sites; Pro: unlimited.POST /site/delete body {"id":1} → {"message":"Site deleted"}POST /site/add-with-cert uses multipart/form-data with cert_file and key_file/openapi/v1/site/status
Site status & certificates
/openapi/v1/site/status{"id":1,"status":1}{"message":"Site enabled","status":1}status: 1=enabled, 0=disabled
/openapi/v1/site/https{"id":1,"enable_https":true}{"message":"Site certificate updated","cert_type":"..."}/openapi/v1/site/update{"message":"Site updated"}/openapi/v1/site/update-cert{"id":1,"cert_name":"..."} or PEM text{"message":"Site certificate updated"}/openapi/v1/site/:id/renew-certificateNo body. Re-issue self-signed certificate.
/openapi/v1/site/:id/replace-certificatemultipart: cert_file + key_file. Replace PEM certificate.
/openapi/v1/site/:id/remove-certificateDisable HTTPS without deleting cert store files.
/openapi/v1/site/:id/sm2-certs{"sign_cert_id":5,"enc_cert_id":6}/openapi/v1/site/:id/sm2-certsRemove SM2 binding.
/openapi/v1/health
Health check
/openapi/v1/attack/logsList logs/openapi/v1/attack/logs/:idSingle entry/openapi/v1/attack/statsStats overview/openapi/v1/attack/ip-groupsGroup by IP/openapi/v1/attack/exportExport JSON/CSV/openapi/v1/attack/logsDelete logs/openapi/v1/attack/logs
Query attack logs
| Parameter | Description |
|---|---|
page | Page (required, min=1) |
page_size | Page size (required, 1–100) |
host | Filter by host |
client_ip | Filter by client IP |
method | Filter by HTTP method |
rule_id | Filter by rule ID |
start_time / end_time | Time range (Unix seconds) |
curl -k -H "Authorization: Bearer $KEY" \
"$WAF/openapi/v1/attack/logs?page=1&page_size=20&host=example.com"/openapi/v1/attack/stats
Stats & export
/attack/export?format=json or format=csv returns a file streamDELETE /attack/logs → {"message":"Deleted","affected":N}/openapi/v1/cache/statsCache stats/openapi/v1/cache/stats/detailDetailed stats/openapi/v1/cache/filesFile list/openapi/v1/cache/filesRead file content/openapi/v1/cache/configUpdate config/openapi/v1/cache/clearClear cache/openapi/v1/cache/files/deleteDelete files/openapi/v1/cache/js-obfuscateStart JS obfuscation/openapi/v1/cache/js-obfuscate/statusObfuscation progress/openapi/v1/cache/config
Update cache config
curl -k -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"enabled": true, "ttl": 3600}' "$WAF/openapi/v1/cache/config"POST /cache/files body: {"file": "/path"}/cache/clear all sites → {"message":"All cache cleared"}; per site → {"message":"Site cache cleared","host":"..."}POST /cache/js-obfuscate body {"host":"example.com"} → {"message":"JS obfuscation started","task_id":"..."}GET /cache/js-obfuscate/status?host=... returns progress percentage/openapi/v1/rule/blockRuleIdDisabled rule IDs/openapi/v1/rules/reloadHot reload WAF rules/openapi/v1/rules/statusBatch update rule status/openapi/v1/rule/blockRuleId→{"id": [], "rules": []}/openapi/v1/rules/reload→{"message": "Rules reloaded", "preserved_status": 609}/openapi/v1/rules/status→{"message": "Rule status updated"}/openapi/v1/monitor/systemCPU / memory / load/openapi/v1/monitor/connectionsConnections/openapi/v1/monitor/historyHistory/openapi/v1/traffic/trendTraffic trend/openapi/v1/statsWAF global stats/openapi/v1/monitor/system
System metrics
/openapi/v1/stats
WAF global stats
/openapi/v1/obs/configObservation config/openapi/v1/obs/configUpdate config/openapi/v1/obs/rulesObservation rules/openapi/v1/obs/rulesCreate rule/openapi/v1/obs/rules/:idUpdate rule/openapi/v1/obs/rules/:idDelete rule/openapi/v1/obs/recordsObservation records/openapi/v1/obs/records/:idRecord detail/openapi/v1/obs/records/:id/rawRaw request/response/openapi/v1/obs/records/clearClear records/openapi/v1/obs/statsObservation stats{"error":"PRO license required","pro_required":true}{"message":"…"}/openapi/v1/pluginsInstalled plugins/openapi/v1/plugins/configPlugin config/openapi/v1/plugins/configUpdate config/openapi/v1/plugins/reloadReload plugins/openapi/v1/plugins/:name/toggleToggle plugin/openapi/v1/plugins/:name/orderReorder/openapi/v1/plugins/:name/sitesSite scope/openapi/v1/plugins/:nameDelete plugin/openapi/v1/plugins/uploadUpload zip/openapi/v1/plugins/import-urlImport from URL/openapi/v1/plugins/marketplaceMarketplace/openapi/v1/plugins/events/recentRecent events/openapi/v1/plugins/events/statsEvent stats/openapi/v1/plugins/events/wsEvents WebSocket/openapi/v1/plugins/events/clearClear eventscurl -k -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"enabled": false}' "$WAF/openapi/v1/plugins/ai-shield-1.0.0/toggle"toggle → {"message":"Plugin ai-shield-1.0.0 disabled"}reload → {"message":"Plugin config reloaded"}PUT /plugins/:name/sites body {"sites":[1,2]} (empty array = global)/upload form field file (zip); /import-url body {"url":"https://..."}/events/ws is a WebSocket stream (AES-GCM encrypted frames) — not recommended for scripts/openapi/v1/cert-templatesList templates/openapi/v1/cert-templateCreate template/openapi/v1/cert-template/:idTemplate detail/openapi/v1/cert-template/:idUpdate template/openapi/v1/cert-template/:idDelete template/openapi/v1/cert/uploadUpload to store/openapi/v1/cert/reloadHot reload certsPOST /cert/upload: multipart/form-data with cert_file, key_file, optional name; auto-detects RSA/ECDSA/SM2POST /cert/reload → {"message":"Certificates reloaded"} (loads all certs into TLS engine)/openapi/v1/waf/http3HTTP/3 status/openapi/v1/waf/http3Enable/disable HTTP/3/openapi/v1/waf/tls-versionsTLS versions/openapi/v1/waf/tls-versionsUpdate TLS versions/openapi/v1/waf/cc-challenge/generateGenerate CC challenge/openapi/v1/waf/cc-challenge/verifyVerify CC challenge/openapi/v1/waf/languagePanel language (public)curl -k -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"enabled": true}' "$WAF/openapi/v1/waf/http3"/openapi/v1/waf/http3→{"enabled": true}/openapi/v1/waf/http3→{"success": true, "enabled": true, "message": "HTTP/3 enabled (UDP :443)"}/openapi/v1/waf/tls-versions{"versions": ["1.2","1.3"]}/openapi/v1/settingsGet settings/openapi/v1/settingsUpdate settings/openapi/v1/settings→{"settings": {"base64_depth": 2, "url_depth": 2, "unicode_depth": 2, "rule_match_rate": 100}}/openapi/v1/settings→{"message": "Settings updated"}/openapi/v1/feedbacksFeedback list (paginated)/openapi/v1/feedbacks/:id/statusUpdate status/openapi/v1/feedbacksDelete feedback/openapi/v1/letsencrypt/alertsExpiry alerts/openapi/v1/letsencrypt/listPending orders/openapi/v1/letsencrypt/status/:idOrder status/openapi/v1/letsencrypt/check-dns/:idCheck DNS/openapi/v1/letsencrypt/orderCreate order/openapi/v1/letsencrypt/saveSave certificate/openapi/v1/letsencrypt/renew-orderRenew/openapi/v1/letsencrypt/cancel/:idCancel order/openapi/v1/letsencrypt/dismiss-alert/:idDismiss alertcurl -k -H "Authorization: Bearer $KEY" "$WAF/openapi/v1/letsencrypt/alerts"Shares endpoints with 8.8 Monitoring and 8.4 Site health checks:
/openapi/v1/statsWAF global stats/openapi/v1/healthAll sites health/openapi/v1/health/:idSingle site health/openapi/v1/realtime/wsRealtime WebSocketUsed for panel health checks, config bootstrap, captcha, etc. No Authorization header required.
/openapi/v1/pingHealth probe → pong/openapi/v1/configPublic panel config/openapi/v1/maintenance-server-statusMaintenance server status/openapi/v1/maintenance-serverMaintenance server URL/openapi/v1/captcha/challengeSlider captcha challenge/openapi/v1/captcha/verifyVerify slider captcha/openapi/v1/qrcodeQR code image (data URI)curl -k "https://127.0.0.1:8088/openapi/v1/ping"
# → "pong"POST /captcha/verify body: {"challenge_id":"...","user_ratio":0.5,"trail":[...],"duration_ms":N}These paths are never exposed, even with *:write — returns endpoint_not_exposed (403):
| Prefix | Reason |
|---|---|
/openapi/v1/update/* | Upgrade / restart / rollback — high risk |
/openapi/v1/panel/* | Panel config (port, TLS, password) — high risk |
/openapi/v1/license/* | License activation / migration |
/openapi/v1/order* | Orders (order-config, order-list, etc.) |
/openapi/v1/internal/* | Internal APIs |
/openapi/v1/openapi/* | Blocks API keys from managing keys (anti-escalation) |
pro_required (403)last_used_at is persisted every 30s to track key activityThis reference reflects the current FOXWAF release; behavior may vary by deployed version.