Add a policy in the dashboard
- Open the app and click Policies in the sidebar.
- Click Add policy, enter a Name, and pick a Type: Key Auth, Rate Limit, Firewall, OpenAPI Validation, or Logging.
- In the Rule card, click Add condition for each match condition if the policy should only apply to some requests. With no conditions, the If row reads “Every request”. Open the Then row to set the type’s settings.
- Pick the Environment. The default, All environments, adds the same policy to both production and preview.
- Click Add policy, then redeploy.

Set policies with the API
Each endpoint takes theproject, app, and environment by ID or slug.
gateway.listPoliciesreturns the list in the order it runs, with IDs.gateway.setPoliciesreplaces the whole list with thepoliciesarray you send, in that order. If any policy is invalid, nothing is saved. An empty array removes every policy. Every policy gets a new ID.gateway.updatePolicychanges one policy bypolicyId. Fields you leave out keep their values, andmatch: nullremoves all match expressions. Sending one ofkeyauth,ratelimit,firewall,openapi, orloggingreplaces the rule and can change its type.
unkey:v1:<workspace_id>:projects/<project_id>/apps/<app_id>/environments/<environment_id>/gateway/policies/*#writeunkey:v1:<workspace_id>:projects/<project_id>/apps/<app_id>/environments/<environment_id>/gateway/policies/*#delete
environment.*.read_policies, or environment.<environment_id>.read_policies for a specific environment. To change one policy, your root key needs unkey:v1:<workspace_id>:projects/<project_id>/apps/<app_id>/environments/<environment_id>/gateway/policies/<policy_id>#write. Use * in place of a resource ID to grant access to all resources at that level. See root key permissions. A keyspace in an authentication policy must be in the same workspace, or the call fails with err:unkey:data:key_space_not_found.
Every change, from the dashboard, API, or CLI, is recorded in the audit log with the full policy list.
Set policies with the CLI
unkey gateway policies list, unkey gateway policies set, and unkey gateway policies update do the same as the three endpoints. Each takes --project, --app, and --environment. unkey gateway policies set takes the full list as --policies '<json array>', and unkey gateway policies update takes --policy-id and --policy '<json object>'.
What’s in a policy
Up to 10 expressions. A request must match all of them for the policy to apply. Leave it out to apply the policy to every request.
Set exactly one. It picks the policy type and holds its settings. See API key authentication, Rate limit, Firewall, OpenAPI validation, and Logging.
id. It changes every time gateway.setPolicies replaces the list.
Pick which requests a policy applies to
Each match expression sets exactly one ofpath, method, header, queryParam, or remoteIp. A request must match every expression in the policy. To match one thing or another, create two policies.
Matches the request path, ignoring case. Set exactly one of
exact, prefix, or regex (1 to 1024 characters). Regular expressions use RE2 syntax, and one that doesn’t compile is rejected when you save. ignoreCase is accepted but has no effect on paths. See how paths and methods match.One or more of
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. Matches any of them, ignoring case. GET also matches HEAD.name (1 to 256 characters) plus exactly one of present: true or value (a StringMatch like path). With value, it matches if any value of that header matches.Matches the remote IP. Set exactly one of
in (the remote IP is in one of the ranges) or notIn (the remote IP is in none of them). Each is a list of 1 to 100 IP addresses or CIDRs, such as 203.0.113.7, 203.0.113.0/24, or 2001:db8::/32. A single address is saved as /32 or /128. Ranges with host bits set, such as 10.1.2.3/8, are rejected. The gateway accepts IPv4 connections today, so IPv6 entries match no request yet. In the dashboard this is Client IP, with is in or is not in.Example: block POST and DELETE on internal paths
How paths and methods match
Many app frameworks send several spellings of a path to the same handler. The gateway matches those spellings too, so a policy on/admin can’t be skipped by requesting /ADMIN. Before matching, the gateway normalizes the path:
- It ignores case, so
/ADMINmatches/admin. - It ignores a trailing slash, so
/admin/matches/admin, and aprefixof/admin/matches/admin. - It removes
;parameters from each segment, so/admin;jsessionid=1matches/admin. - It collapses repeated slashes, so
/a//adminmatches/a/admin. - It rejects paths with encoded dot segments such as
%2e%2e, and double-encoded slashes such as%252F, withambiguous_path. If an enabled policy matches paths with aregex, it also rejects%2F(an encoded/) and%5C(an encoded\). Frameworks disagree on how to read these, so the gateway can’t match them to policies reliably.
regex written against ; or .. text doesn’t match. Your app still receives the original, unmodified request.
A method match on GET also matches HEAD, because most frameworks answer HEAD with the GET handler.
Protect broadly, then narrow
Matching on a list of protected paths is fragile. A route you forget, or a spelling your framework accepts that the gateway doesn’t, stays open. Put API key authentication on a broad scope instead: every request, or a prefix such as/api/ that holds all your private routes. Serve public routes outside that prefix, or from a separate app.
If a narrower path needs a stricter API key policy, such as /api/admin/ with a different keyspace, put that policy above the broad one. The first API key policy that succeeds decides who the caller is, so a stricter policy below a broad one never runs.
Put policies in the right order
The gateway runs matching, enabled authentication, rate limit, firewall, and OpenAPI validation policies from top to bottom. In the dashboard, drag a row by its handle to reorder. Order matters in four ways:- IP blocks before authentication. Put a firewall policy that blocks by
remoteIpabove your API key policies, so blocked clients can’t try keys. - Authentication first. The first API key policy that succeeds decides who the caller is. Later API key policies are skipped.
- Rate limits by caller go after authentication. A rate limit that counts by
authenticatedSubjectorprincipalFieldneeds to know the caller, so it needs an enabled API key policy above it. Saving one without that policy fails with a validation error. - A rejection stops enforcement. After a firewall block, a failed key check, a used-up rate limit, or an OpenAPI error, no later authentication, rate limit, firewall, or validation check runs.
Limits
- 50 policies per environment.
- 10 match expressions per policy.
- 100 entries per
remoteIplist. - 5 keyspaces and 10 key rate limits per API key policy.
- 5 identifiers per rate limit policy.
- Match strings up to 1024 characters, and names up to 256.
Next steps
API key authentication policy
Verify Unkey keys and forward the principal.
Rate limit policy
Limit by IP, header, path, subject, or principal field.
Firewall policy
Deny the requests your match expressions select.
Logging policy
Capture headers, query data, and bodies for matched requests.