Immersive Commons Hack API

Hackathon: applications, the venue NDA, and the event photo gallery.

GitHub OpenAPI

Operations 6

GET /api/hack/apply Read the question catalog and the caller's own application #
POST /api/hack/apply File or update a hackathon application #
POST /api/hack/nda Record your own venue NDA signature #
GET /api/hack/photos List an event's photo gallery #
POST /api/hack/photos Hand in a photo you took at the event #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/immersivecommons-com:immersivecommons-com-hack-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

immersivecommons-com-hack-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Immersive Commons — Agent REST Hack API
  version: '2026-09-15'
  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. An operation scheduled for removal is marked `deprecated: true` here, announced via /developers and llms.txt, and kept at least one release. Deprecated today (2026-09-15, the IC Credits rename): the seven `/api/zai-keys/*` paths, each a byte-identical re-export of its `/api/credits/*` twin (same handler, auth, body and response), so moving is a path change and nothing else. 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: hack
  description: 'Hackathon: applications, the venue NDA, and the event photo gallery.'
paths:
  /api/hack/apply:
    get:
      operationId: getHackApplicationForm
      tags:
      - hack
      summary: Read the question catalog and the caller's own application
      description: Clerk session or agt_ token with `hack:read`. The catalog is the identical object the MCP tool `ic_hack_application_form` returns, so the rendered form and the agent-facing form cannot disagree about what is asked or what is required. Also returns the caller's roster row when they have one, because NDA state lives THERE and not on the application. `open` is computed server-side so the button and the POST cannot disagree about whether the door is open.
      security:
      - clerkSession: []
      - agentBearer:
        - hack:read
      parameters:
      - name: eid
        in: query
        required: false
        schema:
          type: string
        description: Event id. Defaults to the current hackathon.
      - name: type
        in: query
        required: false
        schema:
          type: string
        description: Applicant type. Defaults to `founder`; the valid set is echoed back in `types`.
      responses:
        '200':
          description: Phase, form, and the caller's own application if any.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HackApplyFormResponse'
        '400':
          description: Unknown applicant type.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: No auth.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-ic-route:
        file: app/api/hack/apply/route.ts
        method: GET
    post:
      operationId: submitHackApplication
      tags:
      - hack
      summary: File or update a hackathon application
      description: Clerk session or agt_ token with `hack:register` (live tier re-checked). Calls the SAME `apply()` as the MCP tool, keyed by the SAME member id, so an application filed here is indistinguishable to the reviewer from one an agent filed. Re-posting while `applied` updates in place. A token with no tied Immersive Commons identity is refused 401 - there is nobody to file the application as.
      security:
      - clerkSession: []
      - agentBearer:
        - hack:register
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HackApplyRequest'
      responses:
        '200':
          description: The stored application.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HackApplyResponse'
        '400':
          description: Invalid JSON, unknown applicant type, or answers that fail validation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: No auth, or a token with no tied member identity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Scope/tier not authorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: The application phase is not OPEN.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '413':
          description: Body over the ceiling.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-ic-route:
        file: app/api/hack/apply/route.ts
        method: POST
  /api/hack/nda:
    post:
      operationId: signHackNda
      tags:
      - hack
      summary: Record your own venue NDA signature
      description: 'Clerk session or agt_ token with `hack:register`. SELF-ONLY on purpose: the underlying function takes a member id and would happily sign for anyone, so every exposed path pins that argument to the authenticated caller - an operator ''helpfully'' recording someone else''s NDA is the one thing the venue''s requirement cannot survive. This RECORDS the attestation; the signature itself happens in the venue''s own flow. Idempotent.'
      security:
      - clerkSession: []
      - agentBearer:
        - hack:register
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HackNdaRequest'
      responses:
        '200':
          description: The recorded signature timestamp.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HackNdaResponse'
        '401':
          description: No auth, or no resolvable member identity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Not on the event roster.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-ic-route:
        file: app/api/hack/nda/route.ts
        method: POST
  /api/hack/photos:
    get:
      operationId: listHackPhotos
      tags:
      - hack
      summary: List an event's photo gallery
      description: 'Either a partner `?grant=` link (published photos only, no account needed) or a Clerk session / agt_ token with `hack:read`, after which the live roster decides: staff see everything in every state, an attendee sees published plus their own. Records are the public projection only - a blob URL never crosses this boundary; bytes come from the separate download route.'
      security:
      - {}
      - clerkSession: []
      - agentBearer:
        - hack:read
      parameters:
      - name: eid
        in: query
        required: false
        schema:
          type: string
        description: Event id. Defaults to the current hackathon.
      - name: grant
        in: query
        required: false
        schema:
          type: string
        description: A signed, expiring partner gallery token. Admits to published photos only.
      responses:
        '200':
          description: The visible photos. `also_visible_to_you` carries the caller's own non-published rows when there are any.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HackPhotosListResponse'
        '403':
          description: No roster access and no valid grant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-ic-route:
        file: app/api/hack/photos/route.ts
        method: GET
    post:
      operationId: submitHackPhoto
      tags:
      - hack
      summary: Hand in a photo you took at the event
      description: 'Clerk session or agt_ token with `hack:submit` - the scope every builder at the event already holds, deliberately reused because scopes freeze at mint and a new one would be reachable by nobody. Lands `pending`: an organiser reviews submissions before they appear in the gallery.'
      security:
      - clerkSession: []
      - agentBearer:
        - hack:submit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HackPhotoSubmitRequest'
      responses:
        '200':
          description: The stored (pending) record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HackPhotoSubmitResponse'
        '400':
          description: Invalid JSON, missing `image_base64`, or bytes that do not decode.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: No auth, or no member identity to attribute the photo to.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '413':
          description: Image over the size ceiling.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '415':
          description: '`content_type` is not an accepted image type.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-ic-route:
        file: app/api/hack/photos/route.ts
        method: POST
  /api/hack/photos/grant:
    post:
      operationId: grantHackGalleryLink
      tags:
      - hack
      summary: Mint a partner link to one event's gallery
      description: Organiser-only (`hack:manage` plus a live organizer/event_admin role). A grant is a bearer credential to real people's faces, so minting one is a decision a person makes and not something an attendee can do for themselves. The token carries no identity and cannot be revoked individually - `subject` is recorded IN it so a leaked link can be traced back to who it was minted for, and the TTL is the actual containment (default 14 days, ceiling 90).
      security:
      - clerkSession: []
      - agentBearer:
        - hack:manage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HackPhotoGrantRequest'
      responses:
        '200':
          description: The minted link and its expiry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HackPhotoGrantResponse'
        '400':
          description: Missing `subject`, or a ttl over the ceiling.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Not an organiser for this event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Gallery links are not configured (no signing key).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-ic-route:
        file: app/api/hack/photos/grant/route.ts
        method: POST
