API reference

The core authorize() function and the types it uses. Everything an agent needs to check whether an action is allowed before it runs.

Plain-English model: a request is what the agent wants to do. A policy is your local checklist for what is allowed. A decisionis Shotoku’s answer — approved, denied, or pending. The ledger is the append-only local audit log.

authorize(request, options)

The main function. Describe what the agent wants to do, point it at your policy file and ledger, and it returns a decision.

typescript
import { authorize } from "@shotoku/core";

const response = await authorize(
  {
    actor:    "agent-001",
    action:   "api_call",
    resource: "api.openai.com",
    amount:   0.02,
  },
  {
    policyPath: "./policy.yaml",
    ledgerPath: "./data/decisions.jsonl",
  },
);

Under the hood it:

  1. Loads your policy file
  2. Reads rolling 24-hour spend totals from the ledger
  3. Evaluates the request against your rules
  4. Writes the decision to the ledger
  5. Returns the result

Shotoku fails closed. If the request is malformed, the policy file is missing or invalid, or the ledger is corrupt, Shotoku does not approve the action.

Options

FieldRequiredWhat it does
policyPathyesPath to your policy.yaml rules file
ledgerPathyesPath to the local file where decisions are stored

AuthorizeRequest

Describes the action an agent wants to take.

typescript
interface AuthorizeRequest {
  readonly actor:    string;
  readonly action:   AgentAction;
  readonly resource: string;
  readonly rail?:    ExecutionRail;
  readonly amount?:  number;
  readonly context?: Record<string, unknown>;
}
FieldWhat it means
actorWho is requesting the action — a name or ID for your agent
actionThe category of action being requested — see AgentAction below
resourceWhat the agent is acting on — a domain, API endpoint, or service name
railOptional: the execution channel (x402, mcp, api, etc.)
amountOptional: how much this action costs in USD, if it involves spending
contextOptional: extra details recorded alongside the decision (must be JSON-serializable)

AuthorizeResponse

What authorize() gives back.

typescript
interface AuthorizeResponse {
  readonly approved:    boolean;
  readonly status:      AuthorizationStatus;
  readonly reasons:     readonly ReasonItem[];
  readonly explanation: Explanation;
  readonly decisionId:  string;
  readonly timestamp:   string;
}
FieldWhat it means
approvedtrue if the action can proceed, false otherwise
statusThe full verdict — see AuthorizationStatus below
reasonsA list of specific checks that were run and what they found
explanationA plain-English summary of the decision, ready to show to a user
decisionIdA unique ID for this decision, e.g. dec_abc123
timestampWhen the decision was made, in ISO 8601 format

AuthorizationStatus

The three possible outcomes of an authorization check.

typescript
type AuthorizationStatus = "approved" | "denied" | "pending_approval";
ValueWhat it means
approvedThe request passed all policy checks. The agent can proceed.
deniedThe request was blocked by a policy rule. The agent should stop.
pending_approvalNo rule automatically decided this. A human must run shotoku approve or shotoku deny.

AgentAction

The type of thing an agent wants to do.

typescript
type AgentAction =
  | "purchase"
  | "api_call"
  | "execute_code"
  | "send_email"
  | "mcp_tool"
  | "custom";

Use custom for anything that does not fit the other categories.


ExecutionRail

The channel through which an action would be executed. Optional — include it when you want to write rules that apply to a specific channel only.

typescript
type ExecutionRail = "x402" | "mcp" | "api" | "code" | "custom";

ReasonItem

One specific check that ran during policy evaluation. A decision always includes one or more of these.

typescript
interface ReasonItem {
  readonly type:
    | "policy_match"
    | "budget_check"
    | "limit_check"
    | "blocked"
    | "escalated";
  readonly text: string;
}
TypeWhat triggered it
policy_matchA rule in your policy file matched this request
limit_checkThe amount was checked against a per-transaction cap
budget_checkThe rolling 24-hour spend total was checked against a daily cap
blockedThe request was explicitly blocked — e.g. no policy file found
escalatedThe request was sent for human review

LedgerEntry

One recorded decision. Stored as a single line in decisions.jsonl — one JSON object per line, append-only. Each line is a self-contained record of one decision. You can inspect the file directly in any text editor.

typescript
interface LedgerEntry {
  readonly decisionId: string;
  readonly timestamp:  string;
  readonly request:    AuthorizeRequest;
  readonly response:   AuthorizeResponse;
}

Every new ledger record includes an integrity object that links it to the previous ledger hash:

typescript
interface LedgerIntegrity {
  readonly version:      1;
  readonly sequence:     number;
  readonly previousHash: string;
  readonly hash:         string;
}

If a ledger line is malformed, Shotoku reports the ledger as corrupt instead of skipping the bad line. Skipping would make budgets and approval state hard to trust.


Signed snapshots

A signed snapshot records the hash of the policy file, the current ledger head hash, and a keyed signature over those fields. Use snapshots when you want to prove later that the policy and ledger head have not changed since a point in time.

typescript
import { createSignedSnapshot, verifySignedSnapshot } from "@shotoku/core";

const snapshot = await createSignedSnapshot({
  policyPath: "./policy.yaml",
  ledgerPath: "./data/decisions.jsonl",
  secret:     process.env.SHOTOKU_SNAPSHOT_SECRET!,
});

const result = await verifySignedSnapshot(snapshot, {
  secret: process.env.SHOTOKU_SNAPSHOT_SECRET!,
});

Shotoku uses HMAC-SHA256 for local snapshots. It does not create, store, or manage signing keys — the secret comes from the caller.