Immersive Commons Research API
Query the research RAG corpus.
Query the research RAG corpus.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
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.curl "https://apis.io/api/v1/apis/immersivecommons-com:immersivecommons-com-research-api"
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.
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: 3.2.0
info:
title: Immersive Commons — Agent REST Research 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: Research
description: Query the research RAG corpus.
paths:
/api/research/ask:
post:
operationId: askResearch
x-sandbox: real
tags:
- Research
summary: Query the research RAG corpus
description: Clerk session or agt_ token with `research:query`. Forwards a top-k retrieval (optionally LLM-synthesized) query to the supercommons2 RAG funnel and returns the results. The query text is never logged. 60 requests/hour/member.
security:
- agentBearer:
- research:query
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ResearchAskRequest'
responses:
'200':
description: Retrieval results (or a synthesized answer).
content:
application/json:
schema:
$ref: '#/components/schemas/ResearchAskResponse'
'400':
description: Bad JSON / missing or oversized q / invalid k / invalid sources / invalid model.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: No auth.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Scope/tier not authorized.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Per-member hourly rate budget exceeded.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'502':
description: Upstream RAG funnel error.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: RAG funnel not configured / rate limiter unavailable.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'504':
description: Upstream RAG funnel timeout.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
x-ic-route:
file: app/api/research/ask/route.ts
method: POST
/api/presentations/ask:
post:
operationId: askPresentations
tags:
- Research
summary: Retrieval over the public talk archive
description: PUBLIC and retrieval-only, unlike POST /api/research/ask which is member-gated. Returns matching talk slides with deep links; there is deliberately NO model synthesis here, because that is the costly and abusable part. Bounded by a per-IP hourly budget that fails CLOSED, so a caller who can stall the counter cannot flood the corpus service.
security:
- {}
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PresentationsAskRequest'
responses:
'200':
description: Matching slides with deep links.
content:
application/json:
schema:
$ref: '#/components/schemas/PresentationsAskResponse'
'400':
description: Missing `q`, or over the 500-character ceiling.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Hourly IP budget exhausted.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'502':
description: The retrieval service failed.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: Retrieval is not configured.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'504':
description: The retrieval service timed out.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
x-ic-route:
file: app/api/presentations/ask/route.ts
method: POST
/api/x402/research/ask:
post:
operationId: askResearchOverX402
x-sandbox: real
tags:
- Research
summary: Pay-per-query twin of the research RAG read
description: 'The metered door onto the SAME corpus members query free at POST /api/research/ask — for agents with no IC membership. No token: a bare POST answers 402, and a re-send carrying a signed `X-PAYMENT` header buys one query. The answer is withheld if settlement fails (502) rather than given away. Returns 404 while X402_ENABLED is off. Members should keep using the free route.'
security:
- {}
parameters:
- name: X-PAYMENT
in: header
required: false
schema:
type: string
description: The buyer-signed x402 payment authorization. Omit it to receive the 402 challenge.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/X402ResearchAskRequest'
responses:
'200':
description: The retrieval results, marked with a `via` of x402.
content:
application/json:
schema:
$ref: '#/components/schemas/X402ResearchAskResponse'
'400':
description: Bad JSON, a missing/oversized q, an invalid k, an unknown source, or a model without synthesize.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'402':
description: Payment required — the body carries the price, payTo and asset.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: The paid door is disabled (X402_ENABLED is not true).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Per-payer query budget exceeded.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'502':
description: The answer was produced but settlement failed, so it is withheld.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: The RAG funnel, the payment rails, or the rate limiter is unavailable.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
x-ic-route:
file: app/api/x402/research/ask/route.ts
method: POST
/api/research/submissions/pending:
get:
operationId: listPendingResearchSubmissions
tags:
- Research
summary: Read the pending research-submission queue
description: Clerk operator session only (NOT reachable with an agt_ bearer). Returns every pending research-RAG submission, newest first — the envelopes the MCP tool `ic_research_submit` writes. Ids whose envelope is already gone are filtered out, so a half-committed approve/deny leaves no ghost row in the queue rather than a record that cannot be acted on.
security:
- clerkSession: []
responses:
'200':
description: The live pending queue.
content:
application/json:
schema:
$ref: '#/components/schemas/ResearchSubmissionQueueResponse'
'401':
description: Not signed in.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Not operator-tier.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: KV read failed.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
x-ic-route:
file: app/api/research/submissions/pending/route.ts
method: GET
/api/research/submissions/{id}/approve:
post:
operationId: approveResearchSubmission
tags:
- Research
summary: Approve a pending research submission
description: Clerk operator session only. Moves the submission into the approved bucket with `approved_at` + `approved_by`, deletes the pending key, and drops the id from the queue index. The sc2-side ingest worker scans that bucket and pulls the URL in to embed, so approving is what actually feeds the corpus.
security:
- clerkSession: []
parameters:
- name: id
in: path
required: true
schema:
type: string
description: The pending submission id.
responses:
'200':
description: Approved.
content:
application/json:
schema:
$ref: '#/components/schemas/ResearchSubmissionDecisionResponse'
'400':
description: Missing id.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Not signed in.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Not operator-tier.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: No such pending submission.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: KV read or write failed.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
x-ic-route:
file: app/api/research/submissions/[id]/approve/route.ts
method: POST
/api/research/submissions/{id}/deny:
post:
operationId: denyResearchSubmission
tags:
- Research
summary: Deny a pending research submission
description: 'Clerk operator session only. The mirror of its approve sibling: writes the record into the denied bucket with `denied_at` + `denied_by` (and the optional reason), then clears the pending key and the queue index entry. The body is OPTIONAL — an operator may deny an obvious item without commentary.'
security:
- clerkSession: []
parameters:
- name: id
in: path
required: true
schema:
type: string
description: The pending submission id.
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/ResearchSubmissionDenyRequest'
responses:
'200':
description: Denied.
content:
application/json:
schema:
$ref: '#/components/schemas/ResearchSubmissionDecisionResponse'
'400':
description: Missing id, or an unparseable body.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Not signed in.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Not operator-tier.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: No such pending submission.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: KV read or write failed.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
x-ic-route:
file: app/api/research/submissions/[id]/deny/route.ts
method: POST
components:
schemas:
PresentationsAskResponse:
type: object
required:
- ok
- q
- count
- results
properties:
ok:
type: boolean
q:
type: string
count:
type: integer
results:
type: array
items:
type: object
additionalProperties: true
properties:
paper_id:
type: string
slug:
type: string
talk_title:
type: string
series:
type: string
speaker:
type: string
similarity:
type: number
slide:
type:
- integer
- 'null'
snippet:
type: string
deep_link:
type: string
description: 'Deck URL with a #slide anchor when the chunk carries a slide number.'
detail_link:
type: string
description: Retrieval only — there is deliberately no model synthesis on this surface.
via:
type: string
PresentationsAskRequest:
type: object
required:
- q
properties:
q:
type: string
description: At most 500 characters.
k:
type: integer
description: How many slides to return. 3-25, default 10.
X402ResearchAskResponse:
type: object
description: The same payload the free member route returns, plus `via`.
required:
- via
additionalProperties: true
properties:
via:
type: string
description: Always x402.
results:
type: array
items:
type: object
additionalProperties: true
description: Retrieval hits, when the upstream returned a bare list.
ResearchAskResponse:
type: object
description: Passthrough from the RAG funnel. `/search` returns { results, via }; `/synthesize` returns { q, k, model, answer, citations, retrieval_results, usage, via }.
additionalProperties: true
properties:
results:
type: array
items:
type: object
additionalProperties: true
answer:
type: string
citations:
type: array
items:
type: object
additionalProperties: true
via:
type: string
ResearchSubmissionDenyRequest:
type: object
description: Optional — a deny with no body is valid.
properties:
reason:
type: string
description: Free text, truncated server-side. Stored on the denied record.
ResearchSubmissionQueueResponse:
type: object
required:
- ok
- submissions
properties:
ok:
type: boolean
submissions:
type: array
items:
$ref: '#/components/schemas/ResearchSubmission'
description: Newest first. Empty is a normal state, not an error.
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).
ResearchAskRequest:
type: object
required:
- q
properties:
q:
type: string
maxLength: 500
description: The query.
k:
type: integer
minimum: 1
maximum: 50
default: 10
sources:
type: array
items:
type: string
enum:
- paper
- book
default:
- paper
synthesize:
type: boolean
default: false
description: When true, returns an LLM-synthesized grounded answer.
model:
type: string
enum:
- claude-haiku-4-5
- claude-sonnet-4-6
- claude-opus-4-7
description: Only valid when synthesize=true.
X402ResearchAskRequest:
type: object
required:
- q
properties:
q:
type: string
description: The query. Never logged.
k:
type: integer
description: Top-k retrieval depth.
sources:
type: array
items:
type: string
enum:
- paper
- book
description: Which corpora to search.
synthesize:
type: boolean
description: Ask for an LLM-written answer over the hits instead of the hits alone.
model:
type: string
description: 'Only valid with synthesize=true. One of: claude-haiku-4-5.'
ResearchSubmission:
type: object
description: A member-submitted URL awaiting operator review before it is embedded into the research corpus.
required:
- id
- clerk_user_id
- url
- submitted_at
- status
properties:
id:
type: string
description: Submission id; the path parameter of the approve/deny routes.
clerk_user_id:
type: string
description: Who submitted it.
url:
type: string
description: The source to embed.
note:
type:
- string
- 'null'
description: Optional submitter note.
submitted_at:
type: string
format: date-time
status:
type: string
description: '`pending` while it sits in this queue.'
ResearchSubmissionDecisionResponse:
type: object
required:
- ok
- id
- status
properties:
ok:
type: boolean
id:
type: string
description: The submission that was decided.
status:
type: string
enum:
- approved
- denied
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