Skip to content

Documentation

Put the control layer in front of your agent.

For actions your agent routes through Sevra before execution, the guard returns one of three verdicts: Allow, Require approval, or Block. Each routed decision and its reasons persist on the guard request. A would-be Allow requires its initial gate-event write; some intervention and approval annotations are best-effort. Sevra software does not hold or move funds; your code remains responsible for honoring the verdict before it calls the provider.

one guard call before the money moves
POST /api/app/gate/guard
{
  "agent": "refund-bot",
  "action": "refund",
  "target": "order:ord_8231",
  "params": { "orderId": "ord_8231", "amountMinor": 500 },
  "idempotencyKey": "refund:ord_8231",
  "amountMinor": 500,
  "currency": "USD"
}

→ 200
{
  "verdict": "allow",
  "executionToken": "sx_kQ2v…"
}

Quickstart

From zero to a first guarded call.

Five steps. Workflow identity, route, and credential authority are established once; the fifth is the call your agent makes before each guarded money action.

01

Create your workspace

Sign up at sevra.dev and create your organization. The free Observe tier needs no card and gives you a workspace for routed guard calls and their decision records.

02

Register your agent

Add the agent that will propose money actions and configure its policy controls. The proactive endpoint enforces its real verdict in both live and shadow mode; mode is an audit annotation, not permission to bypass the result.

03

Create the workflow identity

In Billing → Workflow management, create one action family through one integration in one environment. Activate it only after the scope is correct. Observe supports an evaluation workflow; paid workflow allowances govern production activation.

04

Assign the route and issue a key

Assign the intended agent to that exact action family, then issue the one-time workflow key. Store the swk_ value in an approved secret manager; Sevra stores only its hash and will not show it again.

05

Make the first call

For this release candidate, call the HTTP endpoint directly or use the reviewed local packages/sevra-guard source. The source package is named sevra and its guard entry point is sevra/guard, but it has not been published to npm yet. Wrap the money action, gate on the verdict, and execute only with the token.

first-call.ts
import { SevraGuard } from "sevra/guard";

const sevra = new SevraGuard({
  apiKey: process.env.SEVRA_GUARD_KEY!,
  baseUrl: "https://sevra.dev",
});

const decision = await sevra.guard({
  agent: "refund-bot",
  action: "refund",
  target: "order:ord_8231",
  params: { orderId: "ord_8231", amountMinor: 500, currency: "USD" },
  idempotencyKey: "refund:ord_8231",
  amountMinor: 500,
  currency: "USD",
  direction: "out",
});
// decision.verdict is "allow", "require_approval", or "block"

SDK source preview. The registry command will be npm install sevra, with imports from sevra/guard, after the source candidate is explicitly published and verified. The HTTP reference below is the current integration contract.


Integration guide

Put the guard before your refund call.

Your agent keeps its own logic. The integration submits the proposed action, waits or polls when a human decision is required, and calls Stripe only after an Allow with an execution token.

refund.ts, before
// Before: the agent moves money directly. Nothing stands
// between a bad plan and an unrecoverable refund.
async function refundOrder(orderId: string, amountMinor: number) {
  return stripe.refunds.create({
    payment_intent: orderId,
    amount: amountMinor,
  });
}
refund.ts, after
// After: the same function, gated. Sevra evaluates the action,
// holds it for a human when it needs one, and the refund only
// fires with an execution token.
import { SevraGuard, SevraBlocked } from "sevra/guard";

const sevra = new SevraGuard({
  apiKey: process.env.SEVRA_GUARD_KEY!,
  baseUrl: "https://sevra.dev",
});

async function refundOrder(orderId: string, amountMinor: number) {
  const decision = await sevra.guard({
    agent: "refund-bot",
    action: "refund",
    target: `order:${orderId}`,
    params: { orderId, amountMinor, currency: "USD" },
    idempotencyKey: `refund:${orderId}`,
    amountMinor,
    currency: "USD",
    direction: "out",
    // Default mode blocks here until a human resolves a held action,
    // re-polling under the hood. Cap the wait if your agent cannot block:
    overallTimeoutMs: 10 * 60 * 1000,
  });

  if (decision.verdict !== "allow" || !decision.executionToken) {
    // Covers block, denied, timed_out, and collision. Never execute
    // without a token.
    throw new SevraBlocked(decision);
  }

  // Extend Sevra's one-token guarantee through Stripe's idempotency support.
  // Never call the provider on a path that bypasses this guard.
  return stripe.refunds.create(
    { payment_intent: orderId, amount: amountMinor },
    { idempotencyKey: decision.executionToken },
  );
}

