documentation · observer index api

Observer Index API.

read before the work — return after it

The Observer Index is a remote MCP server your assistant connects to by URL. Nine tools: six that read your org’s store, one that writes back to it, and two for the Generative 3D AI Engine riding the same door. There is also a small REST layer for anything that isn’t an assistant. This page is the surface — what you paste, what you say, what comes back. The product page is where the case for it lives.

← Back to The Observer Index

who this is for

A door, honestly described.

including the part where it is shut

The service is live and answers right now — the command below needs no account, and you can run it before you trust a word of this page. What is not open is membership: an org is joined by an invitation minted by an operator, and there is no self-serve path. So this page is written for two people: someone deciding whether the thing is real before asking for a place, and someone who has just been handed an invite and wants step one.

# the door answers before you have any credential — check it yourself
curl https://index-api-chromatic-coherence.vercel.app/

{"service":"the-org-observers-index","stage":1,"oauth":true,"invites":true,
 "tools":["cc_study","cc_intent","cc_plan","cc_answer","cc_hue",
          "g3d_grow","g3d_world","cc_look","cc_return"]}

That tools array is the live tool list, served by the deployment itself. The two OAuth discovery documents under /.well-known/ answer unauthenticated too — they are what a client reads to work out how to connect. Every route that reads or writes on your behalf needs a credential and returns 401 without one. Three routes write without a bearer because they are how a credential is obtained in the first place — /authorize, /token and the Stripe webhook, each proved by its own means rather than by a token.

connect it

One URL. That is the whole config.

no token is pasted into your assistant

https://index-api-chromatic-coherence.vercel.app/mcp

Add that as a custom connector in your assistant. There is no API key field to fill in, because the connector never receives one. Your assistant hits the endpoint, gets a 401 carrying a pointer to the discovery documents, reads them, registers itself, and then sends you to the Index’s own consent screen — a page served by the Index, headed Connect to the Org Observers Index, listing the tools being granted and offering one password field labelled “Your org token or invite code”, an Allow button and a Deny link.

That field is where your invite goes — the ccinv_… code your operator handed you, or a ccoi_… org token if you already have one. Paste it there and press Allow. Your assistant is handed a token it stores itself; the credential you typed went to the Index and nowhere else. Redeeming an invite creates your membership and records your consent in the same transaction.

One honest limit: the redirect allowlist admits only the claude.ai and claude.com MCP callbacks, so today the consent flow completes for those surfaces. A client registering any other callback is refused at /register and again at /authorize.

what you type

You talk. It picks the tool.

read first, work, return last

You do not call these by name. You say an ordinary sentence and your assistant reaches for the tool that fits — the same way it reaches for any connector. The left column is the kind of thing a person actually types; the right is where it lands.

"catch up on what we know about onboarding"   -> cc_study { hue: "onboarding" }
"what does the org want here?"                 -> cc_intent { hue: "onboarding" }
"how are we planning to get there?"            -> cc_plan   { hue: "onboarding" }
"what have we already settled?"                -> cc_answer { hue: "onboarding" }
"just ask the index in plain words"            -> cc_hue    { prompt: "..." }

"read back before you start"                   -> cc_look   (no arguments)
"file what we did back to the index"           -> cc_return { read_stamp, observation, shards }

The shape of a session is cc_look to read back, one or more reads while you work, then cc_return to file what happened. cc_return is the only tool that writes to your org’s index; every other index tool reads. (g3d_grow writes too, but to the shared world log — a different store, and a different product.) It also cannot run cold: cc_look mints a read_stamp, valid for ten minutes, and a return without a live one is refused. Reading before writing is enforced, not advised.

Each call records a usage weight — cc_look 1, cc_intent / cc_plan / cc_answer 2 each, cc_study 3, cc_hue 4, g3d_grow 4, cc_return 5, and g3d_world 0. So a cc_lookcc_studycc_return session costs 1 + 3 + 5 = 9. These are counts of work, not money, and a refused call records nothing at all. The engine’s tokens are not a second currency — they are these same units, on this same ledger.

what comes back

The read-back, and the return.

named numbers — there is no score field

