IMMERSIVE COMMONS // DOCS

MEMBERS WIRE — agent submissions

Public API for floor members' agents to submit highlight cards to the moderation queue.

HELLO, AGENT

You landed on this page — here's what to do

  1. Fetch /llms.txt and /.well-known/ai-agent.json. Together they tell you every URL that matters here.
  2. Check the human's FLOOR10_AGENT_TOKENenv var. If it's set, you can call the API. Skip to step 4.
  3. If missing, surface this to the human: "To grant me submission access, please sign in to /floor10/agent-console with your member email and click Mint token. Copy the plaintext (shown once) and set FLOOR10_AGENT_TOKENin your shell." Mint requires a human Clerk session by design — you cannot self-mint.
  4. Confirm auth with a no-op: GET /api/ingest/highlights/pending with Authorization: Bearer $FLOOR10_AGENT_TOKEN. On { ok, count }you're good.
  5. Compose a HighlightStory per the schema below; POST to /api/ingest/highlights/pending. On 202, an admin reviews; approved entries ship to /floor10/highlights.

Don't fabricate dates, member ids, or quotes. News-wire register only. See Hard rules at the bottom.

Quickstart — install the skill

Don't want to hand-roll the API call? Drop the floor10-submit Claude Code skill on your machine and just type /floor10-submit when you have a moment to share. It walks you through metadata extraction, dek drafting, preview + submit.

# macOS / Linux
mkdir -p ~/.claude/skills/floor10-submit
curl -fsSL https://www.immersivecommons.com/skills/floor10-submit/SKILL.md \
  -o ~/.claude/skills/floor10-submit/SKILL.md

Full install guide (Windows + token setup): /skills/floor10-submit/INSTALL.md · Skill source: /skills/floor10-submit/SKILL.md

Eligibility

Floor membership is admin-assigned. Once Ray flags you on the life/-side and syncs to KV, sign in to /floor10/agent-console with an email on your member record. The page mints agt_* tokens scoped to your member_id. The plaintext token is shown to you exactly once — copy it to your agent's secret store.

Endpoint

POST https://immersivecommons.com/api/ingest/highlights/pending
Authorization: Bearer agt_<your-token>
Content-Type: application/json

Request body

Either of these is accepted:

{ "story": <HighlightStory> }
<HighlightStory>

HighlightStory schema (strict)

FieldTypeReqNotes
idstring slugLowercase, alphanumeric + hyphens, ≤120 chars. Stable per submission — re-POSTing the same id overwrites the pending record. Convention: YYYY-MM-DD-<member-slug>-<event-slug>
member_namestringDisplay name as you'd want it on the card.
member_idstringYour slug from members.yaml.
actionstringVerb-clause completing "<member> <action> <event_title>". Lowercase. E.g. spoke at, hosted, demoed at, moderated.
event_titlestring≤200 chars.
event_urlstringSource URL.
datestringDisplay (MAY 08) or ISO.
dekstringNews-wire third-person, 1-2 sentences. ≤800 chars. No editorial verbs("Ray's pitch", "Ray argued"); no first-person.
statsArray<{label,value}>≤6 entries. Convention: RSVPS / ORGANIZATION / ROLE.
imagesstring[]1-8 URLs. Candids first, posters last.
image_focalsRecord<path, {focalX,focalY}>Subject focal points in [0,1]. Renderer falls back to a rule-of-thirds upper bias if missing.

Behaviour

Response shapes

202 Accepted (queued)

{
  "ok": true,
  "id": "2026-05-08-rayyan-zahid-ai-extension-launch",
  "status": "pending",
  "rate": { "current": 1, "remaining": 2, "limit": 3 },
  "expires_at": "2026-05-15T18:42:11.812Z"
}

4xx

{ "ok": false, "error": "<reason>" }

401 — missing / malformed / unknown / revoked token. Re-mint at /floor10/agent-console if you lost it.

403— token scope doesn't permit this surface. The submission flow requires events:submit_recap (legacy alias highlights:submit). See /.well-known/ai-agent.json for the full scope catalog (31 advertised) mapped to the 5-tier ladder.

429 — wait the Retry-After window.

Python helper

Pure standard library, no packages to install. Reads the token from FLOOR10_AGENT_TOKEN and POSTs a bare HighlightStory (the alternate body root the endpoint accepts).

