A resource is an upstream API sitting behind edge. Registering it tells edge where to
forward traffic and under what conditions. For the conceptual picture, see
Entities.
- Resource ID — lowercase, immutable, and used in the proxy path: requests to
/resources/{resourceId}/*are forwarded to the resource URI. The IDedgeis reserved. - Resource URI — an absolute URI with an empty path, no query and no fragment. The
resource://scheme is reserved for internal resources. - Audience — the list of clients allowed to request this resource. A client that is not in the audience cannot obtain a token targeting it.
- Internal or public — an internal resource has a generated secret;
edgeauthenticates to it withAuthorization: Basic {resourceId}:{secret}and the caller’s own token never reaches it, so it should sit in an internal zone with no access from the web —edgeis its only caller. Tokens issued for it carryresource://{resourceId}in theaudclaim (RFC 8707). A public resource has no secret;edgeruns the same audience, role/permission andallow-expression checks but forwards the caller’s token as-is instead of its own credentials, so the resource is responsible for validating that token itself, and tokens for it carry the resource’s own URI inaud. Internal resource secrets can be rotated in the same way as client secrets. - Endpoints — the individual method + path pairs that make up the resource.
Endpoints
An endpoint is the unit of authorization. Everything Versola enforces per request is configured here:
- Method + relative path — what to match. Segments are literal or
{name}parameters matching exactly one segment; matched values reach CEL asrequest.path.params, and a literal endpoint takes precedence over a parameterized one. - Fetch userinfo — load the owner’s claims from
authand expose them to CEL asuser. - Allow (CEL) — a boolean expression that must return
truefor the call to be authorized. - Inject — rules that write computed values into an upstream header, query parameter, or top-level JSON body field.
- Step-up condition + ACR — when the condition holds, require the caller to have reached a given assurance level (RFC 9470).
- Max auth age — reject the call if the owner authenticated longer ago than this.
Permissions reference endpoints by ID, so an endpoint becomes reachable to an owner only once some role they hold includes a permission covering it.
When a CEL expression can’t be evaluated
Nothing is forwarded upstream on the strength of an expression that didn’t produce a value. What the caller gets depends on why:
- The request doesn’t carry a value the expression reads (e.g.
user.planon a token with noplanclaim) — the call is refused with403, and a step-up condition is treated as met, so the ACR is required rather than skipped. Claims differ legitimately between users, so guard optional ones withhas():has(user.plan) && user.plan == 'premium', orhas(user.plan) ? user.plan : ''for an inject rule that may have nothing to inject. - The expression itself can’t be evaluated (a division by zero, a numeric overflow, an
allow rule returning a non-boolean) — the call fails with
500. This is a configuration problem, not a request problem. Expressions are checked when you save them, so what’s left here is what only shows up against live data.