Skip to main content
An API Policy Token is a second kind of organization token, intended for autonomous agents and narrowly scoped automation. Unlike a regular API Token, it carries no role. Instead you attach an Open Policy Agent policy, written in rego, and Qovery evaluates that policy against every API request the token makes. The policy is the token’s entire authorization.
Beta: this feature and the policy input contract described below are still evolving and are likely to change. Expect to revisit your policies as the feature matures.
In the Qovery Console, this feature is labelled Policy API Token (Beta), in the Token API section of your organization settings.

Why use one

A role answers “what kind of thing may this token do, across a scope”. A policy answers “may this exact request proceed”. That is what makes it a fit for an agent: you can hand out a token that reads everything in one environment, deploys that environment, may update one service, and can never delete anything - a shape no role expresses.

How it works

What Open Policy Agent does

Open Policy Agent (OPA) is an open source, general-purpose policy engine and a CNCF graduated project. Its model is deliberately narrow: it knows nothing about Qovery and holds no permission model of its own. You give it two things - a policy, and a JSON document called the input - and it answers a query against them. That is all it does. That separation is the point. The rules deciding what your agent may do live in the policy rather than in Qovery’s permission code, so you can express a constraint Qovery never anticipated without Qovery having to invent a role for it. Qovery runs OPA as a sidecar next to the API, reachable only over the pod’s loopback interface. Your policy and the requests it is evaluated against never leave Qovery’s infrastructure, and a decision costs an in-pod round trip rather than a call out to a network service.

How Qovery evaluates a request

The policy is evaluated at authentication time, before the request reaches the endpoint it targets. That is also why the policy sees the request’s raw path rather than a route template - the route has not been matched yet.
  1. The token is recognised by its sk-qov-01 prefix in the Authorization header, which is what tells it apart from a regular API token sent under the same Token scheme.
  2. The token is looked up. Only a hash of the token value is stored, so the presented value is hashed and matched against it. An unknown or expired token stops here.
  3. The target is resolved. Qovery takes the IDs appearing in the request path and asks its own database what they are - a service, an environment, a project - and what that resource’s ancestry is. This is what fills qovery_metadata, and it is why a policy can talk about environments while the request only names a service.
  4. The input document is built from the request and that resolved metadata. Its exact shape is documented below.
  5. The policy is loaded. The first time a pod handles a given token, it uploads that token’s policy to its OPA sidecar as a module, under a package unique to the token. Per-token packages are what stop one token’s rules from ever contributing to another token’s decision, and they are the reason your policy must not declare a package of its own. The module is compiled once and reused for subsequent requests.
  6. The policy is evaluated against the input, and OPA returns the value of allow: true, false, or undefined when no rule produced a value.
  7. The decision is applied. true lets the request continue to the endpoint. Anything else - including undefined, a non-boolean value, or an OPA that could not be reached - answers 401 Unauthorized.

Rego, the policy language

Policies are written in rego, OPA’s language. It is declarative and derives from Datalog, extended to query nested JSON documents - which is exactly what an input document is. There are no statements to sequence and no control flow to trace: you declare the conditions under which something holds, and OPA searches for a way to satisfy them. Five ideas carry almost every Qovery policy. A rule has a head and a body, and the body is an AND. Every expression in the body must hold for the rule to produce its value.
Rules sharing a name are an OR. Defining allow twice is not a conflict, it adds an alternative. This is how a policy grants several unrelated things without collapsing into one tangled condition:
Anything missing is undefined, not an error. An expression referring to a value that is not there is itself undefined, and that propagates: the surrounding rule simply does not fire. input.request.body.key on a request that carried no body is undefined, so the rule using it does not fire, so the request is denied. Nothing throws, and fail-closed comes out as the natural default rather than something bolted on. default supplies a value when nothing fires. Without it, a request matching no rule leaves allow undefined; with it, the answer is explicitly false.
:= assigns, == compares, in tests membership. Membership needs no loop, which keeps a list of allowed verbs readable:
Rego also ships a large standard library - startswith, endswith, count, regex.match, sprintf and many more - listed in the policy reference.
The Rego Playground evaluates a policy against an input document in your browser, which is a fast way to get a feel for the language before writing a real policy. Paste one of the recipes and an input document from this page into it.

What this means in practice

  • The policy is the only thing constraining the token. Internally a policy token is granted organization-admin access, and the policy is what narrows it. A policy whose allow is unconditionally true therefore grants full organization-admin access.
  • It fails closed. Anything other than an explicit true denies the request: no matching rule, a non-boolean allow, a request path that Qovery cannot resolve, or a policy engine that cannot be reached.
  • Only an organization Owner or Admin can create one. Creating a policy token is equivalent to handing out organization-admin access, so it is restricted to roles that already hold it. Custom roles cannot create one.
  • Revocation is immediate. The token is looked up on every request, with no caching.
  • Actions are attributed in the audit log as policy:<token-id>:<token-name>, so an agent’s activity is distinguishable from a regular API token’s.
Treat creating an API Policy Token with the same care as granting organization-admin. Start from a default allow := false policy and add only the rules you need.

What the policy sees