Handling the three verdicts

verdictstatusWhat your agent does
allowapproved or executedExecute the action only if a token is present. Pass executionTokento a provider operation with compatible idempotency support; the provider's semantics determine whether retries collapse to one side effect.
require_approvalheldA human decides in the console or Telegram using the durable request and any case file that was created. Eligible refunds and payouts can include Stripe test-mode evidence when a tenant test key is configured. In default mode the SDK waits; in poll mode you receive the ticket and poll yourself.
blockblocked, denied, timed_out, collisionDo not execute. reasons carries the recorded decision rationale.

Waiting on a held action

The guard endpoint returns without waiting for a human. A held action comes back as a ticket, and there are three ways to learn the outcome. Default block mode: the SDK re-polls the decision endpoint with bounded 25 second server waits until the hold resolves or your overallTimeoutMs passes, at which point it returns a block. Poll mode: you get the ticket immediately and call sevra.decision(requestId) on your own schedule. Webhook mode: pass a callbackUrl (https, public hostname) and Sevra POSTs a signed guard.resolved notification when the hold resolves. The payload never carries the execution token; treat it as a wake-up and claim the decision with sevra.decision(requestId). Verify the x-sevra-signature header: t=<unix>,v1=<hex>, HMAC-SHA256 of the timestamp, a dot, then the raw body, keyed with the tenant callback key from Channels, not the workflow bearer key, with a 300 second tolerance. Delivery is best-effort with one retry, so keep a poll as the fallback.

poll-later.ts
// If your agent cannot block, take the held ticket and poll later.
const ticket = await sevra.guard({ ...request, mode: "poll" });
if (ticket.status === "held") {
  // Persist ticket.requestId, then poll until status leaves "held".
  const decision = await sevra.decision(ticket.requestId);
}

The one rule

Execute only when the verdict is allow and an executionToken is present. A thrown network error, a malformed response, or any other status means the action does not run. That discipline is what keeps your integrated caller fail-closed. Sevra cannot control a provider call made on a path that bypasses this check.


HTTP reference

The wire protocol, language by language.

The SDK wraps the guard and decision endpoints. Any language with an HTTP client can use the same protocol. New integrations authenticate with a workflow-scoped swk_ bearer issued once from Billing. The legacy tenant key is accepted only when the agent and action resolve to one exact active workflow route.

POST /api/app/gate/guard

Evaluate an action your caller submits before execution. Allow and Block are terminal; Require approval returns a held ticket. The endpoint does not hold a connection open waiting for a human decision.

request
curl -s https://sevra.dev/api/app/gate/guard \
  -H "Authorization: Bearer $SEVRA_GUARD_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "refund-bot",
    "action": "refund",
    "target": "order:ord_8231",
    "params": { "orderId": "ord_8231", "amountMinor": 500, "currency": "USD" },
    "idempotencyKey": "refund:ord_8231",
    "amountMinor": 500,
    "currency": "USD",
    "direction": "out"
  }'
