AI agentsspecificationJSON Schemariskcontracts

Capability specs as contracts: how agents reason about risk before calling

7 min read

TL;DR

  • Encodes risk directly in the spec - side effects, pricing, required scopes, and rate limits ship as first-class fields, not prose an agent has to infer.
  • Enforces its own invariants - any action marked "sideEffects": "transactional" must declare pricing and auth, or the registry rejects it outright.
  • Gives agents a decision tree, not a guess - before calling, an agent checks retry-safety, cost, permissions, and reversibility straight from the signed spec.

The gap OpenAPI doesn't close

OpenAPI excels at documenting APIs. It describes endpoints, parameters, and responses in detail. Any developer - human or LLM - can read a spec and understand what an API does. That's the problem OpenAPI was built to solve, and it solves it well.

But developers bring judgment a spec never states. They check pricing pages. They read warnings in the docs. They gauge, on instinct, whether retrying a failed call is safe. All of that lives outside the formal specification.

An autonomous agent has none of that judgment:

  • No pricing page to consult - it never reads one.
  • No name to intuit meaning from - book_room doesn't tell it that money moves.
  • No experience to fall back on - every risk signal must be explicit and machine-readable before the call.

That's the gap OpenAPI leaves open. A capability spec is built to close it.

What a capability spec actually encodes

The sgovr-spec.schema.json is a JSON Schema 2020-12 document. Like OpenAPI, it defines action inputs and outputs. Unlike OpenAPI, it makes commerce and trust metadata first-class fields - not prose.

Four fields have no dedicated home in OpenAPI:

  • Side effects - whether the action is safe to retry, mutates state, or involves money. This is the most vital signal an agent needs before invocation.
  • Pricing - the billing model and, for transactional actions, the exact take-rate in basis points. The agent understands the cost structure *before* the call, not from a subsequent invoice.
  • Required auth scopes - which scopes the calling agent's mandate must carry. The agent can verify its authorization *before* attempting the call.
  • Rate limits - requests per minute per agent. The agent can self-throttle, preventing unnecessary 429 errors.

Two more flags sharpen the picture further:

  • Refundable - whether a transactional action can be reversed. An agent planning a fallback path critically needs this information.
  • SCA required - whether a step-up authentication challenge will be mandated, regardless of amount, for provider-designated high-risk actions.

None of this is inferred from convention or HTTP method. Every field is explicit, schema-validated, and enforced at runtime.

Lux Hotels: two actions, two very different risk profiles

The demo provider ships two actions. Comparing their spec entries shows exactly what an agent learns before it calls either one.

search_rooms - safe to call freely

The search_rooms action declares:

  • "sideEffects": "none"
  • "pricing": { "model": "free" }
  • No auth.scopes field - no special permission required.
  • Rate limit: 120 requests per minute.

Four facts, one conclusion: this action is idempotent, free, and open. An agent can retry it on transient failures, use it speculatively, and parallelize it across date ranges - all without asking a human first.

book_room - transactional, scoped, priced

The book_room action tells a different story:

  • "sideEffects": "transactional" - this action moves money. It cannot be safely retried without an idempotency key, which the gateway will enforce.
  • "pricing": { "model": "per_transaction", "takeRateBps": 290, "takeRateFixedMinor": 30 } - the platform's take-rate is 2.90% plus €0.30. The agent knows the cost structure of intermediation *before* it calls, avoiding invoicing surprises.
  • "auth": { "scopes": ["booking:write", "payments:charge"] } - the calling agent's mandate must carry both of these scopes. The agent can verify its own authority before attempting the call; if either scope is missing, it can request escalation from the principal rather than failing mid-transaction.
  • "rateLimit": { "rpm": 30 } - one booking per two seconds per agent. A loop attempting to book faster will be rejected; the agent can pace itself, conserving retries.
  • "refundable": true - if the booking needs cancellation, the principal can recover funds via a refund call. The agent can communicate this guarantee to the principal before they commit.

None of this is visible from the name or the HTTP method. It's explicit in the spec. An agent that reads the spec first behaves differently than one that doesn't.

The schema enforces its own invariants

A spec that allows correct fields but doesn't require them is a suggestion, not a contract. The sgovr-spec.schema.json uses JSON Schema's allOf, if, and then keywords to make critical omissions structurally invalid.

