Latch / Docs
filters

Filter reference

Every filter type, its configuration, and what it does when it denies.

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 15 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, rialo_tx_signers, evm_tx_signers 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.
minAssurance string? Minimum hardware assurance the calling agent's device must have: self_asserted, hardware_attested, or managed_device. Omit for no hardware requirement.

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.

Device assurance

Enrolling a device proves the caller holds the private key and that a signed-in human approved it. It does not yet prove the key lives in a Secure Enclave or a TPM — Latch has no attestation verifier, so every enrolled device currently reports self_asserted and any minAssurance above it denies every request. Set the floor once devices can earn a higher rung.

Level Meaning
self_asserted Key possession plus an authenticated human. No hardware proof.
hardware_attested Verified chain to a TPM-vendor or Apple root proving the key was generated in, and cannot leave, a secure element.
managed_device Attested hardware that the attestation additionally names as organizationally managed.

The check is against the device (kid_u) behind the agent, and it runs after the allowlist check: a request from an allowed agent on a device below the floor is denied with identity_assurance_below_floor.

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.

rialo_tx_signers

Turn the latch into a policy-gated Rialo transaction signer (ed25519). The caller POSTs a semantic transaction plus a signature from an authorization key; the enclave recompiles the transaction to its canonical message, verifies the signature over those exact bytes, checks the signer against this allowlist, then signs the transaction with the latch's wallet and forwards it to the chain (sendTransaction). The authorization signature only authorizes — it is never embedded on-chain.

{ "type": "rialo_tx_signers", "name": "Allowed authorizers",
  "signers": ["<base58 ed25519 pubkey>", "<base58 ed25519 pubkey>"] }
Field Type Description
signers string[] base58 ed25519 public keys permitted to authorize a request.

This filter also sets the chain type the latch signs for (Rialo); it pairs with a transaction-signing secret. Set signers to ["*"] to make the latch permissionless — no authorization signature is then required, and the rest of the pipeline is the only gate.

Denies when no supplied signature verifies over the recompiled message and recovers to an allowlisted signer. An empty signers denies (fail-closed); ["*"] allows.

evm_tx_signers

The same, for EVM transactions (secp256k1, EIP-1559). The enclave recomputes the EIP-1559 signing hash, ecrecovers each supplied signature to an address, checks it against this allowlist, then signs with the latch's wallet and forwards eth_sendRawTransaction.

{ "type": "evm_tx_signers", "name": "Allowed authorizers",
  "signers": ["0x1111111111111111111111111111111111111111"] }
Field Type Description
signers string[] 0x-hex 20-byte addresses permitted to authorize a request.

Sets the chain type to EVM and pairs with a transaction-signing secret, exactly like rialo_tx_signers. ["*"] is permissionless.

Denies when no supplied signature ecrecovers to an allowlisted address. An empty signers denies; ["*"] allows.

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.