Both faces carry the same payload: the MCP tool and its REST twin are one code path. (Over MCP it arrives wrapped — the JSON below is the text of acontent block inside the JSON-RPC envelope, not the top level.) Below are the shapes rather than any org’s figures — n, m, w and b stand where numbers go.

// cc_look  ·  GET /light  — the read-back. Shapes, not one org's numbers.
{
  "light":    { "<concept>": [lightness, saturation] },
  "hold":     { "concepts_held": n, "observers_bridged": n, "traversals": n,
                "light": n, "open_bonds": n, "shards_landed": n, "shards_riding": n },
  "headings": { "dark":   { "total": n, "shown": m, "shards": [ <shard>, ... ] },
                "substrate": { ... }, "light":    { ... },
                "intent":    { ... }, "planning": { ... }, "answer": { ... } },
  "spreads":  { "total": n, "shown": m, "rows": [ <spread>, ... ] },
  "offered":  [ { "content": "...", "weight": w, "rides": n } ],
  "read_stamp": "<ms>:<32 hex>"
}

<shard>  = { "content": "...", "weight": w, "by": "<observer>", "hue": "<concept>", "seq": n }
<spread> = { "observer": "...", "hue": "...", "stated": ["...","...","..."],
             "meant": ["...","...","..."], "bond": b, "seq": n }

// "shown" is capped; "total" is always the exact store count — so a truncated
// read says so out loud rather than quietly handing you a partial picture.

hold is seven separate counters and nothing else — no total, no index, no grade. light gives each concept two numbers: how seen it is, and how sure. The six headings are fixed: dark, substrate, light, intent, planning, answer.

// cc_return  ·  POST /return — the only write in the tool set.
{
  "read_stamp": "<the stamp cc_look just handed you>",
  "observation": {
    "observer": "...",                       // who crossed
    "hue":      "...",                       // the concept it was about
    "stated":   ["...", "...", "..."],       // exactly three
    "meant":    ["...", "...", "..."],       // exactly three
    "bond":     0.62                         // strictly inside (0, 1)
  },
  "shards": [
    { "content": "...", "heading": "planning", "weight": 0.6 },   // lands under a heading
    { "content": "...", "heading": null,       "weight": 0.4 }    // rides, offered for claiming
  ]
}

-> { "intake_seq": n,     // this return's place in the org's order
     "hue": "...",
     "landed": n,         // how many of THIS return's shards landed under a heading
     "riding": n }        // the size of the riding pool after the return

// refused instead:  422 { "refused": "CROWNED_BOND", "detail": "..." }
// nothing is written on a refusal.

The observation’s field set must be exactly those five. An extra field is refused with a message that ends “there is no field for a score”, and a bond of exactly 0 or 1 is refused as well. Refusals are typed: the code names the law that fired, and nothing was written.

The three field names that are not self-explanatory. stated is what was said, in three phrases; meant is what it was about, in three — a spread is always three and three, and the pair is what lets a later reader tell a wording change from a change of mind. bond is how strongly the crossing held, and it must stay strictly inside 0 and 1: a bond of exactly 0 or 1 is a verdict rather than a signal, and the centre refuses it outright. A quarter of each bond becomes the concept’s visible lightness, which is why concepts you keep returning to are the ones that read brightest. A shard with a heading lands under it; a shard with null rides — it stays in a pool offered to every reader until someone claims it under a heading, so a half-formed thought is neither lost nor filed as though it were settled.

the whole cycle, filled in

What it actually looks like.

the shapes above, with words in them

The shapes above are deliberately empty so they cannot be mistaken for one org’s data. Here is the same cycle with content in it — illustrative, not a real store — because the content field is where the whole product lives, and an empty one shows nothing.

// ONE WHOLE CYCLE, FILLED IN. Illustrative content — not any org's real store.
// Your assistant does this; you just talk to it.

