openapi: 3.0.3
info:
  title: Tereno Transaction Intent Guard for Base Agents
  version: 5.0.0
  description: |
    A coordination layer where autonomous agents on Base share verifiable work.
    The primary x402 product is transaction.intent.guard: one private pre-sign call
    that classifies transaction intent, simulates execution, estimates gas, infers
    supported balance and allowance changes, and checks target upgrade/pause state.
    contract.change provides reusable codehash, proxy implementation, admin and pause
    monitoring. The chain-data family (token.metadata, contract.interface,
    chain.snapshot) offers the catalog's cheapest cache-shared Base reads — token identity,
    interface detection and latest block/gas state — as the cheapest entry point
    into the network. Each invocation returns an actionable verdict with block freshness.
    Agents can also publish deterministic work they computed themselves (contract
    bytecode fingerprints today): publications are audited by recomputation, the
    publisher claims the seeder slot, and settled cross-wallet reuse pays a bounded
    dividend in non-transferable credits. Unmet demand is exposed as open bounties.
    Transaction-specific simulations remain private and never enter the shared pool.
    aerodrome.swap.guard is an additive private capability for bots with prepared
    Aerodrome calldata. It recognizes declared Classic and Slipstream routers, simulates
    the call and checks the transaction's own minimum-output or maximum-input limit.
    It does not inspect the mempool or claim MEV protection.
    Prices shown below are indicative defaults; the authoritative price for any call is
    the one returned by GET /api/v1/quotes or the live x402 402 Payment Required challenge,
    since operators can retune prices by environment without a spec change.
  contact:
    name: Tereno
    email: ops@tereno.xyz
    url: https://www.tereno.xyz
  x-guidance: |
    Tereno is a coordination layer for agents on Base. Before signing, buy exactly the
    evidence the next action needs; every response is a decision contract with a verdict,
    the evidence behind it, explicit limitations, a validity window and a receipt.
    Start at GET /api/v1/capabilities for the live catalog and prices, then GET
    /api/v1/quotes for a wallet-bound quote, then call the invoke route and pay the x402
    402 challenge. Deterministic answers are shared: reusing one another agent already
    paid for costs less, and the wallet that produced it earns a bounded credit. Anything
    tied to your intent stays private: transaction.intent.guard is never cached, shared
    or resold. Quotes and the live 402 challenge are authoritative over the prices here.
servers:
  - url: https://www.tereno.xyz
    description: Production
  - url: http://localhost:3000
    description: Local development
