{
  "openapi": "3.1.0",
  "info": {
    "title": "Cascade coordination API",
    "version": "1.0.0",
    "summary": "Agents claim a named cell before acting; one gets a grant, the others a hold. Every decision is signed into a chain anyone can verify offline.",
    "description": "Hand-written for the public endpoints. Field lists match the live responses; responses may gain fields over time, so clients should ignore fields they don't know. Guide: https://cascadedynamics.ai/SKILL.md · Docs: https://cascadedynamics.ai/docs.html",
    "contact": { "email": "info@cascadedynamics.ai", "url": "https://cascadedynamics.ai" }
  },
  "servers": [{ "url": "https://api.cascadedynamics.ai/v1" }],
  "security": [{ "bearer": [] }],
  "externalDocs": { "url": "https://cascadedynamics.ai/docs.html" },
  "paths": {
    "/claim": {
      "post": {
        "operationId": "claim",
        "summary": "Ask for a cell (or several, all-or-nothing) before acting",
        "description": "A grant (ok: true) and a hold (hold: true) are both HTTP 200. A repeat claim by a held agent replays its hold for free (deduped: true). The same agent claiming a cell it holds renews the TTL. Each grant and each hold is one metered decision; a multi-claim that grants costs one per cell. An unverified key may make 1,000 decisions, then gets 403 email_verification_required until the email is verified.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimRequest" },
            "examples": {
              "single": { "value": { "cell": "tickets/4812", "agent": "worker-a", "ttl_ms": 30000 } },
              "multi": { "value": { "cells": ["dock/3", "crane/1"], "agent": "worker-a", "ttl_ms": 30000 } },
              "wait": { "value": { "cell": "tickets/4812", "agent": "worker-b", "ttl_ms": 30000, "wait_ms": 8000 } }
            } } }
        },
        "responses": {
          "200": { "description": "Granted or held. Branch on ok / hold.", "content": { "application/json": { "schema": { "oneOf": [
            { "$ref": "#/components/schemas/Grant" }, { "$ref": "#/components/schemas/MultiGrant" }, { "$ref": "#/components/schemas/Hold" } ] } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "description": "quota_exceeded: the plan allowance (plus any top-up credits) is used up. Nothing was granted or charged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "email_verification_required (unverified key past its 1,000-decision trial) or api_key_required (a console session cannot sign).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/Error" },
          "503": { "description": "chain_backpressure, chain_commit_failed or writer_handoff: nothing was granted; retry shortly.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/release": {
      "post": {
        "operationId": "release",
        "summary": "Hand a cell back, optionally signing an outcome into the record",
        "description": "Never metered. If you forget, the TTL releases the cell.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReleaseRequest" },
          "example": { "cell": "tickets/4812", "agent": "worker-a", "outcome": { "status": "done", "digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" } } } } },
        "responses": {
          "200": { "description": "Released", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Release" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "usage",
        "summary": "This key's meter: the count of signed decisions in its chain",
        "responses": {
          "200": { "description": "Usage", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Usage" } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/runs": {
      "get": {
        "operationId": "listRuns",
        "summary": "Run history: one row per key chain on this account, newest first",
        "responses": {
          "200": { "description": "Runs", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "ok": { "type": "boolean" },
            "runs": { "type": "array", "items": { "$ref": "#/components/schemas/RunRow" } },
            "note": { "type": "string" } } } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/runs/current": {
      "get": {
        "operationId": "currentRun",
        "summary": "This key's chain: metrics plus the last 30 entries",
        "responses": {
          "200": { "description": "Current run", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "run": { "type": "string", "description": "Run id = key id (8 hex)." },
            "scope": { "type": "string", "examples": ["TESTNET"] },
            "workspace": { "type": ["string", "null"] },
            "metrics": { "type": "object", "properties": {
              "grants": { "type": "integer" }, "holds": { "type": "integer" }, "duplicates": { "type": "integer" },
              "tokens_used": { "type": "integer" }, "head": { "type": "string" } } },
            "tail": { "type": "array", "items": { "$ref": "#/components/schemas/ChainEntry" } } } } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/runs/current/bundle": {
      "get": {
        "operationId": "currentBundle",
        "summary": "The signed bundle for this key's chain, to verify offline",
        "description": "Check it at https://cascadedynamics.ai/verify.html or with `npx cascade-verify bundle.json`.",
        "responses": {
          "200": { "description": "Bundle", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Bundle" } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/runs/{run_id}/bundle": {
      "get": {
        "operationId": "runBundle",
        "summary": "The signed bundle for a named run owned by this account",
        "parameters": [{ "name": "run_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[0-9a-f]{8}$" } }],
        "responses": {
          "200": { "description": "Bundle", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Bundle" } } } },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/billing/status": {
      "get": {
        "operationId": "billingStatus",
        "summary": "This account's plan, verification state and capabilities",
        "responses": {
          "200": { "description": "Status", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "plan": { "type": "string" }, "verified": { "type": "boolean" }, "scope": { "type": "string" },
            "stripe_customer": { "type": ["string", "null"] }, "subscription": { "type": ["string", "null"] },
            "billing_configured": { "type": "boolean" },
            "capabilities": { "type": "object", "additionalProperties": true },
            "capabilities_note": { "type": "string" } } } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/billing/plans": {
      "get": {
        "operationId": "plans",
        "summary": "The public plan catalog and top-up terms",
        "security": [],
        "responses": {
          "200": { "description": "Plans", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "ok": { "type": "boolean" },
            "plans": { "type": "array", "items": { "$ref": "#/components/schemas/Plan" } },
            "default_signup_plan": { "type": "string" },
            "topup": { "type": "object", "properties": {
              "usd_per_pack": { "type": "number" }, "decisions_per_pack": { "type": "integer" }, "max_packs": { "type": "integer" },
              "available": { "type": "boolean" }, "note": { "type": "string" } } },
            "note": { "type": "string" } } } } } }
        }
      }
    },
    "/public/stats": {
      "get": {
        "operationId": "publicStats",
        "summary": "Node-wide aggregates across every chain (no account data)",
        "security": [],
        "responses": {
          "200": { "description": "Stats", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "ok": { "type": "boolean" }, "entries": { "type": "integer" }, "grants": { "type": "integer" }, "holds": { "type": "integer" },
            "releases": { "type": "integer" }, "chains": { "type": "integer" }, "duplicate_grants": { "type": "integer" },
            "head_fingerprint": { "type": ["string", "null"] }, "last_entry_at": { "type": ["integer", "null"] },
            "since": { "type": ["integer", "null"] }, "note": { "type": "string" } } } } } }
        }
      }
    },
    "/signup": {
      "post": {
        "operationId": "signup",
        "summary": "Create a workspace and get a sandbox key (shown once)",
        "description": "No bearer. Rate-limited per IP. A repeat signup for an email that already has a password issues no key (existing: true) and, if the email is still unverified, re-sends the verification link.",
        "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email"], "properties": {
          "email": { "type": "string", "format": "email" },
          "password": { "type": "string", "minLength": 10 } } } } } },
        "responses": {
          "200": { "description": "Key issued (or existing: true)", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "ok": { "type": "boolean" }, "account_id": { "type": "string" }, "key": { "type": "string", "description": "The secret. Returned once, never stored in plaintext." },
            "key_id": { "type": "string" }, "scope": { "type": "string" }, "new": { "type": "boolean" },
            "verified": { "type": "boolean" }, "quota": { "type": "integer" }, "verify_required": { "type": "boolean" },
            "trial_decisions": { "type": "integer", "description": "Decisions an unverified key may make (1,000)." },
            "password_set": { "type": "boolean" }, "existing": { "type": "boolean" }, "note": { "type": "string" } } } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/verify-email": {
      "post": {
        "operationId": "verifyEmail",
        "summary": "Redeem the emailed verification token",
        "description": "Single use; expires 24 hours after it was sent. Lifts the 1,000-decision trial cap on every key of the account; usage so far is kept.",
        "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["token"], "properties": { "token": { "type": "string" } } } } } },
        "responses": {
          "200": { "description": "Verified", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "ok": { "type": "boolean" }, "account_id": { "type": "string" }, "verified": { "type": "boolean" }, "quota": { "type": "integer" } } } } } },
          "400": { "description": "Invalid, already used, or expired (code verify_token_expired).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/verify-email/resend": {
      "post": {
        "operationId": "resendVerifyEmail",
        "summary": "Email a fresh verification link",
        "description": "Always 200, whether or not the email has an account. The new link replaces any earlier one. Rate-limited per IP.",
        "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email"], "properties": { "email": { "type": "string", "format": "email" } } } } } },
        "responses": {
          "200": { "description": "Accepted", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "note": { "type": "string" } } } } } },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": { "type": "http", "scheme": "bearer", "description": "An API key (csc_sandbox_… or csc_live_…). Keep it server-side." }
    },
    "responses": {
      "Error": { "description": "Refusal. Branch on `code` when present, not on `error` text.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "ok": { "const": false },
          "error": { "type": "string" },
          "code": { "type": "string", "examples": ["quota_exceeded", "email_verification_required", "bad_outcome", "chain_backpressure"] },
          "note": { "type": "string" }
        }
      },
      "ClaimRequest": {
        "type": "object",
        "required": ["agent"],
        "properties": {
          "cell": { "type": "string", "description": "Any name you choose, e.g. tickets/4812. Use structural ids, never personal data." },
          "cells": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 16, "description": "Instead of cell: claim all of them or none." },
          "agent": { "type": "string", "description": "A stable id for whoever is asking." },
          "ttl_ms": { "type": "integer", "minimum": 1, "description": "How long the grant lasts if never released. Default 2000." },
          "wait_ms": { "type": "integer", "minimum": 0, "maximum": 10000, "description": "Keep the request open until the cell frees or the wait ends. Waiting is free." }
        },
        "oneOf": [{ "required": ["cell"] }, { "required": ["cells"] }]
      },
      "Grant": {
        "type": "object",
        "properties": {
          "ok": { "const": true },
          "cell": { "type": "string" },
          "entry": { "type": "integer" },
          "fence": { "type": "integer", "description": "Only grows per cell. Send it with writes; reject lower fences." },
          "entry_hash": { "type": "string", "description": "The receipt: hash of the signed chain entry." },
          "signed": { "type": "boolean" },
          "tokens_used": { "const": 0 },
          "waited_ms": { "type": "integer" }
        }
      },
      "MultiGrant": {
        "type": "object",
        "properties": {
          "ok": { "const": true },
          "cells": { "type": "array", "items": { "type": "string" } },
          "grants": { "type": "array", "items": { "type": "object", "properties": {
            "cell": { "type": "string" }, "entry": { "type": "integer" }, "fence": { "type": "integer" },
            "entry_hash": { "type": "string" }, "signed": { "type": "boolean" } } } },
          "entry_hash": { "type": "string" },
          "signed": { "type": "boolean" },
          "tokens_used": { "const": 0 }
        }
      },
      "Hold": {
        "type": "object",
        "properties": {
          "ok": { "const": false },
          "hold": { "const": true },
          "cell": { "type": "string", "description": "The cell you are held on (for a multi-claim, the first one taken)." },
          "cells": { "type": "array", "items": { "type": "string" } },
          "behind": { "type": "string", "description": "The agent that holds the cell." },
          "entry": { "type": "null" },
          "entry_hash": { "type": "string" },
          "signed": { "type": "boolean" },
          "deduped": { "type": "boolean", "description": "A replay of your recorded hold: no new entry, no charge." },
          "tokens_used": { "const": 0 },
          "waited_ms": { "type": "integer" }
        }
      },
      "ReleaseRequest": {
        "type": "object",
        "required": ["cell", "agent"],
        "properties": {
          "cell": { "type": "string" },
          "agent": { "type": "string" },
          "outcome": { "type": "object", "required": ["status"], "additionalProperties": false, "properties": {
            "status": { "type": "string", "pattern": "^[A-Za-z0-9_.:-]{1,32}$" },
            "digest": { "type": "string", "pattern": "^[0-9a-fA-F]{64}$", "description": "SHA-256 hex you compute over your own result." } } }
        }
      },
      "Release": {
        "type": "object",
        "properties": {
          "released": { "type": "boolean" },
          "entry_hash": { "type": "string" },
          "signed": { "type": "boolean" },
          "outcome": { "type": "object", "properties": { "status": { "type": "string" }, "digest": { "type": "string" } } }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "used": { "type": "integer" }, "quota": { "type": "integer" }, "scope": { "type": "string" },
          "remaining": { "type": "integer" }, "credits": { "type": "integer" }, "credits_used": { "type": "integer" },
          "used_lifetime": { "type": "integer" }, "tokens_saved_estimate": { "type": "integer", "description": "Estimate: used_lifetime × 600." },
          "verified": { "type": "boolean" }, "verify_required": { "type": "boolean" },
          "trial": { "type": "object", "description": "Present only while the key is unverified.", "properties": {
            "decisions": { "type": "integer" }, "used": { "type": "integer" }, "remaining": { "type": "integer" } } },
          "over": { "type": "boolean" }, "pct": { "type": "integer" }, "nearing": { "type": "boolean" },
          "period": { "type": "string", "enum": ["lifetime", "monthly"] }, "resets_at": { "type": ["integer", "null"] },
          "upgrade_url": { "type": "string" }, "note": { "type": "string" }
        }
      },
      "RunRow": {
        "type": "object",
        "properties": {
          "run_id": { "type": "string" }, "key_id": { "type": "string" },
          "started_at": { "type": ["integer", "null"] }, "last_entry_at": { "type": "integer" },
          "entries": { "type": "integer" }, "grants": { "type": "integer" }, "holds": { "type": "integer" },
          "releases": { "type": "integer" }, "head_hash": { "type": "string" }
        }
      },
      "ChainEntry": {
        "type": "object",
        "description": "One signed decision. Verifiers must ignore verbs they don't know.",
        "properties": {
          "verb": { "type": "string", "examples": ["ASSIGN", "HOLD", "RELEASE"] },
          "cell": { "type": "string" }, "agent": { "type": "string" },
          "entry": { "type": "integer" }, "fence": { "type": "integer" }, "ttl_ms": { "type": "integer" },
          "expires_at": { "type": "integer" }, "behind": { "type": "string" },
          "outcome": { "type": "object" }, "detail": { "type": "string" },
          "prev": { "type": "string" }, "hash": { "type": "string" },
          "sig": { "type": "string", "description": "Ed25519 signature, base64." }
        },
        "additionalProperties": true
      },
      "Bundle": {
        "type": "object",
        "properties": {
          "run": { "type": "string" },
          "chain": { "type": "array", "items": { "$ref": "#/components/schemas/ChainEntry" } },
          "public_key": { "type": "string", "description": "PEM (Ed25519): the root key that certifies the session keys in `certs` (a convenience; verifiers pin their own copy)." },
              "certs": { "type": "array", "description": "Root-signed certificates of the session keys whose kid the entries and seal carry (cascade-verify SPEC.md section 7).", "items": { "type": "object" } },
              "anchors": { "type": "array", "description": "This run's anchors in the public anchor log, with Merkle inclusion proofs (SPEC.md section 8).", "items": { "type": "object" } }
        },
        "additionalProperties": true
      },
      "Plan": {
        "type": "object",
        "properties": {
          "id": { "type": "string" }, "name": { "type": "string" }, "price_usd_month": { "type": "number" },
          "included_decisions": { "type": ["integer", "null"] }, "period": { "type": "string" },
          "enabled": { "type": "boolean" }, "coming_soon": { "type": "boolean" }, "contact": { "type": "boolean" },
          "overage": {}, "capabilities": { "type": "object", "additionalProperties": true }, "description": { "type": "string" }
        }
      }
    }
  }
}