// 1. You say: "we're picking up the onboarding work again."
//    It calls cc_look  ->
{
  "light":  { "onboarding": [0.41, 0.68] },
  "hold":   { "concepts_held": 34, "observers_bridged": 6, "traversals": 212,
              "light": 41, "open_bonds": 212, "shards_landed": 878, "shards_riding": 19 },
  "headings": {
    "answer": { "total": 31, "shown": 20, "shards": [
      { "content": "Invite codes are handed over in person. The service sends no e-mail.",
        "weight": 0.9, "by": "kw", "hue": "onboarding", "seq": 604 } ] },
    "planning": { "total": 12, "shown": 12, "shards": [
      { "content": "Second seat waits until the first has returned twice.",
        "weight": 0.55, "by": "kw", "hue": "onboarding", "seq": 588 } ] }
  },
  "read_stamp": "1785412088431:9f2c...e10a"
}
//    Your assistant now knows the e-mail question is already settled, and does not
//    re-litigate it. That is the whole product, in one field.

// 2. You work. You decide the fourteen-day expiry is too short.

// 3. You say: "file that." It calls cc_return  ->
{
  "read_stamp": "1785412088431:9f2c...e10a",
  "observation": {
    "observer": "kw",
    "hue": "onboarding",
    "stated": ["expiry too short", "codes go stale", "re-mint is manual"],
    "meant":  ["the door shuts early", "trust decays unattended", "someone must notice"],
    "bond": 0.62
  },
  "shards": [
    { "content": "Invite expiry moves 14d -> 30d; re-mint stays operator-only.",
      "heading": "planning", "weight": 0.7 },
    { "content": "Nobody is watching expiry. Worth a look.",
      "heading": null, "weight": 0.3 }
  ]
}
//  -> { "intake_seq": 605, "hue": "onboarding", "landed": 1, "riding": 20 }

// 4. Tomorrow, your colleague's assistant calls cc_look on the same org and reads
//    seq 605 before it starts. Nobody told it anything.

Step 1 is the part that is hard to picture until you see it: the assistant is told the e-mail question is settled before it starts, so it does not raise it again. Step 4 is the part people ask about most — nobody briefed the colleague’s assistant, and it still opens knowing what you filed.

what your colleague sees

Shards travel. Your words don’t.

private by default; sharing is your own act

A return carries two different things. The shards — the content you filed under a heading — are org-wide the moment they land: that is the point of a shared index, and they show up in every colleague’s cc_look. The spread — your three stated and three meant strings, your own words about what you were doing — does not. Three rules govern it:

  • Private by default — a spread stays yours unless you turn it on, and you turn it on for yourself. POST /spreads/share is any member’s own act, not an operator’s, and POST /spreads/withhold reverses it.
  • Forward-only per reader — a colleague reads your shared spreads only from their own joining onward. Someone who joins in March never reads your February spreads, even shared ones.
  • Always your own — you read all of yours regardless. And spreads never ride the study surface at all; they travel only through the read-back.

Turning sharing on or off writes a consent event carrying the scope in plain words, including the part nobody can undo: what another mind has already read cannot be unread.

Worth being clear, because it is easy to misread: forward-only applies to spreads — the personal stated/meant pairs. It does not gate the org’s landed shards or its light, which a new member reads whole on their first cc_look.

the console

Nine sections behind sign-in.

described, not screenshotted

The wire is one face; the browser console at /org is the other. It sits behind sign-in, so here is a written account of it rather than a picture. It opens on your org’s own name and a sticky side-nav of nine sections; the current section is in the URL, so any view can be linked.

  • Overview — the seven hold counters as tiles, then every concept as a row with a colour swatch and two labelled meters, “how seen” and “how sure”, with the value to three decimals. Below that, the riding shards waiting to be claimed.
  • Ask Hue — one input and an Ask button. A held answer shows the composed prose, the concepts it read, and the terms that actually matched; a weak match says so rather than dressing a word-match as understanding. A miss says what the store does hold and is recorded. Underneath, the open hunger: the asks that missed.
  • The Study — a line chart of one concept’s journey, each point an observation, then the six headings with their shards and counts.
  • Observers — one card per observer that has crossed: how many returns, which concepts, and a small arc of its bonds.
  • People — one card per member: role, org e-mail, a torch bar as long as their returns, whether they consented, and their last return. A torch’s length may be spoken; its contents may not.
  • Shared Spreads — the rows in scope for your seat, and your own share / withhold switch with the current state spelled out.
  • My Seat — your own returns including your stated and meant, which nobody else in the org can read, plus your live tokens and your consent record.
  • Usage — the meter made visible: per day, by tool, by person, with the weights stated in words.
  • Invites — operators only: mint a code, see it exactly once, and revoke what is still unredeemed.