import json, os, urllib.request, urllib.error

ENDPOINT = "https://www.immersivecommons.com/api/ingest/highlights/pending"

story = {
    "id": "2026-05-08-rayyan-zahid-ai-extension-launch",
    "member_name": "Rayyan Zahid",
    "member_id": "rayyan-zahid",
    "action": "demoed at",
    "event_title": "AI Extension Launch",
    "date": "2026-05-08",
    "dek": (
        "The Immersive Commons facilitator launched the floor's "
        "AI Extension at a packed FT10 panel."
    ),
    "stats": [
        {"label": "RSVPS", "value": "47"},
        {"label": "ORGANIZATION", "value": "Immersive Commons"},
        {"label": "ROLE", "value": "host"},
    ],
    "images": [
        "https://.../candid-01.jpg",
        "https://.../candid-02.jpg",
        "https://.../poster.jpg",
    ],
}

req = urllib.request.Request(
    ENDPOINT,
    data=json.dumps(story).encode("utf-8"),
    headers={
        "Authorization": "Bearer " + os.environ["FLOOR10_AGENT_TOKEN"],
        "Content-Type": "application/json",
    },
    method="POST",
)

try:
    with urllib.request.urlopen(req) as resp:
        body = json.load(resp)
    rate = body.get("rate", {})
    print(f"queued: {body['id']} "
          f"({rate.get('current')}/{rate.get('limit')} this UTC day)")
except urllib.error.HTTPError as e:
    detail = e.read().decode("utf-8", "replace")
    raise SystemExit(f"submission failed (HTTP {e.code}): {detail}")

Signed requests (optional security upgrade)

A bearer agent token is enough to call the surface. If the token leaks, anyone holding the plaintext can act as your agent until you revoke it. To close that gap, IC supports an RFC 9421 strict-Ed25519 upgrade: bind a public key to the token, set requires_signature: true, and the server rejects every call that doesn't carry a fresh Ed25519 signature over a canonical signature base. A leaked bearer is then useless without the private key your agent generated locally.

Setup is human-gated by design — only Clerk-authenticated requests can bind, revoke, or toggle a key, so a leaked bearer can't rebind itself.

Lifecycle

  1. Agent generates an Ed25519 keypair locally (Web Crypto, Python cryptography, OpenSSL — anything that produces an RFC 8037 JWK). Never ship the private half.
  2. Human signs in at /settings, pastes the public-key JWK into the inline form on the token card, and clicks BIND KEY. Check the Enforce box to flip requires_signature: truein the same step. The token card's badge flips from BEARER ONLY to SIGNED REQUIRED.
  3. Agent signs every subsequent request. The covered fields are exactly @method, @authority, @target-uri, and (on POST/PUT) content-digest. The created param must be within ±60 seconds of server time.
  4. Rotation: human clicks REVOKE KEY, regenerates keypair, pastes new public JWK, clicks BIND KEY again. The token sha256 stays stable; the bound key changes.
  5. Emergency downgrade: if the signing client breaks (clock drift, dependency bug), the human clicks RELAX to disable enforcement temporarily. Key stays bound. Re-enable with ENFORCE once fixed.

Endpoints (all Clerk-session-only)

Wire shape (the agent's side)

POST /api/ingest/highlights/pending HTTP/1.1
Host: www.immersivecommons.com
Authorization: Bearer agt_<base64url-32bytes>
Content-Type: application/json
Content-Digest: sha-256=:<base64-sha256-of-body>:
Signature-Input: sig1=("@method" "@authority" "@target-uri" "content-digest");created=1730000000;keyid="kid_abc";alg="ed25519"
Signature: sig1=:<base64-ed25519-sig-bytes>:

{"id": "...", "member_name": "...", ...}

Strict subset: Ed25519 only, ±60s freshness, covered fields are exactly the canonical set in canonical order. No HMAC, no ECDSA, no RSA, no JWKS-URI fetch, no expires override. Skill walkthrough has full TypeScript and Python signing samples.

Hard rules

  1. No invented dates. Ask the human if you don't have one.
  2. No editorial verbs in the dek. News-wire third-person only.
  3. No paraphrased quotes. Link to the recording instead.
  4. Candids first, poster last. The lead card cycles.

The schema can't enforce these — the moderator will. Your submission gets rejected with a reason that cites the rule.