components:
  schemas:
    HackPhotoSubmitResponse:
      type: object
      required:
      - ok
      - photo
      properties:
        ok:
          type: boolean
        photo:
          $ref: '#/components/schemas/HackPhotoPublic'
        note:
          type: string
          description: Says plainly that an organiser reviews submissions before they appear.
    HackPhotoPublic:
      type: object
      additionalProperties: true
      description: The public projection of a gallery photo. Deliberately carries NO blob URL — bytes come from the separate download route.
      properties:
        id:
          type: string
        eid:
          type: string
        state:
          type: string
          description: pending | published | rejected.
        caption:
          type: string
        credit:
          type: string
        submitted_at:
          type: string
          format: date-time
    HackApplyRequest:
      type: object
      required:
      - applicant_type
      - answers
      properties:
        eid:
          type: string
          description: Event id. Defaults to the current hackathon.
        applicant_type:
          type: string
          description: One of the `types` returned by the GET.
        answers:
          type: object
          additionalProperties: true
          description: Keyed by the question ids in `form`. Validated by the same function the MCP path uses.
    HackPhotoSubmitRequest:
      type: object
      required:
      - image_base64
      properties:
        eid:
          type: string
          description: Event id. Defaults to the current hackathon.
        image_base64:
          type: string
          description: Base64 bytes. A `data:` URL is accepted and unwrapped, but it must be base64-encoded.
        content_type:
          type: string
          description: Declared image type. Checked against the accepted set.
        filename:
          type: string
        caption:
          type: string
        credit:
          type: string
    HackPhotosListResponse:
      type: object
      required:
      - ok
      - eid
      - count
      - photos
      properties:
        ok:
          type: boolean
        eid:
          type: string
        via:
          type: string
          description: 'How the caller was admitted: staff | attendee | grant.'
        count:
          type: integer
        photos:
          type: array
          items:
            $ref: '#/components/schemas/HackPhotoPublic'
        also_visible_to_you:
          type: array
          items:
            $ref: '#/components/schemas/HackPhotoPublic'
          description: Present only when the caller can see rows beyond the published set (their own, or staff visibility).
    HackApplyResponse:
      type: object
      required:
      - ok
      - application
      properties:
        ok:
          type: boolean
        application:
          type: object
          additionalProperties: true
          description: The stored application. Re-posting while applied updates it in place.
    HackApplyFormResponse:
      type: object
      required:
      - ok
      - eid
      - phase
      - open
      - types
      - form
      properties:
        ok:
          type: boolean
        eid:
          type: string
        phase:
          type: string
          description: PRE | OPEN | ... — the door is open only at OPEN.
        nda_required:
          type: boolean
        roster:
          type:
          - object
          - 'null'
          additionalProperties: true
          description: The caller's roster row when they have one. NDA state lives HERE, not on the application.
          properties:
            roles:
              type: array
              items:
                type: string
            nda_signed_at:
              type:
              - string
              - 'null'
              format: date-time
            checked_in_at:
              type:
              - string
              - 'null'
              format: date-time
        open:
          type: boolean
          description: Computed server-side so the button and the POST cannot disagree.
        types:
          type: array
          items:
            type: string
          description: The valid applicant types.
        form:
          type: object
          additionalProperties: true
          description: The question catalog — the identical object the MCP form tool returns.
        application:
          type:
          - object
          - 'null'
          additionalProperties: true
          description: The caller's own application, or null.
        via:
          type: string
          enum:
          - clerk
          - agent
    HackNdaResponse:
      type: object
      required:
      - ok
      - nda_signed_at
      properties:
        ok:
          type: boolean
        nda_signed_at:
          type: string
          format: date-time
          description: Idempotent — a repeat returns the first timestamp.
    HackPhotoGrantRequest:
      type: object
      required:
      - subject
      properties:
        eid:
          type: string
          description: Event id. Defaults to the current hackathon.
        subject:
          type: string
          description: Who the link is for. Recorded IN the token so a leaked link can be traced back.
        ttl_days:
          type: integer
          description: Default 14, ceiling 90. The TTL is the actual containment — a grant cannot be revoked individually.
    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).
    HackPhotoGrantResponse:
      type: object
      required:
      - ok
      - eid
      - subject
      - expires_at
      - url
      properties:
        ok:
          type: boolean
        eid:
          type: string
        subject:
          type: string
        expires_at:
          type: string
          format: date-time
        url:
          type: string
          description: The gallery link to hand over. It carries the bearer token in its query string.
    HackNdaRequest:
      type: object
      properties:
        eid:
          type: string
          description: Event id. Defaults to the current hackathon.
    RateInfo:
      type: object
      description: Per-token rate-limit snapshot returned by write endpoints.
      properties:
        current:
          type: integer
        remaining:
          type: integer
        limit:
          type: integer
  securitySchemes:
    agentBearer:
      type: http
      scheme: bearer
      bearerFormat: agt_<base64url>
      description: 'Per-member, individually-revocable agent token minted via the device-code flow. Send as `Authorization: Bearer agt_...`. The scope array in each operation''s security requirement names the IC scope the handler enforces.'
    clerkSession:
      type: apiKey
      in: cookie
      name: __session
      description: Browser Clerk session cookie. Operations under this scheme are reachable ONLY from a signed-in browser session, NOT with an agt_ bearer token. Agents use the equivalent MCP tools (e.g. ic_get_my_membership, ic_request_tier).
    workshopCode:
      type: http
      scheme: bearer
      bearerFormat: 6-char enrollment code
      description: 'The workshop''s own enrollment code, minted by POST /api/workshop/enroll and presented as a bearer. It is NOT an agt_ token: it carries no scopes, is not revocable per-token, and reaches only the /api/workshop/* routes.'
    sliceServiceSecret:
      type: http
      scheme: bearer
      bearerFormat: shared secret
      description: SLICE_SERVICE_SECRET — the machine-to-machine bearer the print-slicing service presents. A distinct trust boundary (an external machine on the tailnet), which is why it has its own secret rather than reusing the cron one. No member holds it and no member-facing flow can reach these routes.
    floorcastOnboarding:
      type: http
      scheme: bearer
      bearerFormat: fco_<...>
      description: 'A single-use Floorcast onboarding token. It exists because tenant provisioning has a chicken-and-egg problem: no operator exists yet for a floor that does not exist yet, so neither a Clerk session nor an agt_ token can authorize the call. Minting one is a concierge step with no live route, and the token is consumed by the provision it authorizes.'