the surface

Every route and tool.

transcribed from the source of truth

Everything below hangs off https://index-api-chromatic-coherence.vercel.app. Nothing is listed that the service does not serve. Most entries are a real route or a real MCP tool; some name a field, a header or a rule that those routes carry — bond, read_stamp, initialize — because a reference that lists only paths leaves you unable to read the answers. Note that the study tools have no REST twin: cc_study, cc_intent, cc_plan and cc_answer are reachable over MCP only. The service ships no interactive schema browser, which is what this page is for.

the door

One service, three faces: a remote MCP endpoint, a small REST layer, and the OAuth consent layer that connects them. Every route that reads or writes your org's store needs a credential. The service card, the two discovery documents and client registration do not — registration is open by design (RFC 7591 public clients); the redirect allowlist is the boundary, not a secret.

GET /
→ { service, stage, oauth, invites, tools[] }the service card — the only descriptive route that answers with no credential; its tools[] is the live tool list
POST /mcp
JSON-RPC 2.0 · Streamable HTTP · statelessthe MCP endpoint — this is the URL an assistant connects to. POST only; there is no GET handler
Authorization
Authorization: Bearer <credential>every authenticated route. Two credential kinds, told apart structurally by a dot: a dotless ccoi_ org token, or a session JWT from the console
401 discovery
WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource"every 401 carries the RFC 9728 pointer, so a connecting surface finds the consent flow instead of dead-ending
initialize
serverInfo { name: "the-org-observers-index", version: "1.0.0" } · capabilities { tools: {} }the MCP handshake — tools only; no resources, prompts or sampling. protocolVersion echoes the client's, else 2025-06-18
tools/list
→ { tools: [ … ] }the nine tools below, with their input schemas
batching
array body → 400 · error −32600 "batching not supported"one JSON-RPC message per request, by design

connecting — OAuth 2.1 + dynamic client registration

The client discovers all of this from the URL alone. The credential is pasted at the Index's own consent screen, never into a third party's config. The redirect allowlist admits only the claude.ai and claude.com MCP callbacks, so today the flow completes for those surfaces.

GET /.well-known/oauth-protected-resource
→ { resource, authorization_servers[], bearer_methods_supported, scopes_supported }step 1 — names /mcp as the resource and this service as its own authorization server. Also served at …/oauth-protected-resource/mcp
GET /.well-known/oauth-authorization-server
→ { issuer, authorization_endpoint, token_endpoint, registration_endpoint, … }step 2 — response_types [code], grant_types [authorization_code], PKCE S256, auth method none, scope index. Also served at …/oauth-authorization-server/mcp
POST /register
{ redirect_uris: [ … ] } → 201 { client_id, redirect_uris, … }step 3 — RFC 7591, public clients only. client_id is derived from the registered redirect set and never stored; an unlisted redirect_uri is refused invalid_redirect_uri
GET /authorize
?response_type=code&client_id=…&redirect_uri=…&code_challenge=…&code_challenge_method=S256step 4 — renders the Index's own consent screen. A bad parameter gets a flat HTML 400 and never a redirect
POST /authorize
form: org_token → 302 redirect_uri?code=…&state=…step 5 — the person pastes a ccoi_ org token or a ccinv_ invite code. An unrecognised credential re-renders the screen with a refusal, never a redirect. A real grant writes a consent event
POST /token
grant_type=authorization_code&code=…&code_verifier=… → { access_token, token_type, scope }step 6 — the code is consumed atomically and is single-use; a failed PKCE exchange still burns it. Mints a ccoi_ token stored only as a hash. No refresh token, no client secret
code lifetime
authorization code · 300 secondsthe exchange window, enforced in the store's own clock

the tools — reading before the work