The core invariant: any action with "sideEffects": "transactional" must declare both pricing and auth. This isn't a convention. It's a conditional requirement baked into the schema. A transactional action without pricing gets rejected by the validator - it never reaches the registry.

This cuts both ways. Providers can't register ambiguous transactional actions. Agents can trust that every transactional action in the registry carries complete pricing and auth metadata - not because providers were careful, but because the schema doesn't allow anything else.

Validating a spec

The TypeScript SDK ships a CLI that validates a spec against the JSON Schema and checks the transactional invariants: node dist/cli.js validate <spec.json>. The conform command goes further and probes the declared adapter endpoints to confirm they respond as the spec claims.

How an agent reasons about risk before calling

Before invoking any action, an agent pulls its spec and runs a decision tree:

  • Is this action safe to retry? If sideEffects is"none", yes. If it is "stateful" or "transactional", retrying without an idempotency key risks duplicate mutations or double charges. The agent knows its path before the first attempt.
  • Will this cost money? pricing.model answers this:"free", "per_call", or "per_transaction". For per-transaction actions, takeRateBps and takeRateFixedMinor provide the exact platform fee. The agent can surface this to the principal before proceeding.
  • Do I have permission? Comparing the action's auth.scopesagainst the scopes in the agent's own mandate reveals whether the call will pass the gateway's scope check or return a 403. No round-trip is needed to find out.
  • Do I need an idempotency key? Transactional actions require one, enforced by the gateway. The agent can generate and store the key *before* calling, rather than discovering the requirement from a 400 response.
  • Can this be undone? refundable is the binary an agent needs to answer "what happens if I'm wrong?" before committing. A non-refundable transactional action carries a different risk profile than a refundable one, and the spec directly encodes this distinction.
  • Will this trigger a step-up challenge? If sca istrue, the gateway will require a short-lived step-up token before proceeding, regardless of the amount. The agent can request that token from the principal in advance, avoiding a 401 mid-transaction.

Each of these questions finds a machine-readable answer within the spec. An agent consulting the spec before acting doesn't guess; it *derives* its understanding directly from the provider's explicit declaration.

Same format, different priorities

OpenAPI and capability specs share a lot. Both are JSON Schema documents. Both define inputs and outputs. Both are version-controlled. Both are readable by LLMs trained on code.

Where they diverge is what each treats as first-class:

  • OpenAPI documents an API's surface for a human developer. Pricing, authority, and reversibility live in prose - description strings, external docs, informal conversations.
  • Capability specs organize around actions and declare what an action costs, requires, and risks as schema fields a machine can evaluate - not prose a human has to read.

The analogy: OpenAPI serves a developer the way a capability spec serves an agent. Same underlying service, two different audiences, two different jobs.

The spec as the single source of truth

In Sgovr, the spec file isn't documentation. It's the authoritative input for every system component:

  • Registry - validates the spec on upload, rejects any invariant violation.
  • Gateway - enforces scopes, idempotency, and rate limits at call time, reading directly from the registered spec, not its own config.
  • Conformance CLI - verifies declared endpoints against the spec's input schema.
  • Agents - call sgovr_get_spec to plan their invocations before making them.

The spec is signed by the provider's key - an ES256 detached signature over canonical JSON. That's a non-repudiation guarantee: if a provider later disputes a cost or scope requirement, the signed spec is the evidence. This isn't a gentlemen's agreement. It's cryptographically attested.

Changing terms means registering a new signed spec. The gateway only ever enforces what's currently registered. A provider that changes pricing or scope without updating the spec trips a policy violation - before it becomes a billing dispute.

Why this scales

The immediate payoff is safer agents. Machine-readable risk metadata makes an agent more cautious and more accurate than one guessing from action names or HTTP methods.

The bigger payoff is scale.

Human marketplace intermediaries read terms, negotiate pricing, and check compliance by hand. An agent marketplace intermediary can't work that way - it needs those terms machine-readable, with no human in the loop.

Capability specs are that encoding. They turn "an API an agent can call" into "a business capability an agent can evaluate, negotiate, and safely execute against." They don't replace the legal relationship between provider and platform - they state its technical terms in a format both parties' software can enforce. That's the only enforcement that scales.

Further reading

For how the gateway enforces spec invariants at call time - mandate verification, scope checks, idempotency, and reserve-to-settle - see What happens when your AI agent double-books a hotel room. For the relationship between MCP's tool layer and the trust layer above it, see MCP gives agents tools. What gives agents trust?.