Skip to main content

Policy and Permits

A policy defines the rules for a specific action type in a specific environment. When your code calls /v1/authorize, Veritrellis evaluates the matching policy and returns a signed permit if the action is allowed, or a denial reason if it is not.

Decision outcomes

Every authorization request resolves to exactly one outcome:

OutcomePermit issued?Description
allowedYesPolicy approved the action directly. Permit is in the response.
pending_approvalNot yetPolicy requires human approval. Returns a request_id to poll.
deniedNoPolicy blocked the action. Includes a reason_code.

Core concepts

Action type

A canonical label for a risky operation — for example, issue_refund, issue_credit, or send_contract. Action types are workspace-scoped and defined in the admin app.

Each action type has a payload schema. Requests that fail schema validation return a 400 before reaching policy evaluation.

Policy

A policy binds an action type to a set of rules for a given environment (sandbox or production). Rules define:

  • Direct allow conditions — which payloads are approved without human review (e.g. amounts below a threshold)
  • Approval conditions — which payloads require a human approver
  • Deny conditions — which payloads are blocked outright

Policies are version-controlled. Each change creates an immutable policy version; existing in-flight requests are evaluated against the policy version active when they were submitted.

Request

A request is the audit record of an authorization attempt. It captures:

  • The action type, payload, actor reference, and resource reference
  • The policy version evaluated
  • The decision and reason code
  • The approval chain (if approval was required)
  • The permit issued (if any)

Requests are append-only. They cannot be modified after creation.

Approval

When a policy requires human approval, Veritrellis creates a pending request and surfaces it in the admin approvals inbox. When the policy assigns an approver group and email delivery is configured, the members of that group are emailed a link to review it. Approvers can approve or reject via the admin app or a connector surface. On a decision, the requester is emailed the outcome.

Once approved, a permit is issued and returned by the polling endpoint (GET /v1/permits/:requestId). Rejected or expired requests return 409.

Permit

A permit is a signed ES256 JWT issued when a request is authorized. It is cryptographically bound to:

  • The workspace and environment
  • The specific action type
  • The resource reference
  • The request ID

Permits expire. Your code must verify the permit's signature and expiry before executing the action.

Permit JWT claims

Decoded permit payload
{
"iss": "https://api.veritrellis.ai",
"sub": "req_01HXXX...",
"aud": "ws_01HYYY...",
"iat": 1717000000,
"exp": 1717003600,
"workspace_id": "ws_01HYYY...",
"environment": "production",
"action_type": "issue_refund",
"resource_ref": "cus_123",
"policy_version_id": "pv_01HZZZ..."
}
ClaimDescription
issIssuer — always https://api.veritrellis.ai
subThe request ID this permit was issued for
audAudience — the workspace ID
iatIssued-at (Unix timestamp)
expExpiry (Unix timestamp)
workspace_idWorkspace the permit is scoped to
environmentsandbox or production
action_typeThe action type this permit authorizes
resource_refThe resource the permit is bound to
policy_version_idPolicy version that authorized this request

Verifying a permit

Use the SDK verifyPermit helper rather than implementing raw JWT verification:

verify-permit.ts
import { verifyPermit } from "@veritrellis/sdk-node";

try {
const claims = await verifyPermit({
permitJwt: permit,
workspaceId: process.env.VERITRELLIS_WORKSPACE_ID!,
issuer: "https://api.veritrellis.ai"
});

// claims.action_type, claims.resource_ref, claims.sub are all verified
await executeAction(claims);
} catch (err) {
// Permit invalid, expired, or tampered — do not execute
throw new Error("Permit verification failed");
}

Verification fetches the workspace's JWKS from:

GET /v1/workspaces/:workspaceId/.well-known/jwks.json

The SDK caches keys in memory to avoid a network call on every verification. Pass a shared JwksCache instance across your service to share the cache.

Fail closed

If verifyPermit throws for any reason, your code must not execute the action. Failing open defeats the purpose of the permit boundary.

Reason codes

When decision is denied, the response includes a machine-readable reason_code:

CodeMeaning
policy_rule_deniedA deny condition in the active policy matched the payload
no_active_policyNo policy is active for this action type and environment
payload_validation_failedRequest payload did not match the action type schema
workspace_limit_exceededWorkspace entitlement limit reached
api_key_revokedThe API key used for this request has been revoked

Schema source

Action type schemas, policy rule schemas, and permit claims are defined in packages/shared. This is the primary contract source — not the API docs or the SDK.

  • packages/shared/src/actions/ — action payload unions
  • packages/shared/src/policy/ — rule schema and reason codes
  • packages/shared/src/permit/ — permit claims schema