x-payment-info:
  protocol: x402
  discovery: https://www.immersivecommons.com/discovery/resources
  donor_wall: https://www.immersivecommons.com/api/floor10/donations
  note: Payable resources are advertised at /discovery/resources; a bare request to a payable resource returns HTTP 402 with the PAYMENT-REQUIRED challenge.
x-api-lifecycle:
  versioning: unversioned-root (info.version is a dated CalVer change marker)
  deprecation: 'deprecated operations are flagged `deprecated: true` here and kept at least one release; the /api/zai-keys/* aliases (2026-09-15) are the current set'
x-sandbox:
  summary: 'A sandbox token (mint by adding "sandbox": true to POST /api/agent/signup/start) is a real token with real scopes whose WRITE operations return a simulated receipt instead of mutating; reads serve real data. Same base URL + bearer auth. Immutable after mint.'
  obtain:
    endpoint: POST /api/agent/signup/start
    body:
      sandbox: true
  write_response_shape:
    ok: true
    sandbox: true
    simulated: true
    would_have:
      action: string
      scope: string
      args: object
    note: string — scope was checked; deeper business rules (tier/role/ownership/signature) are NOT evaluated in sandbox, and no production state changed. A green receipt means well-formed + scoped, NOT guaranteed to pass in production.
  real_exceptions:
  - POST /api/agent/feedback (bug-report channel must be heard)
  - POST /api/agent/token/revoke (token lifecycle; genuinely revokes)
  not_sandbox_eligible:
  - ic_donate (x402 payment rail — never faked; not a REST operation in this spec)
  - floor-admin toggles + tools with no concrete scope to check — denied fail-closed, never silently simulated