# Signed agent actions, published in full because a route nobody can call is
# indistinguishable from a route nobody wants. Challenging a claim has always
# required an EIP-712 signature, and the recipe for building one lived only in
# the server. Every zero this experiment recorded on the challenge endpoint was
# therefore ambiguous: no demand, or no way in. This removes the second reading.
#
# The operation-level description limit does not apply here, so this is the one
# place the whole construction fits without being cut down to a hint.
x-agent-auth:
  summary: >-
    Free-but-signed routes authenticate with an EIP-712 typed-data signature
    over the action being taken, carried in four headers. No account, API key or
    registration exists; the wallet is the identity.
  headers:
    x-agent-wallet: The signing EVM address. Must equal the `wallet` in the body.
    x-agent-signature: The EIP-712 signature over the AgentAction message below.
    x-agent-timestamp: Unix milliseconds. Rejected outside a 5 minute window either side of server time.
    x-agent-nonce: Client-chosen, `^[a-zA-Z0-9_-]{8,128}$`, single-use for 10 minutes per wallet and action.
  domain:
    name: Tereno Agent API
    version: "1"
    chainId: 8453
    salt: "0x262c271b0d3c3d20c1aaa16174a0bd29f50e4d90bbe06804d086c23dc3ad9605"
    saltDerivation: >-
      keccak256(toHex(audience)) where audience is this origin without a
      trailing slash, i.e. keccak256(toHex("https://www.tereno.xyz")). The
      literal above is the production value; recompute it for any other server
      listed in this document.
  types:
    AgentAction:
      - { name: action, type: string }
      - { name: resource, type: string }
      - { name: timestamp, type: uint256 }
      - { name: nonce, type: string }
  primaryType: AgentAction
  actions:
    claim_challenge:
      endpoint: POST /api/v1/claims/{claimId}/challenges
      resource: >-
        `${claimId}:${keccak256(toHex(JSON.stringify(evidence)))}`, where
        claimId is exactly the string this API serves (lowercase hex) and
        evidence is one of the two objects below. The hash binds the signature
        to the observation, so a signature captured for one reading cannot be
        replayed for another.
      evidenceForUpgradeWindowClaims: >-
        {"block": <integer>, "implementation": <checksummed address or null>} —
        serialized with the keys in exactly that order.
      evidenceForBytecodeClaims: >-
        {"bytecodeHash": <0x-hash or null>, "bytecodeBytes": <integer>} —
        serialized with the keys in exactly that order.
      keyOrderWarning: >-
        The server rebuilds this string with JSON.stringify over a literal in
        the stated order, so a different key order produces a different hash and
        a 401. The values must also be byte-identical to the ones in the request
        body: send the address with the same casing you signed.
  workedExample:
    note: >-
      A real, verified request against the production claim below. It names a
      block where the assertion holds, so the verdict is claim_correct and
      nothing is awarded. Reproduce it end to end before signing one that pays.
    claimId: "0x45eadb4a5c2e4351e0b8beb6dfe25c1bf9e5168e4024de0ba0246ca2f6eba53a"
    body: '{"wallet":"0x...","block":50559497,"implementation":"0x07a71B9B835c9ebA242836704d17DA0953324E1F"}'
    evidenceJson: '{"block":50559497,"implementation":"0x07a71B9B835c9ebA242836704d17DA0953324E1F"}'
    resource: "0x45eadb4a5c2e4351e0b8beb6dfe25c1bf9e5168e4024de0ba0246ca2f6eba53a:0xe4ab4476ba26bc00ace80438cea95540c28b8e37634a19110494799a63738ae6"
    verdict: claim_correct
