Cloud and BYOC for Orca Agent Engine are in Private Preview — request an invite
Docs

Identity and scope

Declare the tenancy dimensions Orca AI Gateway routes and meters on, and configure the validator that turns a caller's token into a principal.

Before the gateway can authorize, meter, or route a request, it has to know who is calling. An auth validator turns the caller's credential into a principal carrying a scope - the set of tenancy dimensions everything downstream matches on.

Scope dimensions

identity:
  scope_dims: [workspace_id, env]

scope_dims declares the dimensions this deployment uses. Nothing in the gateway hard-codes them: routes, rate limits, authorizers, and sinks all refer to dimensions by the names you choose. A single-tenant deployment can leave the list empty.

Order matters, because it determines the ordering of compact scope attributes on spans and usage records. Every dimension referenced anywhere else in the config must appear here, and orca-gateway check fails if one does not.

The context builder also accepts X-Orca-Scope-<dim> headers for dimensions not supplied by a verified principal. A verified claim wins over a same-named header. Treat header-only scope as client asserted: it can affect route matching, while spend enforcement uses it only when the dimension appears in spend.trusted_header_dims.

Validators

identity:
  scope_dims: [workspace_id, env]
  validators:
    - name: platform-jwt
      kind: jwt
      issuers: ["https://auth.example.com/"]
      audiences: ["orca-gateway"]
      jwks_url: "https://auth.example.com/.well-known/jwks.json"
      scope_from:
        jwt_claims:
          ws: workspace_id
          env: env

Validators are tried in order and the first that accepts the request wins.

A supplied credential that every validator rejects returns 401. To require a credential on every request, also configure an authorizer that denies anonymous principals.

FieldNotes
nameUnique within the gateway.
kindjwt or api_key.
issuers, audiencesStandard JWT validation. A token failing either is rejected.
jwks_urlFetch signing keys from a JWKS endpoint.
static_public_key_pemVerify against a PEM public key on disk instead.
scope_from.jwt_claimsMap of claim to scope_dim. The claim's value is copied into the named dimension.

The config model accepts scope_from.header_map, but the server does not pass it into either validator kind. Use claims or API-key attributes for verified scope.

jwt and api_key are the validator kinds wired into the shipped binary. An unrecognized kind fails at boot with an error listing what is supported. api_key accepts credentials only through Authorization: Bearer; configuring a custom header is rejected at boot.

JWKS or static key

Use jwks_url when an identity provider publishes rotating keys - the gateway fetches and caches them. Use static_public_key_pem when the issuer is a service you run and whose key you deploy alongside the gateway, which is the usual shape for an Agent Engine integration where the registry signs its own session tokens.

API keys

An api_key validator loads SHA-256 digests from keys_file at startup. It maps the matching key to its configured principal and attributes:

identity:
  validators:
    - name: local-keys
      kind: api_key
      keys_file: /etc/orca-gateway/api-keys.yaml
api-keys.yaml
keys:
  - id: ingest-prod
    secret_sha256: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
    principal_id: svc-ingest
    attributes:
      scope.workspace_id: ws_42

The file must contain digests, not plaintext secret values. A malformed file, duplicate key id, or custom-header configuration prevents the gateway from starting.

How scope reaches the rest of the config

Once a claim is mapped into a dimension, that dimension is matchable everywhere:

routes:
  - name: prod-only
    match:
      path: "/v1/chat/completions"
      scope:
        env: prod
    strategy:
      mode: fallback
      targets:
        - destination: openai-primary

rate_limits:
  - match:
      scope:
        workspace_id: "ws_42"
    qpm: 600
    tpm: 200000

Scope matching is exact. Although the configuration model accepts match.scope_kind, the current route matcher does not read it, so prefix does not change runtime behavior.

On this page