Resource and Grant
A resource is anything an agent wants to access — an API endpoint, an MCP tool, a data service, or any addressable target. A grant is the binding that permits a specific application to access a resource on behalf of a specific user. Together, resources and grants define the access surface that policies evaluate.
Resources
Section titled “Resources”A resource is registered in a zone with these attributes:
| Field | Type | Description |
|---|---|---|
id | UUID | Internal identifier used in grants and delegation constraints |
zone_id | string | Zone this resource belongs to |
name | string | Human-readable label |
identifier | string | Logical name used in policy rules and mandate target claims (e.g. resource://payments) |
upstream_url | string | null | The actual URL the Gateway proxies to, if routing through Gateway |
prefix | boolean | If true, upstream_url is a URL prefix; path is appended from the request |
scopes | string[] | Full set of scopes available for this resource |
credential_provider_id | string | null | Provider to use for credential substitution, if any |
The identifier is the string that appears in policy rules (input.resource.identifier) and in mandate target claims. It is a stable logical name, not an internal UUID.
The scopes array defines all scopes the resource can grant. A grant can assign any subset of these scopes to an application. A policy can further restrict which scopes are included in the mandate.
Grants
Section titled “Grants”A grant binds an application and an optional user identity to a resource and a set of scopes:
| Field | Type | Description |
|---|---|---|
id | UUID | Internal identifier |
zone_id | string | Zone this grant belongs to |
application_id | string | The application being granted access |
user_id | string | Opaque user identifier (not validated against any directory) |
resource_id | string | The resource being granted |
scopes | string[] | Subset of the resource’s scopes being granted |
status | string | Grant lifecycle status |
The scopes on a grant must be a subset of the resource’s scopes. The grant does not itself authorize access — it establishes that the principal has been given permission. The STS policy still evaluates whether to actually issue a mandate. A grant is necessary but not sufficient.
The user_id is an opaque string. Caracal does not maintain a user directory or authenticate users. The user_id is stored for audit purposes and is available to policies via actor_claims.
Scope flow through the system
Section titled “Scope flow through the system”Resource defines scopes: ["read", "write", "transfer"] │ ▼Grant allows a subset: ["read", "write"] │ ▼Exchange requests a subset: ["read"] │ ▼Policy decides on the request: allow / deny │ ▼Mandate carries granted scope: "read"At each stage, scopes can only be narrowed, never widened. An exchange request cannot ask for scopes the grant does not include. The mandate’s scope claim contains only what the policy allowed.
Credential providers
Section titled “Credential providers”A resource can be associated with a credential provider via credential_provider_id. When this is set, the STS retrieves the relevant provider credential (an OAuth token or API key stored encrypted under the zone DEK) and includes it in the upstreams directive of the token response.
The Gateway reads the upstreams directive and substitutes the provider credential as the Authorization header sent to the upstream. The agent never holds the provider credential — it only presents its mandate to the Gateway, and the Gateway handles the provider authentication.
Provider kind values:
| Kind | Description |
|---|---|
oauth2 | OAuth 2.0 access token |
oidc | OpenID Connect token |
apikey | Static API key |
workload | Machine-to-machine workload identity |
Provider secrets are encrypted before storage. Any field whose name matches the pattern /(secret|password|token|api[_-]?key|private[_-]?key|credential|passphrase)/i is sealed under the zone KEK and never returned in API responses.
How the Gateway uses resources
Section titled “How the Gateway uses resources”When a request arrives at the Gateway, the Gateway:
- Reads the
X-Caracal-Resourceheader from the request to identify the target resource. - Looks up the resource binding in its binding store (populated from the database) to find the upstream URL.
- Verifies the resource identifier appears in the mandate’s
targetclaim. - If the resource has a credential provider, reads the
upstreamsdirective from the mandate to determine the credential substitution mode. - Forwards the request to the upstream with the appropriate Authorization header.
Without a credential_provider_id, the Gateway forwards the mandate as the Authorization: Bearer header to the upstream. The upstream is responsible for verifying the mandate against the zone JWKS.
Resource identifiers in policies
Section titled “Resource identifiers in policies”The identifier field is the string that policies inspect. Use logical names that remain stable even if the underlying upstream URL changes:
# Allow read access to the payments resourceresult := { "decision": "allow", "evaluation_status": "complete", "determining_policies": ["payments-read-policy"], "diagnostics": [],} if { input.resource.identifier == "resource://payments" "read" in input.context.requested_scopes}The input.resource.id (UUID) is also available, but using identifier is more stable and readable.
Next steps
Section titled “Next steps”- Principal and Application — the application that receives access through grants
- Policy — how policies use resource attributes in decisions
- Delegation Graph — how a delegated agent can be restricted to a single resource