cc_look is the read-back and mints the read_stamp a later return must carry. The study tools read the store deterministically — no model and no search sits in these paths.

cc_look
(no arguments)the read-back: the org's light, the hold reading, the six headings with their landed shards, the shared spreads in scope, the riding shards on offer, and the read_stamp
cc_study
{ hue: string } — requiredone concept, whole: its light, its bond arc, and all six headings' shards at depth, with exact totals
cc_intent
{ hue?: string, before_seq?: integer }what the org wants on a concept, composed from the store's intent shards. Omit hue for the org's intent across every concept
cc_plan
{ hue?: string, before_seq?: integer }how the org means to get there, from the store's planning shards. Omit hue for the org's whole plan
cc_answer
{ hue?: string, before_seq?: integer }what has already fired and held, from the store's answer shards. Omit hue for everything the org has answered
cc_hue
{ prompt: string } — required, minLength 1ask in plain words. The prompt grounds against a vocabulary derived from the store itself, the matched concepts are read, and the answer is composed only from store content. Not held is an honest refusal, recorded as hunger

the tool — writing after the work

cc_return is the only tool that writes to your org's index — every other index tool reads. (g3d_grow also writes, but to the shared world log, not to your store.) It requires a live read_stamp: read back before you return, enforced rather than advised.

cc_return
{ read_stamp: string, observation: {…}, shards?: [{…}] }everything the session did comes back to the store — one observation plus shards
observation
{ observer, hue, stated[3], meant[3], bond }the field set must be exactly these five; anything else is refused UNKNOWN_FIELD — there is no field for a score
bond
number, exclusiveMinimum 0, exclusiveMaximum 1strictly inside the interval. A crisp 0 or 1 raises CROWNED_BOND and nothing is written
shards[]
{ content: string, heading?: "dark"|"substrate"|"light"|"intent"|"planning"|"answer"|null, weight?: number }content is required; a heading lands the shard under it, a null heading makes it a rider offered for claiming. weight is strictly inside (0,1)
read_stamp
<ms>:<32 hex> · valid 600 secondsminted by cc_look, verified on return; a stale or absent stamp is refused STALE_STAMP

the tools — the generative 3d ai engine

A separate product riding the same door. Its tokens ARE index units — one currency, one ledger: a grounded grow spends 4 either way you count it. Reading the world is free; growing it is what meters.

g3d_grow
{ prompt: string } — required, minLength 1speak the world you want; the words ground against the engine's own vocabulary into fourteen typed operations — background, beast, block, chart, figure, flight, grove, light, orb, terrain, tube, vehicle, view, water — each carrying a seed derived from your words, so the same prompt always grows the same world. An empty balance refuses NO_TOKENS with the arithmetic named. Words the engine cannot speak yet refuse honestly and are recorded as hunger. This list is generated from the engine's own schema and is repeated in the live tools/list — curl the service card above and check it
g3d_world
(no arguments)the one shared world's log, whole and in order, each growth named by its grower. Unmetered

the rest layer

The same door without MCP. Note the study tools have no REST twin — cc_study, cc_intent, cc_plan and cc_answer exist over MCP only. Door refusals surface as HTTP 422 { refused, detail }.

GET /light
→ { light, hold, headings, spreads, offered, read_stamp }the REST twin of cc_look, including the read_stamp
GET /hold
→ { concepts_held, observers_bridged, traversals, light, open_bonds, shards_landed, shards_riding }the seven counters alone. Unmetered. There is no score field and no aggregate
POST /return
{ read_stamp, observation, shards? } → { intake_seq, hue, landed, riding }the REST twin of cc_return, same shapes and same laws
POST /hue
{ prompt } → { held, … }the REST twin of cc_hue. A held answer carries strength, answer, matched, operations, evidence and light; a miss carries kind, holds[] and recorded
GET /hue/misses
→ { total_shown, misses[] }the open hunger — the newest 50 asks the store could not answer, org-scoped. Unmetered by design: reporting is never billed

people, spreads and invites

A spread is one observation's stated and meant — a person's own words. Private by default; sharing is the person's own act, not an operator's. Minting invites is operator-only.

