The hosted gateway is the open-source OpenWAF Egress run by OpenWAF Cloud at https://egress.openwaf.dev, with agents, targets and keys managed per organization instead of a configuration file.
Gateway URL
https://egress.openwaf.dev/v1/agent/<agent name>/proxy/<target id>/<provider path>
Authorization: Bearer OWE_TOKEN_…<provider path> and the query string are appended to the target's base URL: …/proxy/openrouter/chat/completions goes to https://openrouter.ai/api/v1/chat/completions. Each agent's base_urls gives the URL for every target it may call.
Compared with a self-hosted gateway:
- The agent is identified by its token, and the URL must name that token's agent;
/v1/proxy/<target>/…and theX-OpenWAF-Agentheader are not used. - The agent's token is never forwarded upstream; the target's auth header carries the provider key instead.
- A target without a provider key is refused instead of being called without one.
- Besides the check before the call, the address actually connected to is checked again against private and reserved ranges.
- Changes to agents, targets and keys reach the gateway within seconds; a provider key is cached for up to five minutes.
The checks, detectors, body limits and timeouts are those of the open-source gateway: see the security model and the fixed limits.
Gateway errors
The gateway's own errors are JSON with an error code and a message; the provider's answers pass through unchanged.
| Status | error | Cause |
|---|---|---|
| 401 | token_required | No Authorization header |
| 401 | invalid_token | Unknown token, a rotated one, or the agent is disabled or deleted |
| 401 | unauthorized_agent | The URL does not name the token's agent |
| 403 | egress_blocked | The target is not among the agent's allowed targets (another agent of the organization may call it) |
| 403 | ssrf_blocked | The target resolves to a private or reserved address |
| 403 | injection_detected | A prompt-injection detector matched the body (Block prompt injection on) |
| 403 | data_leak_blocked | A data-leak pattern matched the body (Block data leaks on) |
| 404 | unknown_target | No agent of the organization may call that target id, or it does not exist |
| 413 | body_too_large | Over 8 MiB, or over 1 MiB of text to inspect |
| 502 | credential_resolution_failed | The target has no provider key, or it could not be read |
| 502 | upstream_error | The provider could not be reached |
| 503 | policy_unavailable | The gateway has no current policy for the request; retry later |
The 403 and 413 answers appear in blocked requests. The troubleshooting page explains each code in detail.
Management API
All routes are under https://dash.openwaf.dev/v1/orgs/{org} with an API token (Authorization: Bearer owf_…) or the dashboard session. Reads need any role of the whole organization; writes need admin of the whole organization. Members and tokens limited to a client or a zone get 403. Writes take an optional X-Change-Reason; they are not versioned configuration and refuse dry_run. The TypeScript and Python SDKs include every route except the stream.
| Route | What it does |
|---|---|
GET /egress | {enabled, gateway_url, credentials_available}: whether AI egress is on for the organization, readable even when it is not |
GET /egress/targets | The catalog, then the custom targets, each with its key's metadata (credential: fingerprint, updated_at, updated_by, or null) |
POST /egress/targets | Add a custom target: id, name, base_url, auth_header, auth_prefix |
DELETE /egress/targets/{target} | Delete a custom target and its key |
PUT /egress/targets/{target}/credential | Set or replace the key: {"secret": "…"}; answers the target with the key's fingerprint |
DELETE /egress/targets/{target}/credential | Remove the key |
GET /egress/agents, GET /egress/agents/{id} | Agents, with base_urls per allowed target and token_prefix |
POST /egress/agents | Create an agent: name, description, allowed_targets, enforce_injection, block_dlp, disabled; answers {agent, token} |
PATCH /egress/agents/{id} | Change description, allowed_targets (replaced whole), enforce_injection, block_dlp or disabled |
POST /egress/agents/{id}/rotate-token | New token; answers {agent, token} |
DELETE /egress/agents/{id} | Delete an agent |
GET /egress/events | Blocked requests, newest first, with the filters of the security events |
GET /egress/events/stream | Blocked requests as Server-Sent Events |
Reading and deleting agents, targets and keys works even when AI egress is off for the organization; every other write, and the events, answer 403 feature_disabled. The answers that carry a token or set a key have Cache-Control: no-store.
Limits and formats
| Item | Rule |
|---|---|
| Agent name | ^[a-z0-9][a-z0-9-]{0,62}$, unique in the organization, cannot change |
| Description | Up to 500 characters |
| Allowed targets | 1 to 32 target ids per agent |
| Token | OWE_TOKEN_ and 32 random bytes in base64url, shown once; only its SHA-256 and its prefix (OWE_TOKEN_ and 6 characters) are kept |
| Custom target id | ^[a-z][a-z0-9-]{1,31}$, not a catalog id |
| Custom target name | Up to 100 characters |
| Custom target URL | https://, port 443, public DNS name, no IP, credentials, query or fragment |
| Auth header, prefix | A header name of up to 64 characters; a prefix of up to 32 printable characters |
| Provider key | 1 to 8,192 characters, no spaces or control characters |
API errors
| Status | code | When |
|---|---|---|
| 403 | feature_disabled | AI egress is not enabled for the organization |
| 403 | forbidden | The role is too low, or the member or token is limited to a client or a zone |
| 404 | not_found | No such agent or target in the organization |
| 409 | conflict | An agent or a custom target with that name or id already exists |
| 409 | target_in_use | Deleting a custom target that an agent still allows |
| 422 | invalid | A field breaks the rules above (field names it), or deleting a catalog target |
| 502 | credential_store | The credential store did not accept the change; try again |
| 503 | unavailable | Provider keys cannot be stored right now (credentials_available is false) |
Catalog
| Id | Provider | Base URL | Key header |
|---|---|---|---|
openrouter | OpenRouter | https://openrouter.ai/api/v1 | Authorization: Bearer |
openai | OpenAI | https://api.openai.com/v1 | Authorization: Bearer |
anthropic | Anthropic | https://api.anthropic.com/v1 | x-api-key |
huggingface | Hugging Face | https://router.huggingface.co/v1 | Authorization: Bearer |
groq | Groq | https://api.groq.com/openai/v1 | Authorization: Bearer |
mistral | Mistral AI | https://api.mistral.ai/v1 | Authorization: Bearer |
gemini | Google Gemini (OpenAI-compatible) | https://generativelanguage.googleapis.com/v1beta/openai | Authorization: Bearer |
deepseek | DeepSeek | https://api.deepseek.com/v1 | Authorization: Bearer |
together | Together AI | https://api.together.xyz/v1 | Authorization: Bearer |
fireworks | Fireworks AI | https://api.fireworks.ai/inference/v1 | Authorization: Bearer |
cohere | Cohere | https://api.cohere.com/v2 | Authorization: Bearer |
perplexity | Perplexity | https://api.perplexity.ai | Authorization: Bearer |
xai | xAI | https://api.x.ai/v1 | Authorization: Bearer |
MCP tools
egress_status and egress_events read; egress_create_agent, egress_update_agent, egress_rotate_token, egress_delete_agent, egress_add_target, egress_remove_target and egress_remove_credential write, with confirm=false to check first and no expected_version. There is no tool to set a provider key. See the MCP tools.