FieldTypeNotes
actionstring, requiredThe action class, for example refund, payout, transfer, ledger_write.
paramsobject, requiredThe action's own parameters. The complete request envelope is hashed for collision detection; reusing a key with any changed action field is rejected.
idempotencyKeystring, required, non-emptyYour decision-deduplication anchor, unique per business action. An empty key is a 400.
agentstring, optionalThe agent identifier registered in the console.
targetstring, optionalWhat the action touches, for example order:ord_8231.
amountMinor, currencyinteger + string, together or neitherValidated at the front door: amountMinor must be a safe non-negative integer in minor units, currency ISO 4217 shaped, 3 letters, normalized to uppercase. Providing one without the other is a 400.
direction"out" | "in", optionalAny other value is a 400.
timeoutSecondsnumber, optionalMay only shorten the server hold window, never extend it. Floor 30 seconds.
callbackUrlstring, optionalHttps with a public hostname, or the call is rejected 400 (invalid_callback_url). On resolution Sevra POSTs a signed, token-free guard.resolved wake-up to it.
200: allow
{
  "requestId": "3f7c9a1e-...",
  "verdict": "allow",
  "status": "approved",
  "executionToken": "sx_kQ2v...",
  "score": 120,
  "reasons": ["..."],
  "mode": "live"
}
200: require_approval
{
  "requestId": "3f7c9a1e-...",
  "verdict": "require_approval",
  "status": "held",
  "score": 512,
  "reasons": ["...", "tier:role=admin,count=1"],
  "mode": "live"
}
200: block
{
  "requestId": "3f7c9a1e-...",
  "verdict": "block",
  "status": "blocked",
  "score": 940,
  "reasons": ["..."],
  "mode": "live"
}
StatusMeaning
200A decision, including fail-closed blocks. score is an integer 0 to 1000, or null when the action was unevaluable. mode is live or shadow and is an audit annotation only.
400Invalid JSON; missing action, params, or idempotencyKey; empty idempotency key; or invalid movement fields.
401Missing or invalid workflow/legacy guard key.
403workflow_route_not_authorized: the workflow key does not authorize this exact active agent and action route. No canonical request or execution token is created.
409idempotency_key_collision: the key was reused with a changed request-envelope field. Retrying with an identical full envelope replays the stored decision with a 200 instead.
429Authenticated rate limit reached. Honor Retry-After; the rejected attempt has no verdict or execution token.
503Workflow configuration or resolution is unavailable, or the canonical request could not be persisted. The response is fail-closed and carries no execution capability.

GET /api/app/gate/requests/{id}/decision?wait=0..25

Poll a held request. wait optionally holds the connection up to 25 seconds and returns the instant the request leaves held; otherwise it answers immediately. A 204 means still held, so poll again. When the hold has been approved and the linked guard request has been released successfully, the first poll claims the single execution and returns status: "executed" with the token; polling again is safe and returns the same token. If approval evidence commits but release fails, the request remains held and token-free and requires operator intervention; automated reconciliation is not implemented. A denied hold returns denied, an expired one timed_out, both with verdict block. An unknown id returns a 200 with verdict block and reasons: ["not_found"], the fail-closed shape.

request
curl -s "https://sevra.dev/api/app/gate/requests/$REQUEST_ID/decision?wait=25" \
  -H "Authorization: Bearer $SEVRA_GUARD_KEY"
200 after approval
{
  "requestId": "3f7c9a1e-...",
  "verdict": "allow",
  "status": "executed",
  "executionToken": "sx_kQ2v...",
  "score": 512,
  "reasons": ["..."]
}

GET /api/app/gate/requests/{id}

Fetch the full request record plus the linked approval's decision timeline, with approver identity stripped: { request, approval }. The approval carries id, required, status, and decisions as { verdict, via, at } entries. Returns 404 not_found for an unknown id and 401 on a bad key.

request
curl -s https://sevra.dev/api/app/gate/requests/$REQUEST_ID \
  -H "Authorization: Bearer $SEVRA_GUARD_KEY"

Semantics

The guarantees behind the verdicts.

Fail-closed

If Sevra cannot evaluate an action, it blocks. An unprovisioned control layer, a thrown dependency, a persistence error, or a failed audit write on a would-be Allow all produce a Block with no token. This protects calls routed through the guard; the caller must still refuse any provider path that bypasses the verdict.

Idempotency boundary

The idempotency key collapses retries to one decision: the same key with an identical complete request envelope replays the stored decision verbatim, and the same key with a changed request envelope is a 409 collision. On Allow, Sevra mints at most one execution token. Extending that protection to the external side effect requires a provider operation with compatible idempotency semantics and a caller that never bypasses the guard.

Hold timeout

A held action waits for a human only so long. The window is per-policy: a matched rule, then a matched protected path, then the org default, then the 900 second default, clamped to a plan ceiling. Your timeoutSeconds can only shorten it, floor 30 seconds. An unresolved hold times out to block.

Shadow mode

An agent not yet marked live runs in shadow mode. On this proactive guard endpoint the real verdict is still enforced: a would-be block is blocked and a would-be hold is held. Shadow is an audit annotation on the decision, and it never downgrades the verdict. The decision persists on the guard request; a would-be Allow still requires its initial gate-event write, while non-authorizing lifecycle annotations can be best-effort. A shadow-mode Allow can still return an execution token.

Guard your first refund path.

Start with one money action. Expand only by routing each new caller through the guard and verifying that it honors the returned verdict.