> ## Documentation Index
> Fetch the complete documentation index at: https://immersivecommons.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Run up to 20 public GET reads in one request

> Public, no auth. Submit a bounded list (1..20) of GET sub-requests to the allowlisted PUBLIC read endpoints (`/api/events/upcoming`, `/api/events/get`, `/api/floor10/donations`) and get their results back in order. Each sub-request is dispatched to the SAME handler that serves it directly, so a batched result is identical to calling that endpoint alone. Writes and auth-gated reads are deliberately NOT batchable (they keep their own idempotency + per-token rate accounting). Partial-failure semantics: a rejected sub-request (non-GET, non-allowlisted path, handler error) carries a 4xx/5xx status inside its own result while the batch envelope stays 200; only a malformed batch envelope is a 400 on the whole call.



## OpenAPI

````yaml /openapi.json post /api/batch
openapi: 3.1.1
info:
  title: Immersive Commons — Agent REST API
  version: '2026-07-16'
  summary: The REST surface an AI agent uses to act at Immersive Commons.
  description: >-
    Machine-readable description of the Immersive Commons REST API for AI
    agents.


    **Authentication.** Most operations take a Bearer agent token
    (`Authorization: Bearer agt_...`). Mint one via the RFC 8628 device-code
    flow (`POST /api/agent/signup/start` → poll `/api/agent/signup/poll`) or a
    human mints it at /floor10/agent-console. Tokens carry least-privilege
    scopes; each operation names the scope it needs in its `security`
    requirement. Public reads allow anonymous access (empty security
    requirement). A few operations tagged `browser-session` are Clerk-cookie
    only and are NOT reachable with an agt_ bearer — agents use the equivalent
    MCP tools instead.


    **Error model.** Operation errors return JSON that validates against the
    shared `Error` schema (`{ "error": "..." }`, optionally
    `ok:false`/`error_kind`/`rate`/`retry_after_seconds`). Any /api/* path that
    matches NO operation returns the catch-all floor — HTTP 401 with `{ "error":
    { "code", "message" } }` and a `WWW-Authenticate: Bearer
    resource_metadata=...` header pointing at
    /.well-known/oauth-protected-resource.


    **Idempotency.** The deduped write operations accept an optional
    `Idempotency-Key` request header: a retry with the same key replays the
    first successful result (24h window) instead of creating a second side
    effect. This layers on top of each endpoint's own semantic dedupe (e.g.
    RSVP's 7-day window).


    **Versioning & deprecation policy.** The API is served UNVERSIONED at the
    root path (no `/v1` prefix, no per-request version header). The dated
    `info.version` (CalVer) is the change marker to pin against. No operation is
    deprecated today; when one is scheduled for removal it will be marked
    `deprecated: true` here and carry RFC 8594 `Deprecation` + `Sunset` response
    headers, announced at least 90 days ahead via /developers and llms.txt.
    Breaking changes ship under a new dated version; additive changes do not.


    **Rate limits.** Agent tokens are limited per-token per endpoint family
    (e.g. 3 highlight submissions/day, 60 research queries/hour). 429 responses
    carry a `Retry-After` header. Responses carry the RFC RateLimit headers
    (draft-ietf-httpapi-ratelimit-headers: `RateLimit` + `RateLimit-Policy` in
    RFC 9651 Structured Fields syntax, plus the legacy discrete
    `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset`) so callers
    can self-throttle. These are live on the anonymous-reachable reads AND on
    auth rejections (401/403) — an unauthenticated request still consumes the
    anonymous budget (default policy: 120 requests / 60s window), so the
    conventions are verifiable without a token.
  contact:
    name: Immersive Commons
    email: admin@immersivecommons.com
    url: https://www.immersivecommons.com/developers
  license:
    name: Proprietary
    url: https://www.immersivecommons.com/privacy
servers:
  - url: https://www.immersivecommons.com
    description: Production
  - url: https://www.immersivecommons.com
    description: >-
      Sandbox (test mode) — same base URL; mint a sandbox token via
      `sandbox:true` on /api/agent/signup/start. Writes return simulated
      receipts, reads serve real data.
    x-sandbox: token-flag
security:
  - {}
tags:
  - name: events
    description: Discover and RSVP to events; propose member events.
  - name: batch
    description: Fan out several public GET reads in one request.
  - name: directory
    description: Search the member directory.
  - name: resources
    description: List and book rooms / printers.
  - name: account
    description: The caller's own activity log.
  - name: leaderboard
    description: The public commit leaderboard.
  - name: research
    description: Query the research RAG corpus.
  - name: highlights
    description: Submit event highlights for moderation.
  - name: feedback
    description: File feedback / breakage reports.
  - name: auth
    description: Token mint (device code), self-revoke, setup checks.
  - name: payments
    description: x402 machine payments (donor wall).
  - name: membership
    description: Membership tier (browser-session).
  - name: browser-session
    description: Clerk-cookie only — NOT reachable with an agt_ bearer.
paths:
  /api/batch:
    post:
      tags:
        - batch
      summary: Run up to 20 public GET reads in one request
      description: >-
        Public, no auth. Submit a bounded list (1..20) of GET sub-requests to
        the allowlisted PUBLIC read endpoints (`/api/events/upcoming`,
        `/api/events/get`, `/api/floor10/donations`) and get their results back
        in order. Each sub-request is dispatched to the SAME handler that serves
        it directly, so a batched result is identical to calling that endpoint
        alone. Writes and auth-gated reads are deliberately NOT batchable (they
        keep their own idempotency + per-token rate accounting). Partial-failure
        semantics: a rejected sub-request (non-GET, non-allowlisted path,
        handler error) carries a 4xx/5xx status inside its own result while the
        batch envelope stays 200; only a malformed batch envelope is a 400 on
        the whole call.
      operationId: batchPublicReads
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchRequest'
      responses:
        '200':
          description: Per-sub-request results, in the same order as `requests`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResponse'
        '400':
          description: >-
            Malformed batch envelope: bad JSON, wrong shape, empty, or more than
            20 sub-requests.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '413':
          description: Batch body exceeds 16KB.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - {}
components:
  schemas:
    BatchRequest:
      type: object
      required:
        - requests
      description: >-
        A bounded list (1..20) of public GET sub-requests to run in one
        round-trip.
      properties:
        requests:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/BatchSubRequest'
    BatchResponse:
      type: object
      required:
        - ok
        - count
        - results
      properties:
        ok:
          type: boolean
        count:
          type: integer
          description: Number of results (equals requests.length).
        results:
          type: array
          items:
            $ref: '#/components/schemas/BatchResultItem'
    Error:
      type: object
      required:
        - error
      additionalProperties: true
      description: >-
        Structured JSON error. `error` (string) is always present — an error
        code or a human-readable message. `ok:false` is present on bearer
        auth-gate rejections. Endpoints may attach additional context keys to
        aid recovery (tier, current_tier, limit, window_seconds, valid_kinds,
        recovery_hint, detail, example, ...); the reusable ones are named below.
      properties:
        error:
          type: string
          description: Error code or human-readable message (always present).
        ok:
          type: boolean
          enum:
            - false
          description: Present (false) on auth-gate rejections.
        error_kind:
          type: string
          description: >-
            Machine-readable failure class (rate_limit, validation, not_found,
            ...).
        message:
          type: string
          description: Longer human-readable detail (feedback + some rate-limit errors).
        rate:
          $ref: '#/components/schemas/RateInfo'
        retry_after_seconds:
          type: integer
          description: Seconds to wait before retrying (on 429).
        tier:
          type: string
          description: >-
            The caller's current tier — present on a tier/scope authorization
            (403) rejection.
        current_tier:
          type: string
          description: >-
            The caller's current tier — present on a tier/request validation
            (400) rejection.
        detail:
          type: string
          description: >-
            Extra failure detail — present on some 500 responses (e.g. the agent
            event-log read).
    BatchSubRequest:
      type: object
      required:
        - path
      description: One GET sub-request in a batch.
      properties:
        id:
          type: string
          description: >-
            Optional caller correlation id; echoed back verbatim on the matching
            result.
        method:
          type: string
          enum:
            - GET
          default: GET
          description: Only GET sub-requests are batchable.
        path:
          type: string
          description: >-
            An allowlisted public /api path, optionally with a query string
            (e.g. /api/events/upcoming?limit=3). Allowed: /api/events/upcoming,
            /api/events/get, /api/floor10/donations.
    BatchResultItem:
      type: object
      required:
        - path
        - status
        - ok
        - body
      description: >-
        One sub-request's outcome. `status`/`ok`/`body` are exactly what the
        target endpoint would have returned; a rejected sub-request
        (non-allowlisted path, non-GET, or handler error) carries a 4xx/5xx
        status + an { error } body while the batch envelope stays 200.
      additionalProperties: true
      properties:
        id:
          type: string
          description: Echoed from the sub-request if one was supplied.
        path:
          type: string
          description: The sub-request path as sent.
        status:
          type: integer
          description: >-
            HTTP status the target handler returned (or a 4xx/5xx synthesized
            for a rejected sub-request).
        ok:
          type: boolean
          description: True iff status is 2xx.
        body:
          type: object
          additionalProperties: true
          description: >-
            The target endpoint's JSON body (or an { error } object on a
            rejected sub-request).
    RateInfo:
      type: object
      description: Per-token rate-limit snapshot returned by write endpoints.
      properties:
        current:
          type: integer
        remaining:
          type: integer
        limit:
          type: integer

````