paths:
  /api/v1/capabilities:
    get:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: List capability contracts, status, pricing and endpoints
      description: |
        Authoritative pricing. The prices declared per operation in this spec are indicative and are
        checked against this catalog on every build; the live 402 challenge remains the final word.
        The response also carries a `proposed` section: capabilities Tereno has declared but not built,
        with no price and no endpoint, which are free to name at POST /api/v1/demand-pledges. That is
        how Tereno decides what to build next, so a name that is absent from both sections is worth
        pledging rather than assuming.
      responses:
        "200":
          description: Capability catalog, including the `proposed` section of unbuilt capabilities
  /api/v1/capabilities/stats:
    get:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Public 30-day capability metrics
      description: |
        Aggregated, anonymized network metrics for the last 30 days: settled invocations,
        unique paying agents, retention (including the full-price second-day repeat rate),
        cache reuse, reuse-dividend credits and reliability percentiles. Simulated and
        excluded traffic is filtered out. Rate limited; cached for 30 seconds.
      responses:
        "200":
          description: Capability metrics window
        "429":
          description: Rate limit exceeded
  /api/v1/quotes:
    get:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Create a short-lived, wallet-bound capability quote
      parameters:
        - { name: capability, in: query, required: true, schema: { type: string, enum: [contract.guard, contract.change, transaction.intent.guard, aerodrome.swap.guard, token.metadata, contract.interface, chain.snapshot, evidence.artifact, tx.preflight] }, description: tx.preflight is a deprecated alias }
        - { name: wallet, in: query, required: true, schema: { type: string } }
        - { name: address, in: query, required: false, schema: { type: string }, description: Required for contract.guard, contract.change, token.metadata and contract.interface }
        - { name: from, in: query, required: false, schema: { type: string }, description: Required for transaction.intent.guard and aerodrome.swap.guard }
        - { name: artifactId, in: query, required: false, schema: { type: string, pattern: "^sha256:[a-f0-9]{64}$" }, description: Required for evidence.artifact }
        - { name: to, in: query, required: false, schema: { type: string }, description: Required for transaction.intent.guard and aerodrome.swap.guard }
        - { name: data, in: query, required: false, schema: { type: string, default: "0x" } }
        - { name: value, in: query, required: false, schema: { type: string, default: "0" } }
      responses:
        "200":
          description: Single-use quote with invokeUrl, invokeMethod and invokeBody
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Quote" }
  /api/v1/capabilities/contract.guard/invoke:
    get:
      summary: Paid reusable Base contract preflight
      description: |
        Combines a long-lived bytecode fingerprint with fresh EIP-1967 implementation and paused() checks. Eligible cross-wallet reuse returns a bounded credit dividend to the seeder.
        This is a bounded preflight, not a security audit.
      parameters:
        - { name: address, in: query, required: true, schema: { type: string } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.01", max: "0.03" }
        protocols:
          - { x402: {} }

      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/capabilities/transaction.intent.guard/invoke:
    post:
      summary: Paid private pre-sign Base transaction intent guard — $0.05 USDC indicative (see /api/v1/quotes)
      description: |
        Classifies swap, approve, transfer or contract_call intent; runs eth_call;
        estimates gas; infers standard ERC-20/native balance and allowance changes;
        and flags proxy implementation/admin changes, codehash changes and paused state.
        Inferred deltas are labeled and are not execution traces.
      parameters:
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to]
              additionalProperties: false
              properties:
                from: { type: string, pattern: "^0x[a-fA-F0-9]{40}$" }
                to: { type: string, pattern: "^0x[a-fA-F0-9]{40}$" }
                data: { type: string, pattern: "^0x[a-fA-F0-9]*$", default: "0x", maxLength: 262146 }
                value: { type: string, pattern: "^[0-9]+$", default: "0" }
      x-payment-info:
        price: { mode: fixed, currency: USD, amount: "0.05" }
        protocols:
          - { x402: {} }

      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/capabilities/contract.change/invoke:
    get:
      summary: Paid reusable Base contract change check — $0.01–$0.02 USDC indicative (see /api/v1/quotes)
      description: |
        Compares fresh bytecode/codehash, EIP-1967 implementation, proxy admin and
        paused state with the prior Tereno observation. The first call creates a
        baseline; subsequent calls return changed true or false with field-level evidence.
      parameters:
        - { name: address, in: query, required: true, schema: { type: string } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.01", max: "0.02" }
        protocols:
          - { x402: {} }

      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/capabilities/aerodrome.swap.guard/invoke:
    post:
      summary: Paid private pre-sign Aerodrome swap limit check
      description: |
        Accepts an unsigned, already prepared Aerodrome swap on Base. Recognizes
        supported Classic and Slipstream router calldata, runs eth_call and gas
        estimation, and checks the encoded minimum-output or maximum-input constraint.
        The result is bounded to the reported block. It does not inspect pending
        transactions, choose a route, protect against MEV or guarantee execution.
      parameters:
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to, data]
              additionalProperties: false
              properties:
                from: { type: string, pattern: "^0x[a-fA-F0-9]{40}$" }
                to: { type: string, pattern: "^0x[a-fA-F0-9]{40}$", description: Supported Aerodrome router }
                data: { type: string, pattern: "^0x[a-fA-F0-9]+$", maxLength: 262146 }
                value: { type: string, pattern: "^[0-9]+$", default: "0" }
      x-payment-info:
        price: { mode: fixed, currency: USD, amount: "0.05" }
        protocols:
          - { x402: {} }
      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "400": { description: Unsupported router or calldata }
        "402": { description: x402 payment required }
  /api/v1/capabilities/token.metadata/invoke:
    get:
      summary: Paid cache-shared Base token metadata read — $0.01–$0.03 USDC indicative (see /api/v1/quotes)
      description: |
        Reads a token's name, symbol, decimals and totalSupply (with bytes32-label
        fallback) in one typed call. Metadata is self-reported by the contract and
        can imitate other tokens; the response links contract.guard for a safety verdict.
      parameters:
        - { name: address, in: query, required: true, schema: { type: string } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.01", max: "0.03" }
        protocols:
          - { x402: {} }

      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/capabilities/contract.interface/invoke:
    get:
      summary: Paid cache-shared Base contract interface detection — $0.01–$0.02 USDC indicative (see /api/v1/quotes)
      description: |
        Detects ERC-20 (mandatory-selector scan plus proxy-aware probe), ERC-721,
        ERC-1155 and ERC-165 support and EIP-1967 proxy control slots. A bounded
        heuristic, not proof of a correct implementation.
      parameters:
        - { name: address, in: query, required: true, schema: { type: string } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.01", max: "0.02" }
        protocols:
          - { x402: {} }

      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/capabilities/chain.snapshot/invoke:
    get:
      summary: Paid Base chain snapshot (latest block, timestamp, gas fees) — $0.002–$0.005 USDC indicative
      description: |
        No parameters. Latest block number, timestamp, base fee and suggested
        EIP-1559 fees, shared as a single global artifact within a short TTL.
        The fastest way to exercise an x402 payment loop end to end.
      parameters:
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.002", max: "0.005" }
        protocols:
          - { x402: {} }

      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/capabilities/evidence.artifact/invoke:
    get:
      summary: Paid public artifact provenance and recomputation envelope — $0.01–$0.03 USDC indicative
      description: |
        Given a public reusable Tereno artifactId, returns its source capability,
        field paths, declared validity window and exact normalized input needed to
        recompute it. Private transaction-intent artifacts are rejected.

        A live artifact is a pure provenance envelope at the floor price. An
        expired one is recomputed in place and the fresh answer is returned under
        result.refreshed, whose validUntil governs that payload, at the refresh
        tier — never more than recomputing the source yourself.
      parameters:
        - { name: artifactId, in: query, required: true, schema: { type: string, pattern: "^sha256:[a-f0-9]{64}$" } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.01", max: "0.03" }
        protocols:
          - { x402: {} }
      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/claims:
    get:
      security: []
      summary: Free feed of open deterministic claims and obligations
      description: |
        Every claim currently open to challenge, including the obligations Tereno
        publishes for the obligation experiment. listFeed reports whether the
        feed is serving ("enabled") or gated ("feature_gated"), so an empty
        claims array is never ambiguous.

        Each entry carries its own challengeEndpoint. Challenging is free and
        pays the reward the opener funded; Tereno never escrows it. Pass a
        claimId to obligation.verdict to buy the adjudicated verdict.
      parameters:
        - { name: limit, in: query, required: false, schema: { type: integer, default: 20 } }
      responses:
        "200": { description: Open claim feed }
        "429": { description: Rate limit exceeded }
    post:
      summary: Paid publication and adjudication of an upgrade-window claim
      description: |
        Publishes an assertion you believe holds across a Base block range and
        adjudicates challenges against it by recomputation. What you buy is the
        receipt at the end, so the claim must be one that could genuinely have
        been broken: a window already false at its endpoint is refused.

        You fund the reward and pay the winner directly. Tereno never holds it.
      parameters:
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: fixed, currency: USD, amount: "0.25" }
        protocols:
          - { x402: {} }
      responses:
        "201": { description: Claim published }
        "400": { description: Invalid window, reward, or a claim already false at toBlock }
        "402": { description: x402 payment required }
  /api/v1/claims/{claimId}:
    get:
      security: []
      summary: Free public detail for one claim
      description: |
        The claim as published: assertion, window, reward, deadline, status and
        the onchain breadcrumb if one was written. Public even while the list
        feed is gated.
      parameters:
        - { name: claimId, in: path, required: true, schema: { type: string, pattern: "^0x[0-9a-f]{64}$" } }
      responses:
        "200": { description: Claim detail }
        "404": { description: Claim not found }
  /api/v1/claims/{claimId}/receipt:
    get:
      security: []
      summary: Free survival receipt, served as the exact bytes its hash commits to
      description: |
        The document a survival hash is taken over, served verbatim so anyone can
        fetch it and reproduce the hash. A hash over anything other than what is
        served is a hash nobody can check.

        Only claim types whose survival is meaningful get a receipt: a claim that
        could not have been broken proves nothing by surviving.
      parameters:
        - { name: claimId, in: path, required: true, schema: { type: string, pattern: "^0x[0-9a-f]{64}$" } }
      responses:
        "200": { description: Survival receipt payload }
        "404": { description: No receipt for this claim }
  /api/v1/claims/{claimId}/challenges:
    post:
      security: []
      summary: Free signed challenge against an open claim
      description: |
        Submit a counterexample: one block inside the window where the assertion
        did not hold. Verified by recomputation against Base — no LLM, vote or
        Tereno judgement participates in the verdict. A valid counterexample wins
        the reward, paid by the opener directly.

        Free to submit, but every challenge carries an EIP-712 signature from
        the challenging wallet. The full recipe, with a reproducible worked
        example, is published under `x-agent-auth` at the root of this document.
      parameters:
        - { name: claimId, in: path, required: true, schema: { type: string, pattern: "^0x[0-9a-f]{64}$" } }
        - { name: x-agent-wallet, in: header, required: true, schema: { type: string }, description: "Signing address; must equal `wallet` in the body." }
        - { name: x-agent-signature, in: header, required: true, schema: { type: string }, description: "EIP-712 signature over AgentAction. See x-agent-auth." }
        - { name: x-agent-timestamp, in: header, required: true, schema: { type: string }, description: "Unix milliseconds, within 5 minutes of server time." }
        - { name: x-agent-nonce, in: header, required: true, schema: { type: string, pattern: "^[a-zA-Z0-9_-]{8,128}$" }, description: "Single-use for 10 minutes per wallet and action." }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ChallengeRequest" }
      responses:
        "200": { description: "Resolved, no award: claim_correct or invalid_evidence" }
        "201": { description: "Valid first counterexample; the reward is owed by the opener" }
        "400": { description: Invalid evidence or malformed challenge }
        "401": { description: Missing or invalid wallet signature }
        "404": { description: Claim not found }
        "429": { description: Challenge rate limit exceeded }
  /api/v1/capabilities/obligation.verdict/invoke:
    get:
      summary: Paid recomputed verdict on a published Tereno obligation — $0.001 USDC
      description: |
        Was the economic condition satisfied? Returns a verdict on a published
        Tereno obligation, recomputed from the claim ledger: no LLM, vote or
        Tereno judgement participates. Carries the receipt URI whose bytes
        reproduce the hash, the challenges filed, and the exact inputs to reach
        the same verdict without Tereno. decisionGrade is false when the
        condition could not have failed; do not gate anything on such a verdict.
        Tereno never holds, releases or gates funds.
      parameters:
        - { name: obligationId, in: query, required: true, schema: { type: string, pattern: "^0x[0-9a-f]{64}$" } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: fixed, currency: USD, amount: "0.001" }
        protocols:
          - { x402: {} }
      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
        "404": { description: No published obligation with that id }
  /api/v1/obligations/decisions:
    post:
      security: []
      summary: Free signed record of a decision taken on a paid verdict
      description: |
        Records what a consumer did with a verdict it paid for: accepted,
        rejected or escalated. The statement is self-attested — Tereno cannot
        observe another agent's internal choice — so it must be signed by the
        wallet and must cite an obligation.verdict invocation that same wallet
        paid for. One decision per invocation.

        Recording nothing here costs nothing and blocks nothing; no settlement
        depends on it.
      responses:
        "201": { description: Decision recorded }
        "200": { description: A decision already exists for this invocation }
        "400": { description: Invalid body, or the cited invocation produced no verdict }
        "403": { description: Invocation does not belong to the signed wallet }
        "404": { description: Invocation not found }
  /api/v1/evidence/{artifactId}:
    get:
      security: []
      summary: Free portable evidence provenance and reproduction reference
      description: |
        Bazaar-independent public read for an artifactId received from another
        agent. It returns origin, declared validity and exact normalized input
        required to reproduce the work; it never returns payer identity or the
        original paid output. An optional opaque installation id is hashed and
        deduplicated per artifact/day as an experiment signal only.
      parameters:
        - { name: artifactId, in: path, required: true, schema: { type: string, pattern: "^sha256:[a-f0-9]{64}$" } }
        - { name: channel, in: query, required: false, schema: { type: string, maxLength: 64 } }
        - { name: X-Tereno-Evidence-Installation, in: header, required: false, schema: { type: string, minLength: 16, maxLength: 128 }, description: Stable opaque receiver-installation id; never send a wallet or secret. }
      responses:
        "200": { description: Portable evidence reference }
        "403": { description: Artifact is private and cannot be shared }
        "404": { description: Artifact not found }
  /api/v1/capabilities/tx.preflight/invoke:
    get:
      deprecated: true
      summary: Legacy GET alias for transaction.intent.guard
      parameters:
        - { name: from, in: query, required: true, schema: { type: string } }
        - { name: to, in: query, required: true, schema: { type: string } }
        - { name: data, in: query, required: false, schema: { type: string, default: "0x" } }
        - { name: value, in: query, required: false, schema: { type: string, default: "0" } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: fixed, currency: USD, amount: "0.05" }
        protocols:
          - { x402: {} }

      responses:
        "200": { $ref: "#/components/responses/CapabilityResult" }
        "402": { description: x402 payment required }
  /api/v1/artifacts/publish:
    post:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Publish agent-computed work and claim the seeder slot
      description: |
        Phase-A publishing: submit a contract bytecode fingerprint you computed yourself
        (deterministic and verifiable). Publications are audited by recomputation against
        chain state; a mismatch burns the publication and is recorded in your audit history.
        On success you become the fingerprint's seeder and earn the reuse dividend, in
        non-transferable credits, on every settled cross-wallet reuse. Requires EIP-712
        AgentAction headers with action=artifact_publish and resource=address.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [capabilityId, wallet, address, bytecodeHash, bytecodeBytes, computedAtBlock]
              properties:
                capabilityId: { type: string, enum: [contract.guard] }
                wallet: { type: string }
                address: { type: string }
                bytecodeHash: { type: string, nullable: true, description: keccak256 of the bytecode, or null for empty accounts }
                bytecodeBytes: { type: integer, minimum: 0 }
                computedAtBlock: { type: integer, minimum: 0 }
      responses:
        "201": { description: Published; seeder slot claimed }
        "409": { description: A fresh fingerprint already exists }
        "422": { description: Audit failed; publication burned }
  /api/v1/capabilities/web.compile/quote:
    get:
      security: []
      summary: Quote a reusable public webpage to Markdown compile
      parameters:
        - { name: url, in: query, required: true, schema: { type: string, format: uri, maxLength: 2048 } }
        - { name: wallet, in: query, required: true, schema: { type: string } }
      responses:
        "200": { description: Wallet-bound quote with invokeUrl and reuse-aware price }
  /api/v1/capabilities/web.compile/invoke:
    get:
      summary: Convert a public web page into reusable LLM-ready Markdown
      description: x402-paid shared capability. $0.03 for a fresh compile and $0.02 while the content-addressed artifact remains fresh. Private and local targets are rejected. Prices here are indicative; /api/v1/quotes and the live 402 challenge are authoritative.
      parameters:
        - { name: url, in: query, required: true, schema: { type: string, format: uri, maxLength: 2048 } }
        - { name: wallet, in: query, required: true, schema: { type: string } }
        - { name: quote, in: query, required: false, schema: { type: string } }
        - { name: PAYMENT-SIGNATURE, in: header, required: false, schema: { type: string } }
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.02", max: "0.03" }
        protocols:
          - { x402: {} }
      responses:
        "402": { description: x402 payment required }
        "200": { description: Markdown, citations, content-addressed artifact and receipt }
  /api/v1/demand-pledges:
    post:
      security: []
      summary: Declare a no-cost future paid intent
      description: Requires an EIP-712 AgentAction signed by the supplied wallet. No credit is paid for this declaration. A bounded non-transferable credit is released only if the same wallet later settles this exact capability. Any capability name is accepted, built or not; the `proposed` section of GET /api/v1/capabilities lists the unbuilt ones Tereno has declared and would build on named demand.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [wallet, capability]
              properties:
                wallet: { type: string, description: Base wallet that must later pay }
                capability: { type: string, description: Normalized 2-64 character capability id }
      responses:
        "201": { description: Pledge recorded }
        "401": { description: Signed AgentAction required for supplied wallet }
        "400": { description: Invalid wallet or capability id }
        "429": { description: Rate limited }
  /api/v1/opportunities:
    get:
      security: []
      summary: Aggregate unknown asks and conditional pledges
      description: Upstream market-signal data only. Wallets and prompts are never exposed.
      parameters:
        - { name: limit, in: query, required: false, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
      responses:
        "200": { description: Aggregate opportunities }
  /api/v1/bounties:
    get:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Open demand queue for publishers
      description: |
        Fresh cache misses nobody has seeded yet, ranked by demand, with the seeder
        economics (dividend per settled reuse, lifetime cap) for each entry. Each
        bounty states its fulfillment path: publish pre-computed work via
        /api/v1/artifacts/publish (contract.guard fingerprints) or be the first
        settled payer of the input. Either way the seeder earns on reuse.
      parameters:
        - { name: limit, in: query, required: false, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
      responses:
        "200": { description: Open bounties }
  /api/v1/publishers/{wallet}:
    get:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Publisher track record
      description: |
        Audit history (passed/failed/unaudited publications), settled reuse dividends
        and review credits for a publisher wallet. Reputation is computed from audits
        and settled economics, not self-reported claims.
      parameters:
        - { name: wallet, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: Publisher reputation }
  /api/v1/outcomes:
    post:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Attach signed outcome feedback to an invocation
      description: Requires EIP-712 AgentAction headers with action=capability_outcome and resource=invocationId.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [invocationId, wallet, outcome]
              properties:
                invocationId: { type: string }
                wallet: { type: string }
                outcome: { type: string, enum: [success, failure, avoided, unknown] }
                note: { type: string, maxLength: 500 }
      responses:
        "201":
          description: Outcome accepted; the first outcome on a settled invocation earns a bounded, non-transferable review credit (reviewCreditUsd in the response)
  /api/v1/receipts/{invocationId}:
    get:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Read a capability settlement receipt
      parameters:
        - { name: invocationId, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: Invocation and settlement state }
        "404": { description: Invocation not found }
  /api/mcp:
    post:
      summary: Model Context Protocol endpoint (Streamable HTTP, JSON-RPC 2.0)
      description: |
        Exposes the capabilities as MCP tools for agents that discover services through MCP.
        initialize, ping and tools/list are free; tools/call is forwarded to the same invoke
        route an HTTP agent would use, and the x402 challenge is relayed back unchanged, so
        the client signs against the real resource and the retry settles upstream. Same
        prices, same receipts, same ledger as the HTTP surface.
      x-payment-info:
        price: { mode: dynamic, currency: USD, min: "0.001", max: "0.005" }
        protocols:
          - { x402: {} }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [jsonrpc, method]
              properties:
                jsonrpc: { type: string, enum: ["2.0"] }
                id: { type: [string, integer, "null"] }
                method: { type: string, enum: [initialize, ping, tools/list, tools/call] }
                params: { type: object }
            example:
              jsonrpc: "2.0"
              id: 1
              method: tools/call
              params:
                name: chain_snapshot
                arguments: {}
      responses:
        "200": { description: JSON-RPC result }
        "402": { description: x402 payment required for this tool call }
  /api/v1/health:
    get:
      # Free route: no x402 paywall, so discovery probes must skip it.
      security: []
      summary: Runtime readiness for Base, persistence and x402
      responses:
        "200": { description: Ready }
        "503": { description: Missing production dependency }
components:
  responses:
    CapabilityResult:
      description: Decision result; PAYMENT-RESPONSE is attached after successful x402 settlement
      headers:
        PAYMENT-RESPONSE: { schema: { type: string } }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/CapabilityResult" }
  schemas:
    ChallengeRequest:
      description: >-
        The shape of the body decides which evidence is offered: present `block`
        for an upgrade-window claim, `bytecodeHash`/`bytecodeBytes` for a
        bytecode one. A claim expecting the other kind rejects it by type rather
        than guessing. These same values, in the order documented under
        x-agent-auth, are what the signature commits to.
      type: object
      required: [wallet]
      properties:
        wallet: { type: string, description: "Challenging address; must equal the x-agent-wallet header." }
        block: { type: integer, description: "Upgrade-window claims: a block inside the claimed window. Outside it, the reading says nothing about the assertion and resolves invalid_evidence." }
        implementation: { type: string, nullable: true, description: "Upgrade-window claims: the resolved implementation slot at that block; null asserts no slot was set." }
        bytecodeHash: { type: string, nullable: true, description: "Bytecode claims: the observed code hash, or null for no code." }
        bytecodeBytes: { type: integer, description: "Bytecode claims: the observed code length in bytes." }
    Quote:
      type: object
      required: [capabilityId, quoteId, cacheStatus, listPriceUsd, creditAppliedUsd, priceUsd, expiresAt, invokeUrl, invokeMethod]
      properties:
        capabilityId: { type: string }
        quoteId: { type: string }
        cacheStatus: { type: string, enum: [hit, partial, miss] }
        listPriceUsd: { type: number }
        creditAppliedUsd: { type: number }
        priceUsd: { type: number }
        expiresAt: { type: string, format: date-time }
        invokeUrl: { type: string }
        invokeMethod: { type: string, enum: [GET, POST] }
        invokeBody: { type: object, nullable: true, additionalProperties: true }
    CapabilityResult:
      type: object
      required: [capabilityId, invocationId, verdict, result, receiptUrl]
      properties:
        capabilityId: { type: string }
        invocationId: { type: string }
        verdict: { type: string, enum: [allow, review, block] }
        result:
          type: object
          properties:
            reasonCodes: { type: array, items: { type: string } }
            summary: { type: string }
            evidence: { type: object, additionalProperties: true }
            computedAtBlock: { type: integer }
            validUntilBlock: { type: integer }
            limitations: { type: array, items: { type: string } }
        pricing:
          type: object
          properties:
            quotedCacheStatus: { type: string, enum: [hit, partial, miss] }
            actualCacheStatus: { type: string, enum: [hit, partial, miss] }
            listPriceUsd: { type: number }
            creditAppliedUsd: { type: number }
            pricePaidUsd: { type: number }
        receiptUrl: { type: string }
        nextCall:
          description: >-
            The validity boundary of this answer, as structure rather than prose. `recheck` is the request
            that recomputes it, with `notBeforeBlock` set to the first block at which recomputing can differ.
            `watch` names the capability that reports what changed between two observations of the same
            subject, and is null when this capability already is that. Null for answers with no block window.
            Descriptive only: nothing here is required, and the answer is complete for the window it states.
          type: object
          nullable: true
          additionalProperties: true
          properties:
            validUntilBlock: { type: integer }
            validForSeconds: { type: integer, nullable: true }
            recheck:
              type: object
              additionalProperties: true
              properties:
                capabilityId: { type: string }
                method: { type: string, enum: [GET, POST] }
                url: { type: string }
                body: { type: object, nullable: true, additionalProperties: true }
                notBeforeBlock: { type: integer }
                listPriceUsdCeiling: { type: number }
                payment: { type: string, enum: [x402] }
            watch:
              type: object
              nullable: true
              additionalProperties: true
        evidenceReference:
          description: Portable provenance/reproduction pointer. Present on every capability response; private artifacts explicitly state shareable=false.
          type: object
          additionalProperties: true
