API Reference
API Reference — Upstream Headers, Variables & Integration
The Quayel Gateway enriches every request with IP intelligence, security decisions, and metadata. This data is available through:
- Upstream request headers — forwarded to your backend servers
- Condition paths — used in firewall, rate limit, cache, and LB rules
- JSONL log fields — recorded in access logs
- Response headers — sent back to clients
Upstream request headers
These headers are automatically added to requests forwarded to backend servers.
Standard proxy headers
| Header | Value | Description |
|---|---|---|
X-Forwarded-For | client_ip | Client IP appended to existing chain |
X-Forwarded-Proto | http / https | Original request scheme |
X-Forwarded-Host | host | Original request host |
Client IP resolution
The gateway resolves the real client IP based on the client_ip configuration:
Config source | Behavior |
|---|---|
peer_ip | Uses direct TCP connection IP |
header | Reads IP from specified header (e.g., CF-Connecting-IP) |
header_index | Reads IP from Nth entry in comma-separated header (e.g., X-Forwarded-For) |
The resolved client_ip is used for all IP intel lookups and rate limiting.
Condition paths
These paths can be used in the left field of any condition (firewall, rate limiting, cache, load balancer).
Request fields
| Path | Type | Description | Example Value |
|---|---|---|---|
req.host / http.host | string | Host header | "api.example.com" |
req.path / http.path | string | Request path + query | "/api/v1/users?page=1" |
req.method / http.method | string | HTTP method | "GET" |
req.scheme / http.scheme | string | Request scheme | "https" |
req.ip / http.ip | string | Resolved client IP | "203.0.113.50" |
req.peer_ip / http.peer_ip | string | Direct connection IP | "10.0.0.1" |
req.user_agent / http.user_agent | string | User-Agent header | "Mozilla/5.0 ..." |
IP intelligence fields
| Path | Type | Description | Example Value |
|---|---|---|---|
ip.country | string | ISO 3166-1 alpha-2 country code | "US" |
ip.region | string | Region/state code | "CA" |
ip.city | string | City name | "San Francisco" |
ip.asn | number | Autonomous System Number | 16509 |
ip.as_org | string | ASN organization name | "AMAZON-02" |
ip.provider | string | Hosting/cloud provider label | "Amazon AWS" |
ip.is_vpn | bool | IP is a known VPN provider | true |
ip.is_tor | bool | IP is a Tor exit node | false |
ip.is_datacenter | bool | IP is from datacenter/hosting | true |
ip.is_proxy | bool | IP is a known anonymous proxy | false |
ip.is_mobile | bool | IP is from a mobile network | null |
ip.is_residential | bool | IP is residential | null |
ip.is_residential_proxy | bool | IP is a residential proxy | null |
ip.network_type | string | Network type classification | "datacenter" |
ip.risk_score | number | Risk score (0-100) | 10 |
Header, query, and cookie access
| Path Pattern | Type | Description |
|---|---|---|
req.header.<name> | string | Request header value |
http.header.<name> | string | Request header value |
req.header.<name>.jwt.<claim.path> | any | Decode JWT from header (strips Bearer ), navigate dotted claim path |
req.query.<name> | string | Query parameter value |
http.query.<name> | string | Query parameter value |
req.cookie.<name> | string | Cookie value |
http.cookie.<name> | string | Cookie value |
Examples:
{ "left": "req.header.X-API-Key", "operator": "exists" }
{ "left": "req.header.authorization.jwt.data.role", "operator": "equals", "value": "admin" }
{ "left": "req.query.page", "operator": "greater_than", "value": 1 }
{ "left": "req.cookie.Session-ID", "operator": "exists" }
Operators reference
| Operator | Description | Left Type | Value Type |
|---|---|---|---|
equals | Exact match | any | any |
not_equals | Not equal | any | any |
contains | String contains | string | string |
not_contains | String does not contain | string | string |
starts_with | String prefix match | string | string |
ends_with | String suffix match | string | string |
exists | Value is not null | any | — |
not_exists | Value is null | any | — |
in | Value is in array | any | array |
not_in | Value is not in array | any | array |
greater_than | Numeric > | number | number |
greater_than_or_equal | Numeric >= | number | number |
less_than | Numeric < | number | number |
less_than_or_equal | Numeric <= | number | number |
regex | Regular expression | string | string (regex) |
Firewall actions
| Action Type | Description | Extra Fields |
|---|---|---|
block | Return error response | status (default: 403) |
allow | Continue pipeline (skip remaining firewall rules) | — |
redirect | Return redirect response | status (default: 302), location |
set_request_header | Modify request headers before forwarding | headers (map) |
set_response_header | Modify response headers (reserved) | headers (map) |
Block response body
{
"error": "blocked",
"status": 403,
"rule_id": "block-tor"
}
Redirect response
HTTP/1.1 302 Found
Location: https://example.com/blocked
Content-Length: 0
Rate limiting
Blocked response body
{
"error": "rate_limited",
"status": 429,
"rule_id": "api-limit",
"limit": 100,
"window_seconds": 60
}
Response headers:
| Header | Value |
|---|---|
Content-Type | application/json |
Retry-After | Window seconds |
Rate limit keys
| Key | Description |
|---|---|
req.client_ip | Resolved client IP |
req.peer_ip | Direct connection IP |
req.header.<name> | Header value (e.g., X-API-Key) |
Load balancer
No healthy upstream response
{
"error": "no_healthy_upstream"
}
Status: 503 Service Unavailable
Upstream headers added
| Header | Value |
|---|---|
X-Forwarded-For | Client IP (appended to chain) |
X-Forwarded-Proto | Original scheme (http / https) |
X-Forwarded-Host | Original Host header |
TLS / SNI behavior
When target protocol is https:
| Scenario | SNI used |
|---|---|
tls.sni configured | Uses configured SNI |
| Target is IP + visitor has Host header | Uses visitor's Host |
| Target is IP + no Host header | Uses target IP as SNI |
| Target is hostname | Uses target hostname |
Cache response headers
| Header | Value | Description |
|---|---|---|
Quayel-Cache-Status | NONE / HIT / MISS / EXPIRED / BYPASS / STALE / REVALIDATING | Cache status |
Cache status values
| Value | Description |
|---|---|
NONE | No cache rule matched |
HIT | Response served from fresh cache |
MISS | Cache miss, fetched from upstream |
EXPIRED | (Reserved) |
BYPASS | Cache bypassed by rule |
DYNAMIC | (Reserved) |
STALE | Served stale data (lock race fallback — another request was already revalidating) |
REVALIDATING | Served stale data while another request refreshes the cache (or this request is the one refreshing) |
Cache revalidation
When a cached entry becomes stale, the gateway uses singleflight revalidation via per-key locks:
- First request that finds stale data acquires the lock and goes to origin like a normal cache miss
- Concurrent requests see the lock is held and serve stale cached data with
Quayel-Cache-Status: REVALIDATING - When the first request completes, the lock releases and subsequent requests see fresh cache data
This prevents cache stampedes — only one request per key goes to origin, regardless of how many concurrent requests arrive.
See Cache plugin — Singleflight revalidation for detailed flow diagrams.
JSONL log fields
See Logging for the complete log format.
Key fields for monitoring
| Field | Path | Description |
|---|---|---|
| Client IP | .client_ip | Resolved real client IP |
| Country | .ip_intel.country | GeoIP country code |
| ASN | .ip_intel.asn | Autonomous System Number |
| Provider | .ip_intel.provider | Hosting/cloud provider label |
| VPN | .ip_intel.is_vpn | VPN detection |
| Tor | .ip_intel.is_tor | Tor exit detection |
| Datacenter | .ip_intel.is_datacenter | Datacenter detection |
| Risk Score | .ip_intel.risk_score | 0-100 risk score |
| Firewall Match | .firewall.matched | Any firewall rule matched |
| Rate Limited | .rate_limit.blocked | Request was rate limited |
| Cache Status | .cache.status | HIT/MISS/REVALIDATING/STALE/BYPASS/NONE |
| LB Target | .load_balancer.target | Selected upstream |
| Total Time | .timings_us.total_us | Total processing time (μs) |
Plugin pipeline order
Plugins execute in this fixed order:
1. IP Intelligence (always runs)
2. Firewall (can short-circuit)
3. Rate Limiting (can short-circuit)
4. Load Balancer (can short-circuit — selects upstream target)
5. Cache (can short-circuit — uses upstream target for revalidation)
6. Logging (always runs)
Short-circuit behavior
| Plugin | Can Short-circuit? | When? |
|---|---|---|
| IP Intel | No | Always continues |
| Firewall | Yes | Block, redirect, or set-header action |
| Rate Limit | Yes | Limit exceeded → 429 |
| Cache | Yes | Cache HIT → serve cached response; REVALIDATING/STALE → serve stale cached data |
| Load Balancer | Yes | No healthy upstream → 503 |
Example: complete backend integration
When your backend receives a request through Quayel, it gets:
GET /api/v1/users HTTP/1.1
Host: api.example.com
X-Forwarded-For: 203.0.113.50
X-Forwarded-Proto: https
X-Forwarded-Host: api.example.com
User-Agent: Mozilla/5.0 ...
The access log records:
{
"timestamp": "2026-10-05T17:48:56.973054084+00:00",
"gateway_id": "gw-prod-01",
"request_id": "c30dc8f2-7e2d-474b-979f-23ec1eb2e244",
"client_ip": "203.0.113.50",
"peer_ip": "203.0.113.50",
"client_ip_source": "X-Forwarded-For",
"method": "GET",
"scheme": "https",
"host": "api.example.com",
"path": "/api/v1/users",
"response_status": 200,
"ip_intel": {
"country": "US",
"region": "CA",
"asn": 16509,
"network_type": "datacenter",
"is_vpn": false,
"is_proxy": false,
"is_datacenter": true,
"is_mobile": null,
"is_tor": false,
"risk_score": 10
},
"firewall": {
"matched": false,
"rule_id": null,
"action": null
},
"rate_limit": {
"matched": false,
"rule_id": null,
"blocked": false,
"remaining": null
},
"cache": {
"rule_id": null,
"status": "NONE",
"key_hash": null
},
"load_balancer": {
"lb_id": "default",
"algorithm": "RoundRobin",
"target_id": "backend-1",
"target": "10.0.0.1"
},
"termination_phase": null,
"timings_us": {
"ip_intel_us": 42,
"firewall_us": 3,
"rate_limit_us": 0,
"cache_lookup_us": null,
"load_balancer_us": 7,
"upstream_connect_us": null,
"upstream_ttfb_us": null,
"total_us": 1091
}
}
Environment variables
| Variable | Default | Description |
|---|---|---|
RUST_LOG | info | Log level (debug, info, warn, error) |
QUAYEL_IP_INTEL_CACHE_SIZE | 65536 | LRU cache size for IP lookups |