/v1/perceive: a text read of a frame (what objects are here?). No boxes./v1/ground/objects//v1/ground/scene: bounding boxes for named objects (where exactly is each one?).- an
anchoron an event: pins a choice to a detected object so the player renders it as a clickable hotspot. See Editing the graph for event edits, and Embedding for where that clickable player runs. /v1/seed-frame//v1/probe: paint a fresh frame, or probe one into a starter world.
Every vision route is a write: it needs a secret key (
sk_) with
worlds:write, because each call makes real, paid vendor spend. A publishable
key (pk_) only carries worlds:read and is rejected here. Never ship a secret
key to a browser. Run these from your server.Metered and fail-closed
Each vision call reserves quota before it hits the backend, and refunds on any failure. The failure modes are strict and uniform:402: you’re over your daily cap for that meter (perceive,ground,seed_frame, orprobe). Reserve-before-spend, so you’re never billed past the cap.503: the quota backend is unavailable. The call fails closed rather than letting unmetered spend through.413: the base64 frame exceeds the per-call size cap.400: a required field is missing (no frame, emptylabels, …).502: the vision backend itself errored, and your quota is refunded.
A
200 with an empty result (no hotspots, or an empty perception) is still
charged. The paid vendor call was made regardless of whether it found
anything. Refunding empties would open free-abuse, so you pay for the work
performed.ground unit per label you ask for, capped
at 6 (the backend’s hard cap on returned boxes). Ask for 6 labels and you
spend 6 ground units. Anything past 6 is dropped before it’s billed.
Read a frame with /perceive
POST /v1/perceive is a text-only read of a frame (no bounding boxes). Use
it first, to discover what’s on screen before you decide which objects to make
clickable. It returns a one-line summary, the visible objects, the
affordances (things the player could act on), and the exits.
Pass the frame as a base64-encoded JPEG in frame_b64. room_name and
room_description are optional hints that sharpen the read.
{ summary, visible[], affordances[], exits[] }, all strings and
no coordinates. Take the labels you want to make clickable and feed them to a
grounding route next.
Detect objects with /ground/objects
POST /v1/ground/objects is the open-vocabulary detector: pass labels[]
and get back a hotspot box for each one it finds. This is what turns a name like
"the red valve" into geometry you can pin a choice to.
{ label, bbox, confidence }. bbox is [x, y, w, h] in
0-1000 normalized coordinates: the box is w/1000 of the frame wide and
h/1000 tall, with its top-left corner at (x/1000, y/1000). Because the
coordinates are normalized, they map onto the streaming video at any resolution.
Pin choices with /ground/scene
POST /v1/ground/scene is the closed-vocabulary variant: instead of loose
labels, you pass the graph choices you already have as
expected_choices: [{ id, label }], and each returned hotspot carries a
choice_id linking the box back to the choice it matched. Use this when you’re
grounding a specific set of branches (rather than free-form object discovery).
The choice_id tells you exactly which event to anchor.
{ hotspots: [...] } response shape. Only
choice_id differs: it’s populated on /ground/scene (matched to an
expected_choice) and null when a label wasn’t an expected choice.
The interactive payoff: anchors
Grounding on its own is only detection. To make an object clickable in the player, attach ananchor to the event whose branch that object should
trigger:
anchor.label, the shipped player (StateMachinePlayer
and the embed) runs a live VLM detect loop on the streaming
frame and renders that choice as a clickable chip pinned over the detected
object, the same [x, y, w, h] 0-1000 box geometry the grounding routes
return. The player clicks the object to take the branch. aliases give the
detector alternate names to match on when the object’s on-screen appearance
drifts.
You set the anchor with an ordinary graph edit (events are addressed by their
unique name): either the dedicated event patch or a batch update_event op.
Both funnel through the same fail-closed validation gate.
End-to-end: a frame becomes a clickable object
1
Perceive the frame
POST /v1/perceive with the frame’s frame_b64. Read visible and
affordances to find the objects worth making interactive, such as "the red valve" and "the rusted door".2
Ground the labels you picked
POST /v1/ground/objects with those labels (or /ground/scene if you’re
matching them to existing choices). Confirm each object comes back with a
plausible bbox and a decent confidence before you wire it up. A label the
detector can’t find won’t render a chip.3
Anchor the events
For each object,
PATCH /v1/worlds/{id}/events/{name} (or a batch
update_event op) to set anchor.label on the event that object should
trigger. Add aliases for names the detector might see instead. The write is
validated fail-closed like any graph edit.4
Play it
Embed the world (see Embedding). The player detects the anchored
objects live and renders each anchored choice as a clickable chip pinned to its
object. The player clicks the object to branch.
Paint or probe a frame
Two more routes round out the pipeline, for when you don’t already have a frame:POST /v1/seed-framepaints a fresh 16:9 frame from a textprompt(optionalpov:first_person/third_person_shoulder;mood;style:illustrated/cartridge). It returns the image asimageBase64and a hostedurlyou can pin as an entrance image, plus the resolvedpromptandart_style. Meteredseed_frame.POST /v1/probetakes a frame (frame_b64orframe_url, with an optionalpremise) and returns a starter TestWorld bundle ({ testworld, verified[], probe_report, validation }): detected objects plus a probe report you can author from. Meteredprobe.

