openapi: 3.1.0
info:
  title: ComputerWorld Cloud API
  version: 0.1.0
  summary: Hosted, deterministic ComputerWorld worlds behind one REST door.
  description: |
    The hosted ComputerWorld API. One Cloudflare Worker (`cloud/`) answers `/v1/*`; a
    Durable Object per world instance holds that world's engine (the Wasm build) in memory
    and its last snapshot in storage. The same Worker serves the web console
    (`cloud/console/`) as static assets at `/`, so the console calls this API same-origin
    with its session cookie.

    **Owners and actors.** An account (signed in with a session cookie, or holding an API key)
    owns the worlds it creates and may do anything to them: inspect, snapshot, fork, patch a
    service's state, stage and verify procedures. An *environment* is an actor's grant on one
    world (an actor user, machines, action families, observation channels). Creating one returns
    an *actor token* (`cwa_...`) that can only step, observe, read the scene and render through
    that one environment: never inspect, snapshot or see another environment. This is the
    engine's own owner/actor split (`World` vs `Environment`), kept over HTTP.

    **Determinism.** A world is a pure function of (engine build, definition, seed, the actions
    sent). Every mutating answer carries `state_hash` and `tick`; replaying the same requests
    against a world booted from the same definition and seed reaches the same hashes. The
    `engine` object in `/v1/health` names the build so a hash is only compared within one build.

    **Errors.** Every error is `{ "error": { "code": string, "message": string } }` with an HTTP
    status: 400 `bad_request`, 401 `unauthenticated`, 403 `forbidden`, 404 `not_found`,
    409 `conflict` (e.g. an action the engine refused), 413 `too_large`, 422 `invalid_world`
    (the engine rejected a definition), 429 `rate_limited` / `quota_exceeded` (with
    `Retry-After`), 500 `engine_error`.

    **Numbers.** 64-bit integers from the engine (ticks, pids) are JSON numbers when they fit
    in 2^53 and decimal strings otherwise. Hashes are lowercase hex strings.
