A pipeline is an ordered list of filters. Every request through /proxy is
evaluated against it, in order, and the first filter to deny short-circuits:
the remaining filters do not run, the upstream is never called, and the credential
is never used. A denial returns 403 with the filter that denied and its reason.
There are 13 filter types. They fall into three groups by when they run.
| Group | Filters | When |
|---|---|---|
| Request | endpoint, method, rate_limit, payload, body_size, time_window, ip_allowlist, header_requirement, identity, custom_code, cedar |
Before the upstream is called. A denial means the request never leaves Latch. |
| Response | response |
After the upstream replies, before you see it. Can block or redact. |
| Metered | spend_limit |
In the proxy hot path, against live daily-spend state. |
Any filter can be disabled without removing it ("disabled": true), and any filter
can be made conditional so it only applies to some requests (see
Conditional filters).
endpoint
Restrict which upstream paths the token may reach. Glob patterns: * matches one
segment, ** matches any depth.
{ "type": "endpoint", "name": "Completions only", "mode": "allowlist",
"patterns": ["/v1/chat/completions", "/v1/models/*"] }
| Field | Type | Description |
|---|---|---|
mode |
allowlist | blocklist |
Whether patterns permits or forbids. |
patterns |
string[] |
Glob patterns matched against the request path. |
Denies when the path is not in the allowlist, or matches the blocklist.
method
Restrict HTTP verbs. The bluntest read-only control there is.
{ "type": "method", "name": "Read only", "allowed": ["GET", "HEAD"] }
| Field | Type | Description |
|---|---|---|
allowed |
("GET"|"POST"|"PUT"|"PATCH"|"DELETE"|"HEAD"|"OPTIONS")[] |
Permitted verbs. |
Denies when the request's method is not listed.
rate_limit
A sliding-window request cap.
{ "type": "rate_limit", "name": "30 per minute",
"maxRequests": 30, "windowSeconds": 60, "keyBy": "latch" }
| Field | Type | Description |
|---|---|---|
maxRequests |
integer |
Requests permitted in the window. |
windowSeconds |
integer |
Sliding window length. |
keyBy |
latch | ip | json_path |
What the counter is keyed on. |
jsonPath |
string |
Required when keyBy is json_path: the field in the request body to key on (for example, per end-user). |
Denies when the cap is exceeded, returning 429 with Retry-After.
payload
Assert things about the request body. The most expressive request filter.
{ "type": "payload", "name": "No expensive models", "rules": [
{ "path": "model", "operator": "in", "value": ["gpt-4o-mini", "claude-haiku-4-5"],
"reason": "This token may only use small models." },
{ "path": "max_tokens", "operator": "less_than_or_equal", "value": 4096 }
]}
Each rule has a path (a dot path into the JSON body), an operator, an optional
value, and an optional reason shown in the denial.
| Operator | Meaning |
|---|---|
equals, not_equals |
Exact match. |
in, not_in |
Membership in value (an array). |
exists, not_exists |
Field presence. |
greater_than, greater_than_or_equal |
Numeric comparison. |
less_than, less_than_or_equal |
Numeric comparison. |
matches |
Regular expression. |
max_length, min_length |
Length bounds on a string or array. |
required_fields |
Every field in value must be present. |
type_is |
The value's JSON type. |
Denies when any rule fails, with that rule's reason.
body_size
Cap the request body in bytes. Cheap protection against a runaway or hostile caller.
{ "type": "body_size", "name": "Max 100KB", "maxBytes": 102400 }
Denies when the body exceeds maxBytes.
time_window
Only permit requests during given hours, in a given timezone.
{ "type": "time_window", "name": "Business hours", "timezone": "America/New_York",
"windows": [{ "days": [1,2,3,4,5], "startTime": "09:00", "endTime": "17:00" }] }
| Field | Type | Description |
|---|---|---|
timezone |
string |
IANA zone. Evaluated in this zone, not the caller's. |
windows[].days |
number[] |
Days of the week, 0 = Sunday. |
windows[].startTime / endTime |
"HH:MM" |
Window bounds. |
Denies when the request falls outside every window.
ip_allowlist
Restrict callers by source IP or CIDR range.
{ "type": "ip_allowlist", "name": "Office only", "allowed": ["203.0.113.0/24", "198.51.100.7"] }
Denies when the caller's IP is in no listed range.
header_requirement
Require headers to be present, optionally matching a pattern. Useful for asserting a tenant id or a correlation header that downstream systems depend on.
{ "type": "header_requirement", "name": "Tenant required",
"headers": [{ "name": "X-Tenant-Id", "pattern": "^t_[a-z0-9]+$" }] }
Denies when a required header is missing, or present but does not match pattern.
identity
Gate the latch to specific hardware-bound agent keys. The caller must present a
signed request slip; Latch verifies the ECDSA-P256 signature against an enrolled
key held in a Secure Enclave, so possession of the lat_ token alone is not enough.
{ "type": "identity", "name": "Only Ada's laptop",
"allowedAgentKids": ["kid_9f3c1a2b", "kid_4d7e8f01"] }
| Field | Type | Description |
|---|---|---|
allowedAgentKids |
string[] |
Enrolled agent key ids permitted to use this latch. |
Denies when the request carries no valid slip, or the signing key is not in
allowedAgentKids. This is what makes a stolen token useless on an unenrolled
machine.
custom_code
Run your own policy code. JavaScript executes in an isolated-vm sandbox; Rust
compiles to a WASM component and can be evaluated inside the TEE enclave, so
even Latch cannot see the request it is deciding on.
{ "type": "custom_code", "name": "Business rules", "language": "javascript",
"code": "if (ctx.body.amount > 10000) return deny('Over limit'); return allow();" }
| Field | Type | Description |
|---|---|---|
code |
string |
The policy source. |
language |
javascript | rust |
Rust is required for in-enclave evaluation. |
fetchToken |
string |
Optional token permitting the policy to call out to live external state. |
Denies when your code returns a denial.
cedar
Declarative authorization in Cedar. Evaluated in-enclave for TEE latches.
{ "type": "cedar", "name": "Cedar policy",
"policy": "permit(principal, action == Action::\"POST\", resource) when { context.amount <= 100 };" }
Denies when no permit matches, or a forbid does. Cedar is default-deny.
response
The only filter that runs after the upstream replies. Blocks or redacts the response before it reaches the caller. This is how an upstream that returns more than the caller should see gets contained.
{ "type": "response", "name": "Strip PII", "rules": [{
"blockStatusCodes": [500, 503],
"redactPaths": ["$.customers[*].ssn", "$.customers[*].email"],
"redactMode": "placeholder",
"maxResponseBytes": 1048576
}]}
| Field | Type | Description |
|---|---|---|
blockStatusCodes |
number[] |
Upstream statuses to withhold from the caller. |
redactPaths |
string[] |
JSON paths to strip from the body. |
redactMode |
remove | null | placeholder | zero |
How a redacted value is replaced. |
maxResponseBytes |
integer |
Cap the response size. |
Denies when the upstream returns a blocked status or an over-size body. Redaction does not deny; it rewrites.
spend_limit
A daily spend cap, enforced in the proxy hot path against live spend state keyed by latch. Use it to bound what a token can cost you, not just what it can do.
{ "type": "spend_limit", "name": "$50 a day",
"maxDailyCents": 5000, "pricing": "model_based",
"modelPricing": { "claude-opus-4-8": { "inputPer1k": 1.5, "outputPer1k": 7.5 } } }
| Field | Type | Description |
|---|---|---|
maxDailyCents |
integer |
The daily cap, in cents. |
pricing |
flat | model_based |
How a request's cost is computed. |
costPerRequestCents |
number |
Flat cost per request, when pricing is flat. |
modelPricing |
Record<string, { inputPer1k, outputPer1k }> |
Per-model token pricing, when pricing is model_based. |
Denies when the day's spend would exceed maxDailyCents.
Conditional filters
Any filter can carry a condition, so it only applies to matching requests. A
filter whose condition does not match is skipped, not denied.
{ "type": "body_size", "name": "Cap uploads", "maxBytes": 102400,
"condition": { "methods": ["POST", "PUT"], "pathPrefix": "/v1/files", "hasBody": true } }
| Field | Type | Description |
|---|---|---|
methods |
string[] |
Only apply to these verbs. |
pathPrefix |
string |
Only apply below this path. |
hasBody |
boolean |
Only apply to requests that do (or do not) carry a body. |