Your policy is evaluated against this input document:
The public API path is internally prefixed with api, so POST https://api.qovery.com/environment/<id>/service/deploy reaches your policy as ["api", "environment", "<id>", "service", "deploy"]. Include that first segment when you match on a path.
A few behaviours worth knowing before you write rules against these fields:
  • The qovery_metadata keys are always present, set to null when nothing of that kind was resolved. Test input.qovery_metadata.service_id == null, not the absence of the key.
  • Qovery resolves one target per request, preferring a service over an environment over a project. For /api/environment/<env>/application/<app>, the target is the application: environment_id, project_id and cluster_id are then read from that service’s ancestry rather than from the path. So a rule on environment_id still holds for a request that only names the service.
  • IDs belonging to another organization are ignored, as if they were not in the path.
  • A request whose path cannot be resolved is denied, rather than shown with null fields. A rule phrased as service_id != "<id>" cannot be widened by a failed lookup.
  • Only JSON bodies are forwarded. A request with a non-JSON body, a malformed JSON body, or a body larger than 1 MiB is denied outright rather than presented to the policy with body set to null. Inside a policy, body == null therefore always means “there was no body”.
  • Query parameters, headers and the client IP are deliberately not exposed.

Writing the policy

Do not include a package declaration. Qovery prepends a per-token package so that one token’s rules can never authorize another’s, and a submitted package line is rejected. Submit rule definitions only.
  • The policy must define an allow rule, and the decision must be the boolean true. Anything else denies.
  • Start with default allow := false. Without it, a request that matches no rule leaves allow undefined, which Qovery also treats as a denial - the default just makes the intent explicit.
  • Maximum policy size: 65,536 characters.
  • The policy is compiled when you create the token, so a syntax error comes back immediately as a 400 with the compiler diagnostics.
  • The engine is OPA 1.19, so rego v1 syntax is available directly: if, in and contains need no import statement.

Starter policy

This is the policy prefilled in the Console when you create a token. It grants read-only access to one environment, write access to one service in it (but never a deletion), and the right to deploy that environment.

Policy recipes

Every GET and HEAD on the environment, its services and their sub-resources. Nothing else.
A token for a release pipeline: it can trigger deployments and cannot read or change anything else. Matching on the exact path is what keeps it that narrow.
Full read and write on a single service, with deletion excluded whatever the endpoint.
Because the parsed JSON body is part of the input, a rule can constrain the content of a change and not just its target. Here the agent may create environment variables on one application, but only those named FEATURE_*.
A request with no body leaves input.request.body.key undefined, so this rule denies it.

Create an API Policy Token

1

Open the Token API section

Go to your organization settings and open the Token API section, the same page as the API tokens. The Policy API Token (Beta) section sits below the API token list.
2

Click Add new

Press the Add new button of the Policy API Token section.
3

Configure the token

Provide:
  • Token name: a descriptive name, unique within the organization
  • Description: what the token is for
  • Policy (rego): your policy, prefilled with the starter policy above
4

Store the token value

The token is displayed once, in the form sk-qov-01-.... Copy it before closing the modal.
Important: Make sure you safely store the token returned by the UI. You won’t be able to retrieve it again (you will have to create a new one).

Use the token

An API Policy Token uses the same Authorization header scheme as a regular API token:
If the policy does not allow the request, the API answers 401 Unauthorized.

Inspect and revoke

In the Policy API Token list, each token offers:
  • Inspect policy (scroll icon): shows the policy attached to the token, read-only. A policy can always be reviewed after creation - only the token value is write-once.
  • Delete (trash icon): revokes the token. Confirm the deletion, and the token stops working immediately.
Policies cannot be edited. To change one, delete the token and create a new one with the updated policy.

Manage tokens via the API

Creating a token, with the policy passed as a string in opa_policy:
The response contains the token value in its token field. This is the only response that ever carries it. The list endpoint returns each token with its opa_policy, never the token value. The create endpoint also accepts an optional expires_at (RFC 3339 date-time), after which the token stops authenticating; the Console form does not expose it yet, so set an expiration through the API if you need one. Errors to expect when creating a token:

Test a policy before attaching it

A denied request tells you only that it was denied, so it is worth evaluating a policy locally first. Install OPA 1.19 - the same version Qovery runs - then:
1

Save the policy with a temporary package line

Qovery adds the package declaration for you, so add one locally and remove it before submitting.
2

Save the request you want to test

Write an input.json following the input document shown above.
3

Evaluate

true means the request would be allowed. false, or an empty result, means it would be denied.
Test the denials too, not just the allows. Flip the method to DELETE, point the request at another environment, and confirm the answer is false.

Beta limitations

  • A denied request returns 401 with no explanation. Which rule failed is not surfaced to you today, so test policies locally before attaching them.
  • Policies cannot be edited. Delete the token and create a new one.
  • Expiration is API-only. The Console form does not expose expires_at.
  • The input contract may change. Fields are added rather than renamed or removed, but the feature as a whole is Beta.

API Token

Role-based tokens for CI/CD, Terraform and scripts.

Securing AI Agent Access

The full picture on giving an AI agent a safe footprint on your infrastructure.

Members & RBAC

The roles that back regular API tokens.