servers:
  - url: http://127.0.0.1:8787
    description: Local (`npm run dev` in cloud/)
  - url: https://computaco.ai
    description: Hosted
  - url: https://dev.computaco.ai
    description: Hosted (the Pages project's own name, an alias of computaco.ai)
security:
  - apiKey: []
  - session: []
tags:
  - name: health
  - name: auth
    description: Accounts, sessions and API keys.
  - name: billing
  - name: orgs
    description: Organizations (docs/cloud-api.md § Organizations) — who owns resources and who pays. A session acts in its personal organization unless `x-cw-org` names another; an API key acts in its own.
    description: Plans, upgrades through Stripe, and referral codes.
  - name: catalog
    description: Built-in worlds, procedures, societies and the vocabulary a console offers.
  - name: blueprints
    description: Account-owned world definitions, edited as a topology before a world is booted.
  - name: worlds
    description: Running world instances (owner calls).
  - name: topology
    description: What machines a running world has and how they are connected.
  - name: snapshots
  - name: environments
    description: Actor grants and the actor-facing calls.
  - name: procedures
  - name: swarms
    description: Societies (https://computaco.ai/docs/societies.html) run on the world clock, with matched-fork arms.
  - name: agent
    description: >
      Computatrum, the desktop computer-use agent: its metered chat and vision calls, the
      device sign-in that gives the app an agent token, its usage windows and pay as you go
      (https://computaco.ai/docs/cloud-api.html § Computatrum).

paths:
  /v1/health:
    get:
      tags: [health]
      security: []
      summary: Liveness and the engine build.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  engine: { $ref: "#/components/schemas/EngineInfo" }

  # ---------------------------------------------------------------- auth
  /v1/auth/signup:
    post:
      tags: [auth]
      security: []
      summary: Create an account (username + password). Sets the session cookie.
      description: |
        Usernames are 3–32 characters of `a-z 0-9 _ -`, case-insensitive. Passwords are 10–200
        characters. The password is stored as PBKDF2-SHA-256 (WebCrypto, 100,000 iterations — the most workerd allows —,
        16-byte random salt). The answer carries the account's first API key in full: it is
        shown once and stored only as its SHA-256. Rate limited per IP.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/Credentials"
                - type: object
                  properties:
                    referral_code:
                      type: string
                      example: FIRST100
                      description: >
                        Optional. A code that exists, is active, has not expired and has places
                        left; it is kept on the account and applied when it upgrades (it is
                        reserved then, not now). Any other code is a 400 `invalid_code`.
      responses:
        "201":
          description: "Created. Sets `cw_session` (HttpOnly; Secure; SameSite=Lax; Path=/)."
          content:
            application/json:
              schema:
                type: object
                properties:
                  user: { $ref: "#/components/schemas/User" }
                  api_key: { $ref: "#/components/schemas/ApiKeyCreated" }
        "400": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /v1/auth/signin:
    post:
      tags: [auth]
      security: []
      summary: Sign in. Sets the session cookie.
      description: Five failures for one username in 15 minutes lock it for the rest of the window (429).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Credentials" }
      responses:
        "200":
          description: Signed in.
          content:
            application/json:
              schema:
                type: object
                properties:
                  user: { $ref: "#/components/schemas/User" }
        "401": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /v1/auth/signout:
    post:
      tags: [auth]
      security: [{ session: [] }]
      summary: End the session and clear the cookie.
      responses:
        "204": { description: Signed out. }
  /v1/me:
    get:
      tags: [auth]
      summary: The signed-in account, its quotas and its current usage.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  user: { $ref: "#/components/schemas/User" }
                  quotas: { $ref: "#/components/schemas/Quotas" }
                  usage: { $ref: "#/components/schemas/Usage" }
                  billing: { $ref: "#/components/schemas/Billing" }
    patch:
      tags: [auth]
      summary: Set or clear the account's name, email and date of birth.
      description: >
        Fields left out are kept; null or "" clears one. A name is at most 100 characters
        with no control characters; an email has an @ and a dot in its domain (at most 254
        characters); a date of birth is a real date written yyyy-mm-dd, in the past and not
        before 1900. Any other field is refused with 400, as is an empty object.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProfilePatch" }
      responses:
        "200":
          description: The account as it is now.
          content:
            application/json:
              schema:
                type: object
                properties:
                  user: { $ref: "#/components/schemas/User" }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
    delete:
      tags: [auth]
      summary: Delete the account and everything it owns.
      description: >
        Deletes the account's swarms (with their arms' worlds), its worlds (with their
        snapshots, environments and actor tokens), its blueprints, its API keys, its
        sessions, its usage counters and the account itself, at once and for good; the
        username can be taken again. From a session the body must carry the current
        password. With an API key it must carry `confirm`, the account's username, or
        the password. A missing or wrong `confirm` is 400, a wrong password 401. The
        answer clears the session cookie.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                password: { type: string, description: The current password (required from a session). }
                confirm: { type: string, description: "The account's username (with an API key, instead of the password).", example: ada }
      responses:
        "204": { description: Deleted. }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /v1/me/usage:
    get:
      tags: [auth]
      summary: Daily usage, newest first.
      parameters:
        - { name: days, in: query, schema: { type: integer, default: 30, maximum: 90 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  days:
                    type: array
                    items:
                      type: object
                      properties:
                        day: { type: string, example: "2026-10-28" }
                        requests: { type: integer }
                        steps: { type: integer }
                        renders: { type: integer }
                        worlds_created: { type: integer }
                        model_calls: { type: integer }
  /v1/me/password:
    post:
      tags: [auth]
      security: [{ session: [] }]
      summary: Change the password (signs out every other session).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [current, next]
              properties:
                current: { type: string }
                next: { type: string }
      responses:
        "204": { description: Changed. }
        "401": { $ref: "#/components/responses/Error" }
  /v1/keys:
    get:
      tags: [auth]
      summary: List the account's live API keys (never the secret).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys: { type: array, items: { $ref: "#/components/schemas/ApiKey" } }
    post:
      tags: [auth]
      summary: Create an API key. The `key` field is shown once.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 60 }
      responses:
        "201":
          description: Created.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ApiKeyCreated" }
        "429": { $ref: "#/components/responses/Error" }
  /v1/keys/{key_id}:
    delete:
      tags: [auth]
      summary: Revoke an API key. Takes effect on the next request.
      parameters: [{ $ref: "#/components/parameters/KeyId" }]
      responses:
        "204": { description: Revoked. }
        "404": { $ref: "#/components/responses/Error" }
  /v1/me/invites:
    get:
      tags: [orgs]
      summary: Open invites addressed to the account's email, each with the code to accept it.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  invites: { type: array, items: { type: object, properties: { code: { type: string }, org: { $ref: "#/components/schemas/OrgRef" }, role: { $ref: "#/components/schemas/Role" }, expires_at: { type: integer } } } }

  # ---------------------------------------------------------------- organizations
  /v1/orgs:
    get:
      tags: [orgs]
      summary: Every organization the account is in (personal first), and the one this credential acts in.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  orgs: { type: array, items: { $ref: "#/components/schemas/Org" } }
                  current: { type: string }
    post:
      tags: [orgs]
      summary: Make a team organization; the account is its owner. Its personal plan says how many it may create.
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [name], properties: { name: { type: string, minLength: 2, maxLength: 60 } } }
      responses:
        "201": { description: Created., content: { application/json: { schema: { type: object, properties: { org: { $ref: "#/components/schemas/Org" } } } } } }
        "429": { $ref: "#/components/responses/Error" }
  /v1/orgs/join:
    post:
      tags: [orgs]
      summary: Join the organization an invite code names (201; 200 when already a member).
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [code], properties: { code: { type: string, example: cwi_3f2a9c11d4e5b6a7 } } }
      responses:
        "201": { description: Joined., content: { application/json: { schema: { type: object, properties: { org: { $ref: "#/components/schemas/Org" }, already_member: { type: boolean } } } } } }
        "400": { $ref: "#/components/responses/Error" }
        "403": { description: The invite is for another email., content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /v1/orgs/invites/{code}:
    get:
      tags: [orgs]
      summary: What an invite code opens, before it is accepted.
      parameters: [{ name: code, in: path, required: true, schema: { type: string } }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { org: { $ref: "#/components/schemas/OrgRef" }, role: { $ref: "#/components/schemas/Role" }, email: { type: [string, "null"] }, expires_at: { type: integer } } } } } }
        "404": { $ref: "#/components/responses/Error" }
  /v1/orgs/{org_id}:
    parameters: [{ $ref: "#/components/parameters/OrgId" }]
    get:
      tags: [orgs]
      summary: The organization, your role, its quotas, billing, usage, bought applications and this month's professional steps (member).
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { org: { $ref: "#/components/schemas/Org" }, role: { $ref: "#/components/schemas/Role" }, quotas: { $ref: "#/components/schemas/Quotas" }, billing: { type: object }, usage: { type: object }, applications: { type: array, items: { $ref: "#/components/schemas/Entitlement" } }, professional: { $ref: "#/components/schemas/ProfessionalUsage" } } } } } }
        "404": { $ref: "#/components/responses/Error" }
    patch:
      tags: [orgs]
      summary: Rename a team organization (owner).
      requestBody: { required: true, content: { application/json: { schema: { type: object, required: [name], properties: { name: { type: string } } } } } }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { org: { $ref: "#/components/schemas/Org" } } } } } }
        "403": { $ref: "#/components/responses/Error" }
    delete:
      tags: [orgs]
      summary: Delete a team organization and everything it owns (owner). The body names it to confirm.
      requestBody: { required: true, content: { application/json: { schema: { type: object, required: [confirm], properties: { confirm: { type: string, description: The organization's name. } } } } } }
      responses:
        "204": { description: Deleted. }
        "400": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /v1/orgs/{org_id}/members:
    parameters: [{ $ref: "#/components/parameters/OrgId" }]
    get:
      tags: [orgs]
      summary: The members and their roles (member).
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { members: { type: array, items: { $ref: "#/components/schemas/Member" } } } } } } }
  /v1/orgs/{org_id}/members/{user_id}:
    parameters: [{ $ref: "#/components/parameters/OrgId" }, { name: user_id, in: path, required: true, schema: { type: string } }]
    patch:
      tags: [orgs]
      summary: Change a member's role (owner). The last owner keeps the role (409).
      requestBody: { required: true, content: { application/json: { schema: { type: object, required: [role], properties: { role: { $ref: "#/components/schemas/Role" } } } } } }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { members: { type: array, items: { $ref: "#/components/schemas/Member" } } } } } } }
        "409": { $ref: "#/components/responses/Error" }
    delete:
      tags: [orgs]
      summary: Remove a member (admin), or leave (anyone but the last owner).
      responses:
        "204": { description: Removed. }
        "409": { $ref: "#/components/responses/Error" }
  /v1/orgs/{org_id}/invites:
    parameters: [{ $ref: "#/components/parameters/OrgId" }]
    get:
      tags: [orgs]
      summary: The open invites, each with its link (admin).
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { invites: { type: array, items: { $ref: "#/components/schemas/Invite" } } } } } } }
    post:
      tags: [orgs]
      summary: Make an invite (admin). With `email`, only an account with that email accepts it, once.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                role: { type: string, enum: [member, admin], default: member }
                email: { type: string, format: email }
                max_uses: { type: integer, minimum: 1, maximum: 1000 }
                expires_in_days: { type: integer, minimum: 1, maximum: 90, default: 14 }
      responses:
        "201": { description: Created., content: { application/json: { schema: { $ref: "#/components/schemas/Invite" } } } }
        "429": { $ref: "#/components/responses/Error" }
  /v1/orgs/{org_id}/invites/{invite_id}:
    parameters: [{ $ref: "#/components/parameters/OrgId" }, { name: invite_id, in: path, required: true, schema: { type: string } }]
    delete:
      tags: [orgs]
      summary: Revoke an invite (admin).
      responses:
        "204": { description: Revoked. }
        "404": { $ref: "#/components/responses/Error" }
  /v1/orgs/{org_id}/usage:
    parameters: [{ $ref: "#/components/parameters/OrgId" }]
    get:
      tags: [orgs]
      summary: The organization's daily usage, and this month's professional steps.
      parameters: [{ name: days, in: query, schema: { type: integer, minimum: 1, maximum: 90, default: 30 } }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { days: { type: array, items: { type: object } }, professional: { $ref: "#/components/schemas/ProfessionalUsage" } } } } } }
  /v1/orgs/{org_id}/applications:
    parameters: [{ $ref: "#/components/parameters/OrgId" }]
    get:
      tags: [orgs]
      summary: The professional applications — each with its one-time price and whether this organization has bought it — and whether its plan may buy.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  can_buy: { type: boolean }
                  plan_required: { type: [string, "null"], example: pro }
                  billing_configured: { type: boolean }
                  applications: { type: array, items: { $ref: "#/components/schemas/Application" } }
                  usage: { $ref: "#/components/schemas/ProfessionalUsage" }
  /v1/orgs/{org_id}/applications/{product}/checkout:
    parameters: [{ $ref: "#/components/parameters/OrgId" }, { name: product, in: path, required: true, schema: { type: string, enum: [kicad, freecad, video] } }]
    post:
      tags: [orgs, billing]
      summary: Buy an application once for the whole organization (admin, paid plan) — a Stripe Checkout URL in payment mode at the catalog's price. The webhook grants it when paid.
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { url: { type: string }, id: { type: string }, product: { type: string }, amount_cents: { type: integer } } } } } }
        "402": { description: plan_required — the organization is on Free., content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "409": { description: Already bought., content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "503": { $ref: "#/components/responses/Error" }
  /v1/orgs/{org_id}/meter:
    parameters: [{ $ref: "#/components/parameters/OrgId" }]
    get:
      tags: [orgs, billing]
      summary: This month's professional steps and the metered-steps settings.
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { professional: { $ref: "#/components/schemas/ProfessionalUsage" }, configured: { type: boolean } } } } } }
    put:
      tags: [orgs, billing]
      summary: Turn metered professional steps on or off and set the monthly cap (admin). The first time on answers `checkout_url` for the payment method.
      requestBody: { content: { application/json: { schema: { type: object, properties: { enabled: { type: boolean }, monthly_cap_usd: { type: number, minimum: 1, maximum: 100000 } } } } } }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { metered: { type: object }, checkout_url: { type: [string, "null"] } } } } } }
        "503": { $ref: "#/components/responses/Error" }

  # ---------------------------------------------------------------- catalog
  # ---------------------------------------------------------------- billing
  /v1/plans:
    get:
      tags: [billing]
      security: []
      summary: The plans, their prices and quotas, and whether this deployment takes payment.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  billing_configured: { type: boolean, description: "false: upgrades are not open yet (checkout answers 503 billing_not_configured)." }
                  plans: { type: array, items: { $ref: "#/components/schemas/Plan" } }
  /v1/billing/checkout:
    post:
      tags: [billing]
      summary: Start an upgrade — a Stripe Checkout Session for a monthly subscription.
      description: >
        Answers the Checkout URL to send the person to. The account stays on its plan until
        Stripe reports the payment (the webhook's checkout.session.completed). A referral
        code (this body's, or the one kept from sign-up) is checked and one of its places
        reserved for the session's hour; the discount is a Stripe coupon made from it. An
        account already on the plan, or with a subscription, gets 409 (use the portal).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                plan: { type: string, enum: [pro], default: pro }
                referral_code: { type: string, example: FIRST100 }
      responses:
        "200":
          description: The Checkout Session.
          content:
            application/json:
              schema:
                type: object
                properties:
                  url: { type: string, format: uri, example: "https://checkout.stripe.com/c/pay/cs_test_..." }
                  id: { type: string, example: cs_test_a1b2 }
                  discount: { oneOf: [{ $ref: "#/components/schemas/Discount" }, { type: "null" }] }
        "400": { description: "`bad_request` (plan) or `invalid_code` (unknown, inactive, expired, for another plan, used up, or redeemed by this account already).", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "409": { $ref: "#/components/responses/Error" }
        "502": { description: "`billing_error`: Stripe refused the request.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "503": { description: "`billing_not_configured`: this deployment has no Stripe key; upgrades open soon.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /v1/billing/portal:
    post:
      tags: [billing]
      summary: A Stripe Customer Portal session, to change the card, see invoices or cancel.
      responses:
        "200":
          description: The portal session.
          content:
            application/json:
              schema:
                type: object
                properties:
                  url: { type: string, format: uri }
        "409": { description: The account has not been through checkout. }
        "503": { $ref: "#/components/responses/Error" }
  /v1/billing/webhook:
    post:
      tags: [billing]
      security: []
      summary: Stripe's events (configured in Stripe's dashboard, not called by clients).
      description: >
        Verified by `Stripe-Signature` (HMAC-SHA256 of `t.payload` under STRIPE_WEBHOOK_SECRET,
        timestamp within 300 s); an event id seen before is acknowledged and ignored.
        checkout.session.completed makes the account Pro and redeems its code;
        customer.subscription.updated sets status, renewal and cancel-at-period-end;
        customer.subscription.deleted returns it to Free; invoice.payment_failed marks it
        past due (Pro is kept 7 days, then Free until a payment succeeds);
        checkout.session.expired gives a reserved code's place back.
      parameters:
        - { name: Stripe-Signature, in: header, required: true, schema: { type: string, example: "t=1800000000,v1=5257a8..." } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, description: A Stripe Event. }
      responses:
        "200":
          description: Received.
          content:
            application/json:
              schema:
                type: object
                properties:
                  received: { type: boolean }
                  handled: { type: boolean }
                  duplicate: { type: boolean }
        "400": { description: "`bad_signature`." }
        "503": { description: "`billing_not_configured`." }

  /v1/models:
    get:
      tags: [agent]
      security: [{ apiKey: [] }, { session: [] }]
      summary: The agent models, as OpenAI's `GET /v1/models` lists models.
      description: >
        The chat model (`deepseek-flash`: text and tools) and the vision model (`glm-5.3-flash`:
        text and images), with `served_by` (the provider's own id) and `pricing`: what pay as you
        go charges a million tokens (2.5 times the model's list price). A plan counts
        agent-minutes instead (§ Computatrum).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, example: list }
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, example: deepseek-flash }
                        object: { type: string, example: model }
                        owned_by: { type: string, example: computaco }
                        served_by: { type: string, example: deepseek-flash }
                        input_modalities: { type: array, items: { type: string, enum: [text, image] } }
                        output_modalities: { type: array, items: { type: string } }
                        tools: { type: boolean }
                        pricing: { type: object, properties: { unit: { type: string }, plan: { type: string }, input: { type: number }, cached_input: { type: number }, output: { type: number } } }
  /v1/chat/completions:
    post:
      tags: [agent]
      security: [{ apiKey: [] }, { session: [] }]
      summary: OpenAI chat completions on either agent model, metered as /v1/agent/chat and /v1/agent/vision are.
      description: >
        An OpenAI client whose base URL is https://computaco.ai calls this unchanged. `model`
        chooses the model (`deepseek-flash` when left out; the providers' own ids are taken too).
        The chat model takes the body /v1/agent/chat takes. The vision model reads `image_url`
        parts given as `data:` URLs of PNG, JPEG or WebP (at most 8, each at most 4 MiB) and
        fetches no URL; `tools`, `tool_choice` and `response_format` are passed on to it. The chat
        model takes `response_format: {type: json_schema}` as DeepSeek can: `json_object`, with the
        schema in a system message first (asked of the model, not enforced). `stream: true` answers
        `text/event-stream`: one `chat.completion.chunk` holding the whole answer as its delta,
        one with `usage` and `metering`, then `[DONE]` (the models are called whole). An unknown
        model is a 404.
      parameters:
        - { $ref: "#/components/parameters/ComputatrumTask" }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [messages]
              properties:
                model: { type: string, enum: [deepseek-flash, glm-5.3-flash] }
                messages: { type: array, minItems: 1, maxItems: 500, items: { type: object } }
                max_tokens: { type: integer }
                max_completion_tokens: { type: integer }
                temperature: { type: number }
                tools: { type: array, items: { type: object } }
                tool_choice: {}
      responses:
        "200":
          description: The completion, in OpenAI's shape, and what it was charged (`metering`).
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  object: { type: string, example: chat.completion }
                  model: { type: string }
                  choices: { type: array, items: { type: object } }
                  usage: { type: object, properties: { prompt_tokens: { type: integer }, completion_tokens: { type: integer }, total_tokens: { type: integer }, cached_tokens: { type: integer }, images: { type: integer } } }
                  metering: { $ref: "#/components/schemas/Metering" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /v1/agent/chat:
    post:
      tags: [agent]
      security: [{ apiKey: [] }, { session: [] }, { agentToken: [] }]
      summary: OpenAI-style chat completions for Computatrum's next action, metered.
      description: >
        Served by DeepSeek `deepseek-flash` (AGENT_CHAT_MODEL). The body's `model` is ignored;
        `stream: true` is refused. Admitted against the account's 5-hour and weekly windows of
        agent-minutes, or pay as you go when they are used up (or always, in PAYG's `always`
        mode). The windows are charged the call's time plus the gap since its task's previous
        call, up to 15 s (send the task in `X-Computatrum-Task`); pay as you go is charged the
        credits the model reports it used.
      parameters:
        - { $ref: "#/components/parameters/ComputatrumTask" }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [messages]
              properties:
                messages: { type: array, minItems: 1, maxItems: 500, items: { type: object, required: [role], properties: { role: { type: string, enum: [system, user, assistant, tool] }, content: { oneOf: [{ type: string }, { type: array, items: { type: object } }, { type: "null" }] } } } }
                max_tokens: { type: integer, minimum: 1, default: 1024, description: More than 8192 is lowered to 8192. }
                temperature: { type: number }
                top_p: { type: number }
                stop: { oneOf: [{ type: string }, { type: array, items: { type: string } }] }
                response_format: { type: object, example: { type: json_object } }
                tools: { type: array, items: { type: object } }
                tool_choice: {}
                reasoning_effort: { type: string, example: low }
                task_id: { type: string, description: The task, if not in X-Computatrum-Task. }
      responses:
        "200":
          description: The completion, in OpenAI's shape, and what it was charged.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  object: { type: string, example: chat.completion }
                  created: { type: integer }
                  model: { type: string, example: deepseek-flash }
                  choices: { type: array, items: { type: object, properties: { index: { type: integer }, message: { type: object, properties: { role: { type: string }, content: { type: string }, reasoning_content: { type: string } } }, finish_reason: { type: string } } } }
                  usage: { type: object, properties: { prompt_tokens: { type: integer }, completion_tokens: { type: integer }, total_tokens: { type: integer }, cached_tokens: { type: integer } } }
                  metering: { $ref: "#/components/schemas/Metering" }
        "400": { description: "`bad_request`, also when the model refuses the request (nothing is charged).", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "429": { description: "`quota_exceeded`: a window is used up (Retry-After: seconds to its reset) or pay as you go reached its monthly cap; `rate_limited`: requests a minute.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "503": { description: "`model_unavailable` (Retry-After) or `agent_not_configured`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /v1/agent/vision:
    post:
      tags: [agent]
      security: [{ apiKey: [] }, { session: [] }, { agentToken: [] }]
      summary: Ask about a screenshot (what is on it, where a control is), metered.
      description: >
        Served by Workers AI `@cf/zai-org/glm-5.3-flash` (AGENT_VISION_MODEL), low reasoning
        effort, temperature 0.1. Coordinates asked for come back in the image's pixels. Admitted
        and charged as chat is.
      parameters:
        - { $ref: "#/components/parameters/ComputatrumTask" }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [image, prompt]
              properties:
                image: { type: string, description: "Base64 PNG, JPEG or WebP (or a data: URL), at most 4 MiB decoded." }
                prompt: { type: string, maxLength: 20000, example: "Give the pixel coordinates (x, y) of the center of the Send button." }
                max_tokens: { type: integer, minimum: 1, default: 512, description: More than 2048 is lowered to 2048. }
                task_id: { type: string, description: The task, if not in X-Computatrum-Task. }
      responses:
        "200":
          description: The answer.
          content:
            application/json:
              schema:
                type: object
                properties:
                  text: { type: string, example: "Send button center: approximately (102, 575)." }
                  model: { type: string, example: "@cf/zai-org/glm-5.3-flash" }
                  usage: { type: object, properties: { prompt_tokens: { type: integer }, completion_tokens: { type: integer }, total_tokens: { type: integer }, images: { type: integer } } }
                  model_ms: { type: integer }
                  metering: { $ref: "#/components/schemas/Metering" }
        "400": { $ref: "#/components/responses/Error" }
        "413": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /v1/agent/usage:
    get:
      tags: [agent]
      security: [{ apiKey: [] }, { session: [] }, { agentToken: [] }]
      summary: The 5-hour and weekly windows, pay as you go this month, and the last 30 days by model.
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/AgentUsage" } } } }
  /v1/agent/device:
    post:
      tags: [agent]
      security: []
      summary: Start a device sign-in for the Computatrum app.
      description: Counts against the address's sign-up and sign-in attempts (ten a minute).
      requestBody:
        content:
          application/json:
            schema: { type: object, properties: { client_name: { type: string, maxLength: 60, example: Computatrum on ada-mbp } } }
      responses:
        "200":
          description: The codes. The app shows `user_code`, opens `verification_url`, and polls /v1/agent/device/token every `interval` seconds.
          content:
            application/json:
              schema:
                type: object
                properties:
                  device_code: { type: string, example: "cwd_9f…" }
                  user_code: { type: string, example: BCDF-GHJK }
                  verification_url: { type: string, format: uri, example: "https://computaco.ai/console/#/device?code=BCDF-GHJK" }
                  verification_uri: { type: string, format: uri, example: "https://computaco.ai/console/#/device" }
                  expires_in: { type: integer, example: 900 }
                  interval: { type: integer, example: 5 }
                  client_name: { type: string }
        "429": { $ref: "#/components/responses/Error" }
  /v1/agent/device/token:
    post:
      tags: [agent]
      security: []
      summary: Poll a device sign-in; once approved, the agent token (exactly once).
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [device_code], properties: { device_code: { type: string } } }
      responses:
        "200":
          description: Approved.
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token: { type: string, example: "cwt_4b…" }
                  token_type: { type: string, example: bearer }
                  scope: { type: string, example: agent }
                  token_id: { type: string, example: agt_1a2b3c4d5e6f }
                  user: { type: object, properties: { id: { type: string }, username: { type: string }, plan: { type: string } } }
        "400": { description: "`authorization_pending` (keep polling), `slow_down` (poll less often), `access_denied` (declined), `expired_token` (15 minutes passed), `invalid_grant` (unknown or used), `bad_request`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /v1/agent/device/{user_code}:
    get:
      tags: [agent]
      security: [{ session: [] }]
      summary: A pending sign-in, for the console to show before the person approves it.
      parameters: [{ $ref: "#/components/parameters/UserCode" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { user_code: { type: string }, client_name: { type: string }, created_at: { type: integer }, expires_at: { type: integer } } } } } }
        "403": { description: Not a console session (a device is never approved with a key). }
        "404": { description: Unknown, expired, or already decided. }
  /v1/agent/device/{user_code}/approve:
    post:
      tags: [agent]
      security: [{ session: [] }]
      summary: Approve a sign-in; the app's next poll gets an agent token for this account.
      parameters: [{ $ref: "#/components/parameters/UserCode" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { user_code: { type: string }, status: { type: string, enum: [approved] } } } } } }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /v1/agent/device/{user_code}/deny:
    post:
      tags: [agent]
      security: [{ session: [] }]
      summary: Decline a sign-in; the app's next poll gets access_denied.
      parameters: [{ $ref: "#/components/parameters/UserCode" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { user_code: { type: string }, status: { type: string, enum: [denied] } } } } } }
        "404": { $ref: "#/components/responses/Error" }
  /v1/agent/tokens:
    get:
      tags: [agent]
      summary: The apps signed in to this account.
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { tokens: { type: array, items: { type: object, properties: { id: { type: string }, name: { type: string }, created_at: { type: integer }, last_used_at: { type: [integer, "null"] } } } } } } } } }
  /v1/agent/tokens/{token_id}:
    delete:
      tags: [agent]
      summary: Sign an app out; its token stops working at once.
      parameters: [{ name: token_id, in: path, required: true, schema: { type: string }, example: agt_1a2b3c4d5e6f }]
      responses:
        "204": { description: Revoked. }
        "404": { $ref: "#/components/responses/Error" }
  /v1/billing/payg:
    get:
      tags: [billing, agent]
      summary: Computatrum pay-as-you-go settings and this month's spend.
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { payg: { $ref: "#/components/schemas/Payg" }, configured: { type: boolean } } } } } }
    put:
      tags: [billing, agent]
      summary: Turn pay as you go on or off, or set its cap and mode.
      description: >
        Turning it on the first time answers a Stripe Checkout URL for a subscription to the
        metered price (credits at $0.0025, reported to a Billing Meter); it is on once Stripe's
        webhook reports the subscription. Off, cap and mode take effect at once.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled: { type: boolean }
                mode: { type: string, enum: [overflow, always], description: "overflow: after the plan's windows; always: every call." }
                monthly_cap_usd: { type: number, minimum: 1, maximum: 10000, example: 20 }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { payg: { $ref: "#/components/schemas/Payg" }, checkout_url: { type: [string, "null"], format: uri } } } } } }
        "400": { $ref: "#/components/responses/Error" }
        "503": { description: "`billing_not_configured`." }

  /v1/catalog/worlds:
    get:
      tags: [catalog]
      summary: The built-in worlds a world or blueprint can start from.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  worlds: { type: array, items: { $ref: "#/components/schemas/CatalogWorld" } }
  /v1/catalog/worlds/{name}:
    get:
      tags: [catalog]
      summary: A built-in world's full definition (the world schema, https://computaco.ai/docs/world-schema.html).
      parameters: [{ name: name, in: path, required: true, schema: { type: string }, example: agent-desktop }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WorldDefinition" }
  /v1/catalog/procedures:
    get:
      tags: [catalog]
      summary: Built-in expert procedures (https://computaco.ai/docs/expert-procedures.html).
      parameters:
        - { name: world, in: query, schema: { type: string }, description: Only this world's procedures. }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  procedures:
                    type: array
                    items:
                      type: object
                      properties:
                        world: { type: string }
                        id: { type: string }
                        domain: { type: string }
                        instruction: { type: string }
                        roles: { type: array, items: { type: string } }
                        phases: { type: integer }
                        app_modules: { type: array, items: { type: string }, description: The application modules its phases launch. }
                        host: { type: [string, "null"], description: The host a world booted for it runs on; null when none carries its applications (`unhosted` names them). }
                        unhosted: { type: array, items: { type: string } }
  /v1/catalog/procedures/{world}/{id}:
    get:
      tags: [catalog]
      summary: One built-in procedure, whole.
      parameters:
        - { name: world, in: path, required: true, schema: { type: string } }
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Procedure" }
  /v1/catalog/societies:
    get:
      tags: [catalog]
      summary: Built-in society specs (https://computaco.ai/docs/societies.html).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  societies:
                    type: array
                    items:
                      type: object
                      properties:
                        world: { type: string }
                        id: { type: string }
                        people: { type: integer, description: Named people plus population counts. }
                        rounds: { type: integer }
                        arms: { type: array, items: { type: string } }
  /v1/catalog/societies/{world}/{id}:
    get:
      tags: [catalog]
      summary: One built-in society spec, whole.
      parameters:
        - { name: world, in: path, required: true, schema: { type: string } }
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SocietySpec" }
  /v1/catalog/vocabulary:
    get:
      tags: [catalog]
      summary: The closed vocabularies a console's pickers offer.
      description: Platforms (desktop themes), presentations, OS profiles, service kinds, action families, observation channels, link directions.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  platforms: { type: array, items: { type: string }, example: [macos, windows, ubuntu, ios, android] }
                  platforms_available: { type: array, items: { type: string }, description: What this deployment's engine draws; a definition naming another platform is refused at boot. }
                  presentations: { type: array, items: { type: string }, example: [desktop, laptop, phone, server] }
                  profiles: { type: array, items: { type: object } }
                  service_kinds: { type: array, items: { type: string } }
                  action_families: { type: array, items: { type: string } }
                  observation_channels: { type: array, items: { type: string } }
                  link_fields: { type: object }
                  zones: { type: array, items: { type: string }, example: [local, internet, host] }
                  internet_attachments: { type: array, items: { type: string } }
                  applications: { type: array, items: { type: string }, description: Application kinds `application.v1 launch` takes. }

  # ---------------------------------------------------------------- blueprints
  /v1/blueprints:
    get:
      tags: [blueprints]
      summary: The account's blueprints.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  blueprints: { type: array, items: { $ref: "#/components/schemas/BlueprintSummary" } }
    post:
      tags: [blueprints]
      summary: Create a blueprint from a built-in world, another blueprint, or a definition.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                from: { type: string, description: "A built-in world name (`agent-desktop`) or a blueprint id (`bp_...`)." }
                definition: { $ref: "#/components/schemas/WorldDefinition" }
      responses:
        "201":
          description: Created (validated by the engine).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Blueprint" }
        "422": { $ref: "#/components/responses/Error" }
  /v1/blueprints/{blueprint_id}:
    parameters: [{ $ref: "#/components/parameters/BlueprintId" }]
    get:
      tags: [blueprints]
      summary: A blueprint with its whole definition.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Blueprint" }
    put:
      tags: [blueprints]
      summary: Replace the definition (and/or name). Validated by the engine before it is kept.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                definition: { $ref: "#/components/schemas/WorldDefinition" }
      responses:
        "200":
          description: Saved.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Blueprint" }
        "422": { $ref: "#/components/responses/Error" }
    delete:
      tags: [blueprints]
      summary: Delete a blueprint (worlds booted from it keep running).
      responses:
        "204": { description: Deleted. }
  /v1/blueprints/{blueprint_id}/topology:
    parameters: [{ $ref: "#/components/parameters/BlueprintId" }]
    get:
      tags: [blueprints, topology]
      summary: The blueprint's topology as a graph (machines, nodes, links, services, themes).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Topology" }
    patch:
      tags: [blueprints, topology]
      summary: Apply topology edits in order, validate, and save (all or nothing).
      description: |
        The console's "what machines are connected and how" editor sends these. The edits apply
        to the definition in order; the result is validated by the engine (a world is booted and
        dropped); on any error nothing is saved and the answer says which edit failed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [edits]
              properties:
                edits: { type: array, items: { $ref: "#/components/schemas/TopologyEdit" } }
      responses:
        "200":
          description: Saved; the new topology.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Topology" }
        "422": { $ref: "#/components/responses/Error" }
  /v1/validate:
    post:
      tags: [blueprints]
      summary: Validate a world definition without keeping it.
      description: Size-checked (quota `definition_bytes`), schema-checked, then booted by the engine with seed 0 and dropped.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [definition]
              properties:
                definition: { $ref: "#/components/schemas/WorldDefinition" }
      responses:
        "200":
          description: The verdict.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  errors: { type: array, items: { type: string } }
                  machines: { type: integer }
                  services: { type: integer }
                  boot_ms: { type: number }

  # ---------------------------------------------------------------- worlds
  /v1/worlds:
    get:
      tags: [worlds]
      summary: The account's running worlds.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  worlds: { type: array, items: { $ref: "#/components/schemas/WorldSummary" } }
    post:
      tags: [worlds]
      summary: Boot a world from a built-in world, a blueprint, or a definition.
      description: |
        Exactly one of `world`, `blueprint`, `definition`. `platform` redraws every machine as
        that desktop (it sets `metadata.desktop_themes`). `procedure` boots the world for that
        procedure: its machines get the desktops it names (as `Procedure::prepare` does), and
        the world goes to a host carrying the applications it launches. `host` names the host
        instead (https://computaco.ai/docs/cloud-api.html § Hosts); left out, the host carrying most of what the
        world's machines can launch is chosen, core on a tie. Counts against `worlds_max`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                world: { type: string, example: agent-desktop }
                blueprint: { type: string, example: bp_3f2a9c }
                definition: { $ref: "#/components/schemas/WorldDefinition" }
                seed: { type: integer, default: 1 }
                platform: { type: string, enum: [macos, windows, ubuntu, ios, android] }
                procedure:
                  description: A procedure to boot the world for, `{world, id}` from the catalog or inline.
                  oneOf: [{ type: object, properties: { world: { type: string }, id: { type: string } } }, { type: object }]
                host: { type: string, enum: [core, office, media], description: The host to run the world on. }
                idle_ttl:
                  type: integer
                  minimum: 60
                  maximum: 86400
                  description: >
                    Seconds with no request to the world after which it deletes itself, as DELETE
                    /v1/worlds/{id} does, and gives its place under `worlds_max` back. Any request that
                    reaches the world (an owner's or an actor token's) starts the time again. Left
                    out, the world is kept until it is deleted.
      responses:
        "201":
          description: Booted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/World" }
        "422": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /v1/worlds/import:
    post:
      tags: [worlds, snapshots]
      summary: Boot a new world from an exported snapshot.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [snapshot]
              properties:
                name: { type: string }
                snapshot: { type: object, description: "The object `GET /v1/worlds/{id}/export` returns." }
      responses:
        "201":
          description: Imported.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/World" }
  /v1/worlds/{world_id}:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [worlds]
      summary: A world's summary.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/World" }
    delete:
      tags: [worlds]
      summary: Delete the world, its snapshots and its environments (their tokens stop working).
      responses:
        "204": { description: Deleted. }
  /v1/worlds/{world_id}/state_hash:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [worlds]
      summary: The world's state hash and tick.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Clock" }
  /v1/worlds/{world_id}/advance:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [worlds]
      summary: Move the world clock forward (timers, settlement windows, queues fire).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ticks: { type: integer, minimum: 0, description: Logical microseconds. }
                duration: { type: string, example: 1h, description: "Alternative to `ticks`: `500ms`, `30s`, `5m`, `2h`, `1d`." }
      responses:
        "200":
          description: Advanced.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Clock" }
  /v1/worlds/{world_id}/reset:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [worlds]
      summary: Reset to the definition with a (new) seed. Environments are dropped.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                seed: { type: integer }
      responses:
        "200":
          description: Reset.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Clock" }
  /v1/worlds/{world_id}/files:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { name: path, in: query, required: true, schema: { type: string, example: /home/ada/notes.txt }, description: An absolute path on the machine. }
      - { name: machine, in: query, schema: { type: string, example: workstation }, description: The machine; may be left out when the world has one. }
    get:
      tags: [worlds]
      summary: The owner reads a file on a machine.
      description: >
        Read through the owner's file session on the machine: an environment of the machine's
        user granted only `filesystem.v1`, made on the first file call and kept in the world's
        state (a fork carries it, a reset drops it). No actor token reaches this route. A path
        that is not a readable file (absent, or a directory) is a 404: the engine does not say
        which. At most 4 MiB.
      responses:
        "200":
          description: The file.
          content:
            application/json:
              schema:
                type: object
                properties:
                  machine: { type: string }
                  path: { type: string }
                  content: { type: string, description: The bytes as UTF-8 (lossy). }
                  base64: { type: string, format: byte, description: The exact bytes. }
                  bytes: { type: integer }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "413": { $ref: "#/components/responses/Error" }
    put:
      tags: [worlds]
      summary: The owner writes a file onto a machine.
      description: Exactly one of `content` (text) or `base64` (bytes); at most 4 MiB. Recorded in the world's trajectory like any action.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                content: { type: string }
                base64: { type: string, format: byte }
      responses:
        "200":
          description: Written.
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: "#/components/schemas/Clock" }
                  - type: object
                    properties:
                      machine: { type: string }
                      path: { type: string }
                      bytes: { type: integer }
        "400": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "413": { $ref: "#/components/responses/Error" }
  /v1/worlds/{world_id}/inspect:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [worlds]
      summary: The owner's view of the whole world state (`World.inspect()`).
      parameters:
        - { name: pointer, in: query, schema: { type: string }, description: "A JSON Pointer into the answer (`/state/services/mail/state`); only that value is returned." }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { type: object }
  /v1/worlds/{world_id}/definition:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [worlds]
      summary: The definition the world was booted from (with live machine additions).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WorldDefinition" }
  /v1/worlds/{world_id}/trajectory:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [worlds]
      summary: Every action the world has taken, in order (the replay journal).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { type: object }
  /v1/worlds/{world_id}/services/{service_id}/state:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { name: service_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [worlds]
      summary: One service's state.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { type: object }
    patch:
      tags: [worlds]
      summary: An owner write to a service's state (JSON merge patch), recorded so a replay repeats it.
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Patched.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Clock" }
  /v1/worlds/{world_id}/perform:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [worlds, procedures]
      summary: Perform one grounded gesture through an environment (`World.perform`).
      description: The engine finds the target on the session's screen the way a procedure does and sends the steps; the answer lists them.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [environment, gesture]
              properties:
                environment: { type: string, example: env_1 }
                gesture: { $ref: "#/components/schemas/Gesture" }
                options:
                  type: object
                  properties:
                    width: { type: integer, default: 1280 }
                    height: { type: integer, default: 800 }
      responses:
        "200":
          description: Performed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  steps: { type: array, items: { type: object } }
                  state_hash: { type: string }
                  tick: { type: integer }

  # ---------------------------------------------------------------- topology (running world)
  /v1/worlds/{world_id}/topology:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [topology]
      summary: The running world's topology (machines, nodes, links, services, themes).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Topology" }
  /v1/worlds/{world_id}/machines:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [topology]
      summary: Add a machine to the running world, wired explicitly (`World.addComputer`).
      description: |
        The only topology change a running world takes live. Links, services and themes of a
        running world are fixed at boot: edit a blueprint (`PATCH /v1/blueprints/{id}/topology`)
        and boot it, or fork and rebuild.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [computer]
              properties:
                computer: { $ref: "#/components/schemas/Computer" }
                node: { $ref: "#/components/schemas/Node" }
                links: { type: array, items: { $ref: "#/components/schemas/Link" } }
                theme: { type: string, enum: [macos, windows, ubuntu, ios, android] }
      responses:
        "201":
          description: Added; the new topology.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Topology" }
        "409": { $ref: "#/components/responses/Error" }
  /v1/worlds/{world_id}/machines/{machine_id}:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { name: machine_id, in: path, required: true, schema: { type: string } }
    delete:
      tags: [topology]
      summary: Remove a machine from the running world (`World.removeComputer`).
      responses:
        "200":
          description: Removed; the new topology.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Topology" }

  # ---------------------------------------------------------------- snapshots
  /v1/worlds/{world_id}/snapshots:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [snapshots]
      summary: The world's kept snapshots.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  snapshots: { type: array, items: { $ref: "#/components/schemas/Snapshot" } }
    post:
      tags: [snapshots]
      summary: Take a snapshot (kept in the world's storage; counts against `snapshots_per_world`).
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                label: { type: string }
      responses:
        "201":
          description: Taken.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Snapshot" }
  /v1/worlds/{world_id}/snapshots/{snapshot_id}:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { $ref: "#/components/parameters/SnapshotId" }
    delete:
      tags: [snapshots]
      summary: Drop a snapshot.
      responses:
        "204": { description: Dropped. }
  /v1/worlds/{world_id}/snapshots/{snapshot_id}/restore:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { $ref: "#/components/parameters/SnapshotId" }
    post:
      tags: [snapshots]
      summary: Restore the world to a snapshot. Environments that existed then are reconnected; later ones are dropped.
      responses:
        "200":
          description: Restored.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Clock" }
  /v1/worlds/{world_id}/fork:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [snapshots]
      summary: Fork into a new world (now, or from a kept snapshot). Environments are carried over with new tokens.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                snapshot: { type: string, description: A snapshot id; omitted, the world as it is now. }
                host: { type: string, enum: [core, office, media], description: Fork onto another host; omitted, the world's own. }
      responses:
        "201":
          description: Forked; the new world, whose `state_hash` equals the source's at the fork point.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/World"
                  - type: object
                    properties:
                      environments: { type: array, items: { $ref: "#/components/schemas/EnvironmentCreated" } }
  /v1/worlds/{world_id}/export:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [snapshots]
      summary: The world as a portable snapshot (`exportSnapshot`), with its definition and seed.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  format: { type: string, const: computerworld-cloud-export/1 }
                  engine: { $ref: "#/components/schemas/EngineInfo" }
                  definition: { $ref: "#/components/schemas/WorldDefinition" }
                  seed: { type: integer }
                  snapshot: { type: string, description: The engine's snapshot JSON, as a string. }
                  state_hash: { type: string }
                  environments: { type: array, items: { $ref: "#/components/schemas/EnvironmentConfig" } }

  # ---------------------------------------------------------------- environments
  /v1/worlds/{world_id}/environments:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    get:
      tags: [environments]
      summary: The world's environments (grants; never their tokens).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  environments: { type: array, items: { $ref: "#/components/schemas/Environment" } }
    post:
      tags: [environments]
      summary: Grant an actor an environment. Returns its actor token once.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/EnvironmentConfig" }
      responses:
        "201":
          description: Granted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EnvironmentCreated" }
        "409": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /v1/worlds/{world_id}/environments/{env_id}:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { $ref: "#/components/parameters/EnvId" }
    get:
      tags: [environments]
      security: [{ apiKey: [] }, { session: [] }, { actorToken: [] }]
      summary: The environment's grant.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Environment" }
    delete:
      tags: [environments]
      summary: Revoke the environment; its token stops working.
      responses:
        "204": { description: Revoked. }
  /v1/worlds/{world_id}/environments/{env_id}/step:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { $ref: "#/components/parameters/EnvId" }
    post:
      tags: [environments]
      security: [{ apiKey: [] }, { session: [] }, { actorToken: [] }]
      summary: Send actions (`Environment.step`). Each is checked against the grant.
      description: |
        `machine` may be omitted when the grant names one machine. An action outside the grant
        comes back as an unsuccessful outcome (not an HTTP error); the HTTP status is 200 whenever
        the batch was taken. Counts against `steps_per_minute` (each action is one).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [actions]
              properties:
                actions: { type: array, maxItems: 64, items: { $ref: "#/components/schemas/Action" } }
                render:
                  type: object
                  description: >
                    Draw the screen the actions left and answer it as `frame`, in the same round trip:
                    what GET …/render would draw for the new state. Counts as one render. With
                    `if_changed`, a step that leaves the state hash as it was draws nothing.
                  properties:
                    width: { type: integer, minimum: 64, maximum: 2560, default: 1280 }
                    height: { type: integer, minimum: 64, maximum: 1600, default: 800 }
                    if_changed: { type: boolean, default: false }
      responses:
        "200":
          description: The outcomes, in order (and `frame`, when `render` was given).
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: "#/components/schemas/StepResult" }
                  - type: object
                    properties:
                      frame:
                        type: object
                        properties:
                          png: { type: string, format: byte, description: The PNG, base64. }
                          width: { type: integer }
                          height: { type: integer }
                          state_hash: { type: string }
        "429": { $ref: "#/components/responses/Error" }
  /v1/worlds/{world_id}/environments/{env_id}/observe:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { $ref: "#/components/parameters/EnvId" }
    get:
      tags: [environments]
      security: [{ apiKey: [] }, { session: [] }, { actorToken: [] }]
      summary: The environment's observation channels (`Environment.observe`).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { type: object, properties: { channels: { type: object }, tick: { type: integer } } }
  /v1/worlds/{world_id}/environments/{env_id}/scene:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { $ref: "#/components/parameters/EnvId" }
      - { $ref: "#/components/parameters/Width" }
      - { $ref: "#/components/parameters/Height" }
      - { name: elements, in: query, schema: { type: boolean }, description: "Also return `elements`: every interactive node with its role, label, interaction id and clipped box in screen pixels, front first." }
    get:
      tags: [environments]
      security: [{ apiKey: [] }, { session: [] }, { actorToken: [] }]
      summary: The screen as a scene graph (`Environment.scene`) — windows, controls, text runs, bounds, roles.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  width: { type: integer }
                  height: { type: integer }
                  digest: { type: string }
                  focus: { type: object }
                  nodes: { type: array, items: { type: object } }
                  elements: { type: array, items: { $ref: "#/components/schemas/Element" } }
  /v1/worlds/{world_id}/environments/{env_id}/render:
    parameters:
      - { $ref: "#/components/parameters/WorldId" }
      - { $ref: "#/components/parameters/EnvId" }
      - { $ref: "#/components/parameters/Width" }
      - { $ref: "#/components/parameters/Height" }
      - { name: format, in: query, schema: { type: string, enum: [png, rgba], default: png } }
      - { name: If-None-Match, in: header, schema: { type: string }, description: "The ETag of a frame already held: an unchanged screen answers 304 and is not counted against renders_per_minute." }
    get:
      tags: [environments]
      security: [{ apiKey: [] }, { session: [] }, { actorToken: [] }]
      summary: The screen as pixels. Counts against `renders_per_minute`.
      responses:
        "200":
          description: "A PNG (or raw RGBA bytes, row-major, with `X-Width`/`X-Height`). `X-State-Hash` and `X-Tick` name the state drawn; `ETag` is the state hash, environment, size and format. The screen shown is the environment's focused machine: a grant with several machines draws the one it acts on (a `machine` parameter needs engine support; grant one environment per machine)."
          content:
            image/png: { schema: { type: string, format: binary } }
            application/octet-stream: { schema: { type: string, format: binary } }
        "304": { description: The screen is the one the ETag names. }

  # ---------------------------------------------------------------- procedures
  /v1/procedures/instantiate:
    post:
      tags: [procedures]
      summary: Draw a procedure's parameters from a seed (stateless).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProcedureRef" }
      responses:
        "200":
          description: The instance.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProcedureInstance" }
  /v1/worlds/{world_id}/procedures/stage:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [procedures]
      summary: Write an instance's setup files into the world (owner).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [instance]
              properties:
                instance: { $ref: "#/components/schemas/ProcedureInstance" }
      responses:
        "200":
          description: Staged.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Clock" }
  /v1/worlds/{world_id}/procedures/project:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [procedures]
      summary: Perform an instance through environments and return the demonstration.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [instance, environment]
              properties:
                instance: { $ref: "#/components/schemas/ProcedureInstance" }
                environment:
                  description: "One environment id, or `{role: env_id}` for a team procedure."
                  oneOf: [{ type: string }, { type: object, additionalProperties: { type: string } }]
                options: { $ref: "#/components/schemas/ProjectOptions" }
      responses:
        "200":
          description: The demonstration.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Demonstration" }
  /v1/worlds/{world_id}/procedures/verify:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [procedures]
      summary: Judge the world against an instance's checks.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [instance]
              properties:
                instance: { $ref: "#/components/schemas/ProcedureInstance" }
                captures: { type: object, additionalProperties: { type: string }, description: "A demonstration's `captures`: fills the checks that name a captured `{{@name}}`." }
      responses:
        "200":
          description: The verification.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Verification" }
  /v1/worlds/{world_id}/procedures/run:
    parameters: [{ $ref: "#/components/parameters/WorldId" }]
    post:
      tags: [procedures]
      summary: Instantiate, stage, project and verify in one call.
      description: |
        Grants the session(s) itself (the procedure's machine and user, or each role's) with
        pointer, keyboard, application, browser and filesystem, unless `environment` names
        existing ones. `fork: true` runs it in a throwaway in-memory fork and leaves this world
        untouched (`world` is then null); use `POST /fork` first to keep the result.
        The world must run on a host carrying every application the procedure launches (409
        otherwise, naming the host to fork to), and must have been booted with the desktops
        the procedure names (409 otherwise: boot a world with `procedure`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/ProcedureRef"
                - type: object
                  properties:
                    environment: { oneOf: [{ type: string }, { type: object, additionalProperties: { type: string } }] }
                    options: { $ref: "#/components/schemas/ProjectOptions" }
                    fork: { type: boolean, default: false }
                    include_steps: { type: boolean, default: false }
      responses:
        "200":
          description: The run.
          content:
            application/json:
              schema:
                type: object
                properties:
                  instance: { $ref: "#/components/schemas/ProcedureInstance" }
                  demonstration: { $ref: "#/components/schemas/Demonstration" }
                  verification: { $ref: "#/components/schemas/Verification" }
                  world: { type: [string, "null"], description: "The world it ran in; null with `fork: true`." }
                  state_hash: { type: string }
                  timings_ms: { type: object }
                  host: { type: string }
                  memory: { $ref: "#/components/schemas/Memory" }
        "409": { $ref: "#/components/responses/Error" }

  # ---------------------------------------------------------------- swarms
  /v1/swarms/compile:
    post:
      tags: [swarms]
      summary: Compile a society spec without running it — the people, devices, accounts, follows and arms it becomes.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SwarmCreate" }
      responses:
        "200":
          description: The compiled plan.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SwarmPlan" }
        "422": { $ref: "#/components/responses/Error" }
  /v1/swarms:
    get:
      tags: [swarms]
      summary: The account's swarms.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  swarms: { type: array, items: { $ref: "#/components/schemas/Swarm" } }
    post:
      tags: [swarms]
      summary: Compile a society and boot it (one world, at round 0, before the fork).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SwarmCreate" }
      responses:
        "201":
          description: Booted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Swarm" }
  /v1/swarms/{swarm_id}:
    parameters: [{ $ref: "#/components/parameters/SwarmId" }]
    get:
      tags: [swarms]
      summary: The swarm's state (round, arms, each arm's world id and state hash, metrics so far).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Swarm" }
    delete:
      tags: [swarms]
      summary: Delete the swarm and its arms' worlds.
      responses:
        "204": { description: Deleted. }
  /v1/swarms/{swarm_id}/rounds:
    parameters: [{ $ref: "#/components/parameters/SwarmId" }]
    post:
      tags: [swarms]
      summary: Run rounds. At `fork_at` the arms are forked automatically (matched fork).
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                count: { type: integer, default: 1, minimum: 1, maximum: 100, description: Rounds to run (capped by the horizon). }
                ask_model: { type: boolean, default: false, description: "Ask the deployment's model (DeepSeek) the prompts the journal lacks, counted against model_calls_per_day, and run the round again with the answers. Without it, such a round comes back `pending`." }
                arms: { type: array, items: { type: string }, description: Only these arms (default all). }
      responses:
        "200":
          description: The `round_end` records of each arm.
          content:
            application/json:
              schema:
                type: object
                properties:
                  round: { type: integer }
                  done: { type: boolean }
                  rounds: { type: array, items: { $ref: "#/components/schemas/RoundEnd" } }
                  pending: { type: array, items: { $ref: "#/components/schemas/PendingDecision" }, description: "LLM decisions with no journal entry: the round was rolled back in the arms that met them; answer with POST /journal (or ask_model) and run it again." }
                  model: { type: object, properties: { calls: { type: integer }, prompt_tokens: { type: integer }, completion_tokens: { type: integer } } }
                  note: { type: string }
  /v1/swarms/{swarm_id}/fork:
    parameters: [{ $ref: "#/components/parameters/SwarmId" }]
    post:
      tags: [swarms]
      summary: Fork the arms now (before `fork_at`), applying each arm's ops.
      responses:
        "200":
          description: Forked.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Swarm" }
  /v1/swarms/{swarm_id}/journal:
    parameters: [{ $ref: "#/components/parameters/SwarmId" }]
    get:
      tags: [swarms]
      summary: The LLM journal (model replies keyed by prompt hash), JSON lines.
      responses:
        "200":
          description: OK
          content:
            application/x-ndjson: { schema: { type: string } }
    post:
      tags: [swarms]
      summary: Add journal entries (answers for pending LLM decisions, or a recorded journal to replay).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                entries: { type: array, items: { type: object, properties: { key: { type: string }, reply: { type: string } } } }
      responses:
        "200":
          description: Added.
          content:
            application/json:
              schema: { type: object, properties: { entries: { type: integer } } }
  /v1/swarms/{swarm_id}/trace:
    parameters: [{ $ref: "#/components/parameters/SwarmId" }]
    get:
      tags: [swarms]
      summary: One arm's trace (boot, round_start, activate, decision, inject, intervention, round_end), JSON lines.
      parameters:
        - { name: arm, in: query, schema: { type: string } }
        - { name: since, in: query, schema: { type: integer }, description: Only records after this index. }
      responses:
        "200":
          description: OK
          content:
            application/x-ndjson: { schema: { type: string } }
  /v1/swarms/{swarm_id}/summary:
    parameters: [{ $ref: "#/components/parameters/SwarmId" }]
    get:
      tags: [swarms]
      summary: Outcomes per arm, checks, divergence per round and the common-random-numbers check.
      description: The shape scripts/society/experiment.py writes (cloud/src/society/summary.ts is its port). Before the fork, `{forked false, rounds}`.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SwarmSummary" }

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: "An API key: `Authorization: Bearer cw_live_...`. Owns the account's worlds."
    session:
      type: apiKey
      in: cookie
      name: cw_session
      description: The console's HttpOnly session cookie. Mutating calls with a cookie must send `Origin` matching the host.
    actorToken:
      type: http
      scheme: bearer
      description: "An actor token `cwa_...`: step, observe, scene, render of one environment only."
    agentToken:
      type: http
      scheme: bearer
      description: "An agent token `cwt_...` from the device sign-in: /v1/agent/chat, /v1/agent/vision and /v1/agent/usage only."
  parameters:
    ComputatrumTask:
      name: X-Computatrum-Task
      in: header
      required: false
      schema: { type: string, pattern: "^[A-Za-z0-9_-]{1,64}$", example: 4f1c2a9e-7b3d-4e8a-9c1f-2d6b8e0a5c3b }
      description: The task the call belongs to (a UUID per task); its gaps to the task's previous call count as agent-minutes, up to 15 s. Without one a call counts its own time only.
    KeyId: { name: key_id, in: path, required: true, schema: { type: string }, example: key_8c1f20aa }
    OrgId: { name: org_id, in: path, required: true, schema: { type: string }, description: "An organization's id: org_… for a team, the account's own id for its personal one.", example: org_3f2a9c11d4e5b6a7 }
    BlueprintId: { name: blueprint_id, in: path, required: true, schema: { type: string }, example: bp_3f2a9c11 }
    WorldId: { name: world_id, in: path, required: true, schema: { type: string }, example: w_5d1e0c7b2a }
    SnapshotId: { name: snapshot_id, in: path, required: true, schema: { type: string }, example: s_3 }
    EnvId: { name: env_id, in: path, required: true, schema: { type: string }, example: env_1 }
    SwarmId: { name: swarm_id, in: path, required: true, schema: { type: string }, example: sw_77ab01 }
    UserCode: { name: user_code, in: path, required: true, schema: { type: string }, example: BCDF-GHJK, description: "Case, spaces and the dash do not matter." }
    Width: { name: width, in: query, schema: { type: integer, default: 1280, minimum: 64, maximum: 2560 } }
    Height: { name: height, in: query, schema: { type: integer, default: 800, minimum: 64, maximum: 1600 } }
  responses:
    Error:
      description: An error.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
    EngineInfo:
      type: object
      properties:
        version: { type: string, description: "engineVersion()" }
        build: { type: string, example: computerworld-web-full }
        wasm_sha256: { type: string }
        lazy: { type: boolean }
    Credentials:
      type: object
      required: [username, password]
      properties:
        username: { type: string, pattern: "^[a-z0-9_-]{3,32}$" }
        password: { type: string, minLength: 10, maxLength: 200 }
    User:
      type: object
      properties:
        id: { type: string, example: u_9a8b7c6d5e4f }
        username: { type: string }
        created_at: { type: integer, description: Unix ms. }
        plan: { type: string, example: free }
        name: { type: [string, "null"], maxLength: 100, example: Ada Lovelace }
        email: { type: [string, "null"], format: email, example: ada@example.org }
        date_of_birth: { type: [string, "null"], format: date, example: "1990-02-28" }
    ProfilePatch:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        name: { type: [string, "null"], maxLength: 100 }
        email: { type: [string, "null"], format: email, maxLength: 254 }
        date_of_birth: { type: [string, "null"], format: date, description: "yyyy-mm-dd, in the past, 1900 or later." }
    Role: { type: string, enum: [owner, admin, member], description: "owner > admin > member." }
    OrgRef:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
    Org:
      type: object
      properties:
        id: { type: string, example: org_3f2a9c11d4e5b6a7 }
        name: { type: string }
        kind: { type: string, enum: [personal, team] }
        plan: { type: string, example: free }
        role: { $ref: "#/components/schemas/Role" }
        created_at: { type: integer }
        members: { type: integer }
    Member:
      type: object
      properties:
        user_id: { type: string }
        username: { type: string }
        name: { type: [string, "null"] }
        email: { type: [string, "null"] }
        role: { $ref: "#/components/schemas/Role" }
        joined_at: { type: integer }
    Invite:
      type: object
      properties:
        id: { type: string, example: inv_8c1f20aa }
        org_id: { type: string }
        code: { type: string, example: cwi_3f2a9c11d4e5b6a7 }
        url: { type: string, description: "The link to send: /console/#/join?code=…" }
        role: { $ref: "#/components/schemas/Role" }
        email: { type: [string, "null"] }
        max_uses: { type: [integer, "null"] }
        uses: { type: integer }
        created_by: { type: string }
        created_at: { type: integer }
        expires_at: { type: integer }
    Entitlement:
      type: object
      properties:
        product: { type: string, example: kicad }
        purchased_at: { type: integer }
        purchased_by: { type: [string, "null"] }
        amount_cents: { type: integer }
        source: { type: string, enum: [stripe, operator] }
    Application:
      type: object
      description: A professional application for sale (packages/catalog.json `products`), as one organization sees it.
      properties:
        id: { type: string, example: kicad }
        name: { type: string, example: KiCad }
        description: { type: string }
        price_cents: { type: integer, example: 49000 }
        price: { type: string, example: $490 }
        packages: { type: array, items: { type: string } }
        purchased: { type: boolean }
        purchased_at: { type: [integer, "null"] }
        purchased_by: { type: [string, "null"] }
    ProfessionalUsage:
      type: object
      description: Steps this month in worlds running bought applications, against the plan's allowance, and the metered billing beyond it.
      properties:
        period_start: { type: integer }
        period_end: { type: integer }
        steps: { type: integer }
        steps_included: { type: integer }
        steps_left: { type: integer }
        unit: { type: object, properties: { steps_per_credit: { type: integer, example: 100 }, price_usd_per_credit: { type: number, example: 0.0025 }, usd_per_1000_steps: { type: number, example: 0.025 } } }
        metered: { type: object, properties: { enabled: { type: boolean }, requested: { type: boolean }, subscribed: { type: boolean }, status: { type: string }, monthly_cap_usd: { type: number }, credits: { type: number }, spend_usd: { type: number }, unreported_credits: { type: number } } }
    ApiKey:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        org_id: { type: string, description: The organization the key acts in. }
        created_by: { type: [string, "null"] }
        prefix: { type: string, example: cw_live_3f9a }
        created_at: { type: integer }
        last_used_at: { type: [integer, "null"] }
    ApiKeyCreated:
      allOf:
        - $ref: "#/components/schemas/ApiKey"
        - type: object
          properties:
            key: { type: string, description: The whole key. Shown once. }
    Plan:
      type: object
      properties:
        id: { type: string, enum: [free, pro, enterprise] }
        name: { type: string, example: Pro }
        price:
          description: null for Enterprise (by agreement).
          oneOf:
            - type: object
              properties:
                amount_cents: { type: integer, example: 2900 }
                currency: { type: string, example: usd }
                interval: { type: string, example: month }
            - type: "null"
        self_serve: { type: boolean, description: false for Enterprise. }
        contact: { type: [string, "null"], example: "mailto:sales@computaco.ai" }
        quotas: { $ref: "#/components/schemas/Quotas" }
    Discount:
      type: object
      properties:
        code: { type: string, example: FIRST100 }
        percent_off: { type: integer, example: 20 }
        duration: { type: string, enum: [forever, once, repeating], description: "forever: for the life of the subscription." }
        duration_in_months: { type: [integer, "null"] }
    Billing:
      type: object
      properties:
        plan: { type: string, enum: [free, pro, enterprise] }
        status: { type: string, enum: [none, active, past_due, canceled] }
        renews_at: { type: [integer, "null"], description: Unix ms of the next charge. }
        cancel_at_period_end: { type: boolean }
        grace_until: { type: [integer, "null"], description: "Past due: Pro is kept until then (Unix ms)." }
        discount: { oneOf: [{ $ref: "#/components/schemas/Discount" }, { type: "null" }] }
        referral_code: { type: [string, "null"], description: The code given at sign-up, applied at upgrade. }
        has_customer: { type: boolean, description: The account has been through checkout (the portal works). }
        configured: { type: boolean, description: This deployment takes payment. }
    Quotas:
      type: object
      properties:
        worlds_max: { type: integer, example: 8 }
        environments_per_world: { type: integer, example: 16 }
        snapshots_per_world: { type: integer, example: 16 }
        blueprints_max: { type: integer, example: 32 }
        swarms_max: { type: integer, example: 2 }
        swarm_people_max: { type: integer, example: 32 }
        api_keys_max: { type: integer, example: 10 }
        definition_bytes: { type: integer, example: 4194304 }
        requests_per_minute: { type: integer, example: 600 }
        steps_per_minute: { type: integer, example: 1200 }
        renders_per_minute: { type: integer, example: 120 }
        model_calls_per_day: { type: integer, example: 500, description: Model calls swarms make for their LLM people. }
        agent_minutes_5h: { type: number, example: 15, description: "Computatrum agent-minutes (the time the agent works on a task) in a rolling 5-hour window." }
        agent_minutes_week: { type: number, example: 30, description: Computatrum agent-minutes in a rolling week. }
    Usage:
      type: object
      properties:
        worlds: { type: integer }
        blueprints: { type: integer }
        swarms: { type: integer }
        api_keys: { type: integer }
        this_minute: { type: object, properties: { requests: { type: integer }, steps: { type: integer }, renders: { type: integer } } }
        today: { type: object, properties: { model_calls: { type: integer } } }
        totals: { type: object, properties: { requests: { type: integer }, steps: { type: integer }, renders: { type: integer }, worlds_created: { type: integer }, model_calls: { type: integer } } }
    CatalogWorld:
      type: object
      properties:
        name: { type: string, example: agent-desktop }
        id: { type: string, description: The definition's own id. }
        description: { type: string }
        machines: { type: array, items: { type: object, properties: { id: { type: string }, user: { type: string }, profile: { type: string }, theme: { type: string } } } }
        services: { type: integer }
        internet: { type: boolean }
        bytes: { type: integer }
        procedures: { type: integer }
        societies: { type: integer }
        boots: { type: boolean, description: Whether this deployment's engine boots it. }
        error: { type: string, description: The engine's reason when it does not. }
        app_modules: { type: array, items: { type: string }, description: The application modules its machines can launch. }
        host: { type: string, description: The host it boots on when no procedure or host is given. }
    WorldDefinition:
      type: object
      description: The world schema (https://computaco.ai/docs/world-schema.html). `schema_version` must be 1.
      required: [schema_version, id]
      properties:
        schema_version: { type: integer, const: 1 }
        id: { type: string }
        internet: { type: boolean }
        profiles: { type: array, items: { type: object } }
        computers: { type: array, items: { $ref: "#/components/schemas/Computer" } }
        network: { type: object }
        services: { type: array, items: { $ref: "#/components/schemas/Service" } }
        internet_overlays: { type: object }
        metadata: { type: object }
      additionalProperties: true
    Computer:
      type: object
      required: [id, profile, address, user]
      properties:
        id: { type: string }
        profile: { type: string, example: ubuntu }
        address: { type: string, example: 10.0.0.10 }
        user: { type: string }
        node: { type: string }
        presentation: { type: string, enum: [desktop, laptop, phone, server] }
        initial_files: { type: object, additionalProperties: { type: string }, description: "Path → content, seeded on boot." }
        installed_apps: { type: array, items: { type: string } }
        packages: { type: array, items: { type: string } }
      additionalProperties: true
    Node:
      type: object
      properties:
        id: { type: string }
        address: { type: string }
        zone: { type: string, enum: [local, internet, host] }
      additionalProperties: true
    Link:
      type: object
      required: [from, to]
      properties:
        from: { type: string, description: A node id. }
        to: { type: string }
        bidirectional: { type: boolean, default: true }
        latency_us: { type: integer, default: 10 }
        loss_per_million: { type: integer, default: 0 }
      additionalProperties: true
    Service:
      type: object
      required: [id, kind]
      properties:
        id: { type: string }
        kind: { type: string, example: mail }
        node: { type: string }
        domains: { type: array, items: { type: string } }
        port: { type: integer }
        tls: { type: boolean }
        initial_state: { type: object }
      additionalProperties: true
    Topology:
      type: object
      description: A graph view of a definition, for a console to draw. `links[].id` is the link's index in `network.links`.
      properties:
        world: { type: string }
        live: { type: boolean, description: true for a running world (only machines change live). }
        internet: { type: boolean }
        machines:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              profile: { type: string }
              user: { type: string }
              address: { type: string }
              node: { type: string }
              theme: { type: [string, "null"] }
              presentation: { type: [string, "null"], description: "desktop | laptop | phone | server (the computer's own, or metadata.device_presentations' kind)" }
              installed_apps: { type: array, items: { type: string } }
              desktop_apps: { type: [array, "null"], description: "metadata.desktop_apps for this machine, when declared." }
              packages: { type: array, items: { type: string } }
              initial_files: { type: array, items: { type: string }, description: The seeded files' paths. }
        nodes: { type: array, items: { $ref: "#/components/schemas/Node" } }
        links: { type: array, items: { allOf: [{ $ref: "#/components/schemas/Link" }, { type: object, properties: { id: { type: integer } } }] } }
        services: { type: array, items: { type: object, properties: { id: { type: string }, kind: { type: string }, node: { type: string }, domains: { type: array, items: { type: string } }, port: { type: integer } } } }
        dns: { type: array, items: { type: object } }
        themes: { type: object, additionalProperties: { type: string } }
        implicit_lan: { type: boolean }
    TopologyEdit:
      type: object
      description: |
        One edit. `op` is one of:
        `add_machine {computer, node?, links?, theme?}`, `update_machine {id, set}` (merged into the computer),
        `remove_machine {id}` (also its links, node and theme),
        `add_node {node}`, `remove_node {id}`,
        `add_link {link}`, `update_link {id, set}`, `remove_link {id}`,
        `add_service {service}`, `update_service {id, set}`, `remove_service {id}`,
        `set_theme {machine, theme}` (theme `null` removes it), `set_presentation {machine, presentation}`,
        `set_internet {internet}`, `set_implicit_lan {implicit_lan}`, `set_metadata {key, value}`.
      required: [op]
      properties:
        op: { type: string }
      additionalProperties: true
    BlueprintSummary:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        world_id: { type: string, description: The definition's id. }
        machines: { type: integer }
        services: { type: integer }
        bytes: { type: integer }
        created_at: { type: integer }
        updated_at: { type: integer }
    Blueprint:
      allOf:
        - $ref: "#/components/schemas/BlueprintSummary"
        - type: object
          properties:
            definition: { $ref: "#/components/schemas/WorldDefinition" }
    Clock:
      type: object
      properties:
        state_hash: { type: string }
        tick: { type: integer, description: The world clock, logical microseconds. }
    WorldSummary:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        source: { type: string, description: "`builtin:agent-desktop`, `blueprint:bp_...`, `definition`, `fork:w_...`, `import`, `swarm:sw_...`." }
        definition_id: { type: string }
        seed: { type: integer }
        created_at: { type: integer }
        machines: { type: array, items: { type: string } }
        idle_ttl: { type: [integer, "null"], description: "The world's idle lifetime in seconds, or null: kept until deleted." }
        expires_at: { type: [integer, "null"], description: "When it deletes itself if nothing reaches it first (ms since the epoch), or null." }
    World:
      allOf:
        - $ref: "#/components/schemas/WorldSummary"
        - $ref: "#/components/schemas/Clock"
        - type: object
          properties:
            environments: { type: integer }
            snapshots: { type: integer }
            boot_ms: { type: number, description: Engine boot (or import) time on this request. }
            resident: { type: boolean, description: Whether the engine instance was in memory (false = rebuilt from storage on this request). }
            host: { type: string, description: "The host its object runs on (https://computaco.ai/docs/cloud-api.html § Hosts): core, office or media. The id ends in `-<host>` off core." }
            memory: { $ref: "#/components/schemas/Memory" }
    Memory:
      type: object
      description: Wasm memory in the world's isolate (which other worlds of the same host may share).
      properties:
        wasm_bytes: { type: integer, description: The engine's. }
        module_bytes: { type: object, additionalProperties: { type: integer }, description: Each application module's instances. }
        total_wasm_bytes: { type: integer }
    Snapshot:
      type: object
      properties:
        id: { type: string }
        label: { type: string }
        state_hash: { type: string }
        tick: { type: integer }
        bytes: { type: integer }
        created_at: { type: integer }
    EnvironmentConfig:
      type: object
      description: The engine's EnvironmentConfig. Defaults give a desktop actor on the world's first machine.
      properties:
        actor: { type: string, description: The machine user the actor acts as (default the machine's user). }
        machines: { type: array, items: { type: string } }
        actions:
          type: array
          items: { type: string }
          default: [pointer.v1, keyboard.v1, application.v1]
          example: [pointer.v1, keyboard.v1, application.v1, browser.v1]
        observations: { type: array, items: { type: string }, default: [semantic.v1] }
        label: { type: string }
      additionalProperties: true
    Environment:
      type: object
      properties:
        id: { type: string, example: env_1 }
        session: { type: string, description: "The engine's session id (`session-0`)." }
        label: { type: string }
        config: { $ref: "#/components/schemas/EnvironmentConfig" }
        created_at: { type: integer }
        token_prefix: { type: string }
    EnvironmentCreated:
      allOf:
        - $ref: "#/components/schemas/Environment"
        - type: object
          properties:
            token: { type: string, description: "The actor token (`cwa_...`). Shown once." }
    Action:
      type: object
      required: [family, op]
      properties:
        family: { type: string, example: pointer.v1 }
        op: { type: string, example: click }
        machine: { type: string }
        payload: { type: object, example: { x: 640, y: 400, width: 1280, height: 800 } }
    StepResult:
      type: object
      properties:
        outcomes:
          type: array
          items:
            type: object
            properties:
              success: { type: boolean }
              value: {}
              error: { type: [object, "null"] }
              effect: { type: object }
        state_hash: { type: string }
        tick: { type: integer }
        elapsed_ms: { type: number }
    Element:
      type: object
      properties:
        role: { type: string }
        label: { type: string }
        value: { type: string }
        interaction: { type: string, example: "shell:launch:editor" }
        rect: { type: object, properties: { x: { type: integer }, y: { type: integer }, width: { type: integer }, height: { type: integer } } }
        center: { type: array, items: { type: integer } }
        focused: { type: boolean }
        disabled: { type: boolean }
    Gesture:
      type: object
      description: "A procedure gesture (https://computaco.ai/docs/expert-procedures.html) — `{do: click|double_click|drag|scroll|type|key|click_model, target?, text?, key?, at?}`."
      required: [do]
      properties:
        do: { type: string }
        target: { type: object, properties: { role: { type: string }, label: { type: string }, text: { type: string }, interaction: { type: string }, app: { type: string } } }
      additionalProperties: true
    Procedure:
      type: object
      description: An expert procedure (https://computaco.ai/docs/expert-procedures.html).
      required: [id, phases]
      additionalProperties: true
    ProcedureRef:
      type: object
      description: Exactly one of `procedure` (inline) or `ref` (`{world, id}` from the catalog).
      properties:
        procedure: { $ref: "#/components/schemas/Procedure" }
        ref: { type: object, properties: { world: { type: string }, id: { type: string } } }
        seed: { type: integer, default: 1 }
        overrides: { type: object }
    ProcedureInstance:
      type: object
      properties:
        procedure: { $ref: "#/components/schemas/Procedure" }
        seed: { type: integer }
        values: { type: object }
    ProjectOptions:
      type: object
      properties:
        width: { type: integer, default: 1280 }
        height: { type: integer, default: 800 }
        mode: { type: string, enum: [gui, tool], default: gui }
        typing: { type: string, enum: [text, keys], default: text }
    Demonstration:
      type: object
      properties:
        completed: { type: boolean }
        failure: {}
        steps: { type: array, items: { type: object } }
        verification: { $ref: "#/components/schemas/Verification" }
        state_hash: { type: string }
        counts: { type: object }
    Verification:
      type: object
      properties:
        passed: { type: boolean }
        checks: { type: array, items: { type: object, properties: { check: { type: object }, passed: { type: boolean }, detail: {} } } }
    SocietySpec:
      type: object
      description: A society spec (https://computaco.ai/docs/societies.html § The spec).
      additionalProperties: true
    SwarmCreate:
      type: object
      description: Exactly one of `spec` or `ref`. `world` overrides the spec's base world (a built-in name or a blueprint id).
      properties:
        name: { type: string }
        spec: { $ref: "#/components/schemas/SocietySpec" }
        ref: { type: object, properties: { world: { type: string }, id: { type: string } } }
        world: { type: string }
        seed: { type: integer }
        journal:
          description: "Model replies to replay from: `\"builtin\"` (the journal recorded beside a built-in society, with `ref`), the JSON-lines text of a journal file, or a list of `{key, reply}`. A header line is skipped."
          oneOf: [{ type: string }, { type: array, items: { type: object, properties: { key: { type: string }, reply: { type: string } } } }]
    SwarmPlan:
      type: object
      properties:
        id: { type: string }
        world: { type: string }
        seed: { type: integer }
        clock: { type: object, properties: { quantum_us: { type: integer }, rounds: { type: integer }, slot_us: { type: integer }, start: { type: string } } }
        people: { type: array, items: { type: object, properties: { id: { type: string }, population: { type: [string, "null"] }, machine: { type: string }, profile: { type: string }, policy: { type: object }, persona: { type: string }, accounts: { type: object } } } }
        devices: { type: array, items: { type: object } }
        follows: { type: object, description: "{site: [[follower, followee], ...]}" }
        exogenous: { type: array, items: { type: object } }
        arms: { type: array, items: { type: object } }
        fork_at: { type: integer }
        uncompiled: { type: array, items: { type: string } }
    Swarm:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        spec_id: { type: string }
        seed: { type: integer }
        round: { type: integer, description: Rounds completed. }
        rounds: { type: integer, description: The horizon. }
        forked: { type: boolean }
        fork_at: { type: integer, description: The round the arms fork at (matched fork). }
        planned_arms: { type: array, items: { type: string }, description: The design's arms; before the fork `arms` holds only `prefix`. }
        journal: { type: integer, description: Model replies held. }
        arms:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              world: { type: string }
              state_hash: { type: [string, "null"] }
              round: { type: integer }
              metrics: { type: object }
        people: { type: integer }
        created_at: { type: integer }
    SwarmSummary:
      type: object
      properties:
        society: { type: string }
        seed: { type: integer }
        people: { type: integer }
        rounds: { type: integer }
        fork_at: { type: integer }
        preregistration: { type: string, description: SHA-256 of the spec's outcomes and arms. }
        uncompiled: { type: array, items: { type: string } }
        arms:
          type: object
          additionalProperties:
            type: object
            properties:
              outcomes: { type: object, additionalProperties: { type: number }, description: Each outcome metric at the horizon. }
              per_round: { type: object, additionalProperties: { type: object, additionalProperties: { type: number } }, description: "round → metric → value" }
              state_hashes: { type: array, items: { type: string } }
              aligned: { type: boolean, description: Every activation ran at its slot tick. }
              decisions: { type: integer }
              activations: { type: integer }
              actions: { type: integer }
              checks: { type: object, additionalProperties: { type: boolean } }
        effects:
          type: object
          additionalProperties:
            type: object
            properties:
              preregistered: { type: boolean }
              direction: { type: string }
              arms: { type: object, additionalProperties: { type: object, properties: { value: { type: number }, difference: { type: number } } } }
        divergence:
          type: object
          additionalProperties: { type: array, items: { type: object, properties: { round: { type: integer }, same_state: { type: boolean }, screens_differ: { type: integer }, people_reached: { type: integer } } } }
        crn:
          type: object
          additionalProperties: { type: object, properties: { holds: { type: boolean }, same_input: { type: integer }, violations: { type: array, items: { type: object } } } }
      additionalProperties: true
    RoundEnd:
      type: object
      properties:
        arm: { type: string }
        round: { type: integer }
        state_hash: { type: string }
        tick: { type: integer }
        active: { type: array, items: { type: string } }
        decisions: { type: integer }
        metrics: { type: object }
        checks: { type: array, items: { type: object } }
        elapsed_ms: { type: number }
    PendingDecision:
      type: object
      properties:
        arm: { type: string }
        round: { type: integer }
        person: { type: string }
        key: { type: string, description: The journal key the answer must be posted under. }
        messages: { type: array, items: { type: object }, description: The prompt the model would be asked. }
    Metering:
      type: object
      description: >
        What a Computatrum call was charged: its agent-minutes (the plan's windows) and its model
        cost in credits (pay as you go). A credit is $0.001 of model cost at the providers' peak
        list prices.
      properties:
        cost_micro: { type: integer, example: 543, description: Model cost in micro-dollars. }
        credits: { type: number, example: 0.543 }
        billed: { type: string, enum: [plan, payg] }
        ms: { type: integer, example: 2140, description: "The call's own time plus the gap since its task's previous call, at most 15,000 ms (none without a task)." }
        minutes: { type: number, example: 0.04 }
        task: { type: string, description: The task the call was counted in, when it named one. }
        usd: { type: number, example: 0.001358, description: "Pay as you go only: what it adds to the month's spend." }
    AgentWindow:
      type: object
      description: One rolling window, in agent-minutes.
      properties:
        limit: { type: number, example: 120 }
        used: { type: number, example: 31.25 }
        remaining: { type: number, example: 88.75, description: 0 when full. }
        started_at: { type: [integer, "null"], description: "ms; null when no window is running (the next call starts one)." }
        resets_at: { type: [integer, "null"] }
        full: { type: boolean }
        fair_use: { type: boolean, description: "Present (true) when the window is full because its calls' model cost reached the fair-use ceiling, 15 credits for each minute of its limit." }
    Payg:
      type: object
      properties:
        enabled: { type: boolean, description: Requested and its subscription active. }
        requested: { type: boolean }
        subscribed: { type: boolean }
        status: { type: string, enum: [none, active, past_due, canceled] }
        mode: { type: string, enum: [overflow, always] }
        monthly_cap_usd: { type: number, example: 20 }
        period_start: { type: integer, description: The calendar month (UTC) the cap counts. }
        period_end: { type: integer }
        credits: { type: number }
        spend_usd: { type: number }
        unreported_credits: { type: number, description: Not yet sent to Stripe's meter (whole credits are sent each minute). }
    AgentUsage:
      type: object
      properties:
        plan: { type: string }
        unit:
          type: object
          properties:
            name: { type: string, example: minute, description: The windows' unit. }
            task_gap_cap_s: { type: number, example: 15 }
            payg: { type: object, properties: { name: { type: string, example: credit }, model_cost_usd: { type: number, example: 0.001 }, price_usd: { type: number, example: 0.0025 } } }
        windows: { type: object, properties: { five_hour: { $ref: "#/components/schemas/AgentWindow" }, weekly: { $ref: "#/components/schemas/AgentWindow" } } }
        payg: { $ref: "#/components/schemas/Payg" }
        last_30_days:
          type: array
          items:
            type: object
            properties:
              kind: { type: string, enum: [chat, vision] }
              model: { type: string }
              calls: { type: integer }
              tokens_in: { type: integer }
              tokens_cached: { type: integer }
              tokens_out: { type: integer }
              images: { type: integer }
              minutes: { type: number }
              credits: { type: number }
              payg_credits: { type: number }