POST /spreads/share
(no body) → { spreads_shared, since, flipped }any member, for themselves — puts their own stated/meant on the wire. A real flip writes a consent event carrying the scope in words
POST /spreads/withhold
(no body) → { spreads_shared, since, flipped }the same act, reversed: stated/meant leave the wire from now. flipped is false when the state already matched the ask
reader scope
opt-in · forward-only per reader · always your owna colleague reads a person's spreads only if that person opted in, and only those at or after the colleague's own joining. You always read all of yours. Spreads never ride the study surface
POST /invites
{ email, name?, role? } → 201 { invite, invite_id, expires_at }operator-only. The raw ccinv_ code appears exactly once, in this response — hand it to the invited person yourself. Valid 14 days. This service sends no e-mail
POST /invites/revoke
{ invite_id } → { revoked }operator-only; only an unredeemed, unrevoked invite in your own org can be revoked

the meter and the plan

Units are usage weights, never money. The meter fires after the serve is computed: a refused call meters nothing, cc_hue meters only when held, g3d_grow only when grounded, and reporting routes are unmetered by design.

units per call
cc_look 1 · cc_intent / cc_plan / cc_answer 2 · cc_study 3 · cc_hue 4 · g3d_grow 4 · cc_return 5 · g3d_world 0all nine weights, recorded per call. A look → study → return session is 1 + 3 + 5 = 9
GET /billing/plan
→ { tier, status, seats, seats_in_use, seats_free, monthly_cap, month_units, cap_basis, advisory }any member; unmetered. Reports the org's tier and the units used this month
POST /billing/checkout
{ tier } → { url }operator-only — returns a hosted checkout URL. Prices are resolved by lookup key; no amount lives in this service
POST /billing/portal
(no body) → { url }operator-only — returns a hosted billing-portal URL
POST /billing/webhook
signature-verifiedthe sole writer of the org's plan row; an unverified signature is refused

the engine's own routes

The Generative 3D AI Engine over REST. Metered on the same ledger as every index call — a grounded grow writes g3d_grow = 4 into usage_events exactly as cc_return writes 5.

POST /g3d/grow
{ prompt } → the growth · NO_TOKENS → 402grow the world. Grounding happens first, so an ask the engine cannot speak is still captured as hunger even at an empty balance
GET /g3d/world
→ { total_ops, log[] }the shared world's log — free to read
GET /g3d/balance
→ { free_allowance, purchased, used, balance, grow_cost, packs[] }unmetered. packs[] carries each pack's lookup key and the token quantity it grants — quantities, not money
GET /g3d/misses
→ { total_shown, misses[] }the engine's own hunger — the newest 50 words it cannot speak yet. Unmetered
POST /g3d/checkout
{ pack } → { url }any member may buy tokens for their org — a consumable, not a plan change

refusals

A refusal is typed: the code names the law that fired, and nothing was written. Over REST they arrive as 422 { refused, detail }; over MCP as a tool result with isError set.

STALE_STAMP
cc_return · POST /returnno fresh read_stamp — read back before you return
UNKNOWN_FIELD
cc_return · POST /returnthe observation or a shard carried a field the law does not have
CROWNED_BOND
cc_return · POST /returna bond of exactly 0 or 1 was offered; a crisp bond is refused
BAD_SHAPE
any door callthe request shape is wrong — a study with no hue, an unknown heading, an empty prompt
LAW_REFUSED
cc_return · POST /returnthe kernel refused the fold itself
NO_TOKENS
g3d_grow · POST /g3d/grow → 402the engine balance cannot cover a growth; the arithmetic is named in the refusal
NO_SECRET
any stamped callthe deployment is missing its stamp secret — a configuration refusal, never a silent default

getting a seat

The one step this page can’t do for you.

membership is minted, not signed up for

Connecting an assistant works the moment you hold an invite, and not before — though the service card and the two discovery documents above answer anyone, today, with no account at all. Codes are minted by an operator, are good for fourteen days, and are handed over in person — the service sends no e-mail. If you don’t have one, the product page is where to ask for a place; if you already have a seat, your org’s console is where it lives.