HTTP API¶
Interactive documentation is served by a local backend at http://localhost:8000/docs (the hosted
one turns it off with API_DOCS=0). The same OpenAPI document is rendered below.
Feature endpoints¶
| Path | Purpose |
|---|---|
POST /api/extract |
multipart photos (one to MAX_PHOTOS images, each at most MAX_UPLOAD_MB), or sample_ids (demo sample photo ids, read in their place and answered only by their own recording), and optional crops (label crops from /api/segment): reads the label into typed fields, each with value, unit, confidence, source (photo, or label_crop when crops were sent) and raw, plus item_type, label_text, warnings, the counts of photos and crops, the model used, from_fixture, cost_usd and enhanced (whether what the model read was contrast-stretched and sharpened; a blurred label adds a line to warnings) |
POST /api/segment |
multipart photo (one image), or sample_id (a demo sample photo), and optional repeated concepts (default item, label, connector; an unknown key is sent to the model as its own phrase): one region per match with concept, box (half-open pixels of the prepared photo), confidence, coverage, a JPEG crop data URL and the mask outline as svg_path; plus the overlay (the photo with the masks drawn over it), warnings, width, height, the model, from_fixture and cost_usd |
Errors, for both: 400 for a bad or oversized photo (by upload size or pixel count), a HEIC
file, too many photos or concepts, 403 for a file sent by the demo visitor (Sign in with the
access code to read your own photos.), 404 for an unknown sample or a sample with no
recording (No recorded answer for this sample yet.), 422 when neither the file nor the
sample is sent, or both, 502 when the model answer was incomplete or the Model API failed, 503 when the spend
cap is reached or fixture mode has no recorded answer. The detail text is safe to show.
Demo samples¶
The demo set's photos (and its listing texts and voice notes, when present), named by id: what
the demo visitor reads instead of a file of their own. Open to the visitor; empty where the set
is not shipped (DEMO_SET).
| Path | Purpose |
|---|---|
GET /api/samples |
the catalog: items (the set's things, each with key, name, item_type and its photos, each {id, file, item_key, item_name}, in the set's order), listings ({id, name, search_key, search_title, asking_price, currency}) and notes ({id, item_key, item_name}) |
GET /api/samples/{id}/file |
the sample as stored: the photo (image/jpeg), the listing text or the voice note; 404 for an unknown id |
GET /api/samples/{id}/thumb |
a sample photo at most 320 px on its long side, for the gallery |
POST /api/samples/read |
JSON sample_ids (sample photo ids) and find_label (default true): an item whose every photo is picked is one reading of them all, any other photo is read alone, one result each in the catalog's order: {sample_ids, item_key, item_name, whole_item, segmentation, extraction, missing, note}, with segmentation when the label was found first. The flow is the web app's (SAM 3.1 on the first photo, Muse Spark on the photos and the label crops), on the stored files, so the recording of that exact photo answers; with none, missing is true and note says so instead of a reading. Unknown ids are 404. Through the code's door each reading past the first is one more call against the allowance |
Inventory endpoints¶
JSON in, JSON out. An item carries its fields (the current value per field), every
assertion with its provenance, confidence, source and review state, the ids of its
separated components, and a version that every write increments.
| Path | Purpose |
|---|---|
POST /api/items |
save an item privately: extraction (an answer of /api/extract, every field becomes an observed value to confirm) and/or the item's own fields (name, item_type, condition, faults, tests_done, accessories, location, notes, intent, availability, parent_id); 201 with the item |
GET /api/items |
every item not deleted, newest first, without location and notes |
GET /api/items/{id} |
the item with its assertions and components, and needs_parent: a part that cannot be tied to its device yet (a part profile, no model or part number of its own, no parent item, no parent_device or parent_device_model); the owner's parent_device is left out for the demo visitor |
PATCH /api/items/{id} |
change any of the item's own fields; with version set, a stale write is refused with 409 |
DELETE /api/items/{id} |
hide the item; 204; its events stay readable |
POST /api/items/{id}/fields |
an owner value: field, value, unit, provenance (owner_reported, documented, tested, estimated); accepted as stated, beside any observation |
POST /api/items/{id}/assertions/{aid}/review |
{"review": "accepted" \| "rejected" \| "proposed"} for one observed value |
POST /api/items/{id}/components |
separate a part into its own item under this one; the parent's faults note it; 201 with the part |
GET /api/items/{id}/events |
the history: actor, time, change, version |
GET /api/items/{id}/photos |
how many photos are kept with the item (count); an item read from a capture has the capture's, an item saved from the web form the label photos it was read from |
GET /api/items/{id}/photos/{n} |
one of them as JPEG, 1 first, never cached; 404 past the count |
POST /api/items/{id}/public, POST /api/captures/{id}/public |
{public}: shown to the demo visitor (the item or capture, its values and photos, an item's drawn pictures; never a location, a note, a clip or a draft) or private again; the demo visitor's GET on items and captures lists the public ones and answers 404 for the rest |
POST /api/items/{id}/photos |
multipart uploads, one to MAX_PHOTOS in all on the item: re-encoded on the way in, metadata dropped; 201 with the new count; 422 over the limit, 400 for a file that is not an image |
POST /api/items/{id}/parent-photo |
multipart photo: a photo of the device a part came from (the label under its base is best), read like a label (one Muse Spark call) and kept as a photo of the device, apart from the part's photos (outside MAX_PHOTOS, drafts and the photo zip); the brand and model number (or part number) read off it are proposed as parent_device_model, source photo, for the owner to accept; 201 with the item, the model read (empty when none) and the extraction; 404 for an item that is not there, 422 when the item already keeps MAX_PHOTOS photos of the device, 400 for an empty or too large file, each refused before anything is counted. Counted as a call that spends; refused to the demo visitor |
GET /api/items/{id}/device-photos, GET /api/items/{id}/device-photos/{n} |
how many photos of the device the part came from are kept (count), and one of them as JPEG, 1 first; the owner's alone, refused to the demo visitor |
POST /api/items/{id}/picture |
JSON use (a project template id, a use in your own words, or empty for the item itself): Muse Image draws what the item could become from its type, accepted values, condition and faults, one image per call, only on this request; 201 with the picture (n, use, model, cost_usd, from_fixture, created_at); 503 with pictures are off on this server (PICTURE_ENABLED) while the switch is off. Illustrative, never evidence; counted as a call that spends |
GET /api/items/{id}/pictures |
the pictures drawn for the item and whether this server draws any (enabled); the demo visitor sees those of a public item (404 for the rest) and is never let draw one |
GET /api/items/{id}/pictures/{n} |
one picture as JPEG, 1 first, never cached; 404 for a number never drawn. Kept beside the photos, never counted as one |
POST /api/items/{id}/scenarios/{scenarios_id}/reuse/{card}/picture |
Muse Image draws how a reuse card's project could look once built (card counts from 0), from the card's title and what_it_does, the item's kind with up to three accepted values (none that identify it, no price) and the card's with_items kinds; one image per call, only on this request; 201 with the picture (use is the card's title); 404 for scenarios of another item or a card that is not there; 503 while PICTURE_ENABLED is off. Kept with that set of cards and gone with the item; counted as a call that spends; refused to the demo visitor |
GET /api/items/{id}/scenarios/{scenarios_id}/reuse/{card}/pictures, GET .../pictures/{n} |
the pictures drawn for that card and enabled; one as JPEG, 1 first, never cached. Not for the demo visitor |
Errors: 404 for an unknown item or assertion, 409 on a version mismatch, 422 for a value
outside the allowed sets (intent, availability, provenance, review), a field name that is not
snake_case, a blank name, part name or value, an owner value claiming to be observed, a
profile that is not one that ships, or text
over its bound (names 200 characters, values 500, details and *_notes fields 4000). A key sent as null in a
PATCH means not given.
Measurement and profile endpoints¶
| Path | Purpose |
|---|---|
POST /api/measure |
multipart photo (or sample_id, a demo sample photo; the demo visitor's only way, a file from the visitor is 403) and request (JSON: feature, profile, image size as marked, reference of kind two_point with points and length_mm or rectangle with corners, width_mm and height_mm, each with a tolerance_mm, the feature points, straight_on): the result with accepted, value_mm, uncertainty_mm, method, basis, every check with passed, detail and blocking, redo when refused, px_per_mm, the marks scaled to the stored photo, tolerance_mm and needs_direct_reading when the profile names the feature. Nothing is stored |
POST /api/items/{id}/measurements |
the same, stored on the item; an accepted capture proposes an observed value (source measurement) to confirm; 201 with the measurement (id, assertion_id, at) |
GET /api/items/{id}/measurements |
every capture on the item, accepted or refused |
GET /api/items/{id}/measurements/{mid}/photo |
the prepared JPEG the marks refer to |
GET /api/access, POST /api/access, DELETE /api/access |
the two doors: whether a code is needed (gated), whether this caller is through it (granted) and whether that reaches the live models (live); the code (the configured master or an issued record) for a session: a cookie, and the same token in the answer for a caller without cookies; 401 for a wrong code, 429 after too many tries; the end of a session. Without a session on a gated host every request is the demo visitor's: /healthz, /readyz, /api/version, the documentation, the profiles, the templates and the demo samples and the stateless tries (/api/samples/read, /api/extract, /api/segment, /api/assess on values written in, /api/measure; the ones that take a photo take a sample id, a file is 403) answer, the tries from the recordings and so many per address and hour (429 over it); everything else is 403 with demo: the code unlocks saving, drafts, prices, projects and captures. Through the door: 429 over an allowance with Retry-After, 503 for a call that spends while DEMO_PAUSED is set. See Demo access |
GET /api/profiles |
the shipped category profiles with their feature counts |
GET /api/profiles/{id} |
one profile |
GET /api/profiles/{id}/features |
the features worth measuring, each with key, label, unit, tolerance_mm and how |
GET /api/profiles/{id}/rules |
what a candidate is checked against: each rule with key, label, kind (equal, within, at_least, at_most), unit, hard, identity, tolerance, tolerance_pct and the question to ask when the value is unknown |
POST /api/assess |
JSON need and candidate, each an inventory item_id or values keyed by rule key (a profile on the need when no item carries one): the verdict from the ladder with its summary, every check with status (pass, fail, unknown), needed, observed, unit, basis and the candidate's backing (provenance, review, source, uncertainty), the questions for the candidate's unknowns, the rules missing a needed value and the checks to_confirm; 422 without a profile or when both sides are the same item, 404 for an unknown item |
POST /api/items/{id}/drafts |
JSON mode (sell or give), holds_data, pickup, asking_price (200 characters each, one line; the asking price one amount of dollars such as $1,200.50, else 422; the saved listing details are used when they are empty): one Muse Spark call (effort low) reads the item's accepted values (none that identifies it: a serial, MAC address or IMEI stays out, and one read off a photo is taken out of the text), the public listing details and the item's first four photos and writes a Facebook Marketplace (US) draft; the rules check category and condition against backend/app/listing/marketplace_us.yaml, set price in whole dollars and price_basis (the asking price, else the typical figure of the latest price guidance, named; 0 for a give-away), cut the title to 100 characters and the tags to 20, keep the photos order to the item's photos, append the disclosures (faults, what was and was not tested, what is included, data, pickup or shipping, availability), take out phone numbers, emails and street addresses, and drop any line that rests on nothing confirmed; the draft comes with facts (each naming its field), uses (give), warnings, to_confirm, details_version, its checklist (prepare, photos, and data when the item holds data), ready, stale and the handoff_url; 201. Counted as a call that spends |
GET /api/items/{id}/drafts, GET /api/drafts/{id} |
the drafts written for an item, each with stale set when the item changed since |
POST /api/drafts/{id}/checklist |
JSON key and done: tick or untick one step; 422 for an unknown step |
GET /api/marketplace/us |
the Marketplace (US) categories (each a path, whether it was seen on the public category pages, what it holds), the four conditions and the free_category |
GET /api/items/{id}/sell-details, PUT /api/items/{id}/sell-details |
the Listing details form: condition_words, included (stand, cables, power_cord, box, manual, remote), included_other, faults, untested, data (none, wiped, not_wiped), reason (private), handover (pickup, shipping, either), area (a city, ZIP or neighborhood), asking_price (above 0), lowest_price (private, not above the asking price), availability; each save is a new version; 422 for a street address in area, or a phone number, email or street address in the free text; null before the first save |
GET /api/drafts/{id}/photos.zip |
the item's photos in the draft's order (01-photo-2.jpg, ...), as stored: re-encoded on the way in, no metadata |
GET /api/items/{id}/marketplace, POST /api/items/{id}/marketplace/listed, .../sold, .../given |
what happened after the owner posted: listed (on, price, an optional url under https://www.facebook.com/marketplace/), sold (on, price above 0: also a reported sale for price guidance, and the item becomes gone), given (on; the item becomes gone); dates YYYY-MM-DD, not past the owner's day (UTC+14 at most); listed may follow listed, nothing follows sold or given (409), and a sale's event, reported sale and gone are one write; the state and the events |
POST /api/items/{id}/price |
JSON currency (ISO 4217, default USD), country (ISO 3166-1, optional), city (optional): Muse Spark searches for listings and sales of the same model, the rules keep only comparables a search result backs; four quantities (asking, sold, reported, estimate), each with count, low, high, typical, basis; the backed comparables with page and date, the sources, how many lines were dropped, three strategies, weak with its reason, caveats, parts when the item has components, searches made, and stale when the item changed since (a decision is no change); 201 |
GET /api/items/{id}/price |
every guidance written for the item, newest last |
POST /api/items/{id}/price/reports, GET /api/items/{id}/price/reports |
a sale you know of (price above 0, currency, sold_on as YYYY-MM-DD, note up to 200 characters), kept apart from everything the search finds and counted under reported in the next guidance; 201 |
POST /api/items/{id}/uses |
what the item is good for: the rules name the project templates with a requirement this kind of item fills (templates, each with the requirement keys it fills); Muse Spark proposes up to three uses from the item's accepted values, condition, faults, tests and the owner's preferences, each with title, what_it_does, needs, effort (low, medium, high) and a note; kept on the item with its item_version and stale once the item changes; 201 |
POST /api/items/{id}/scenarios |
what to do with it, for this owner: one Muse Spark call (low effort) from the item's accepted values (none that identify it), condition, faults, tests, accessories and intent, the owner's profile (rated skills, tools, effort, opens_devices, budget) and the kinds of item they own besides; answers keep_fix (worth_fixing, problems, steps, difficulty easy/moderate/hard, why, parts, cost, time), reuse (two or three, each with title, what_it_does, with_items (read as the kinds the owner has now, without a count; others left out), difficulty, why, extra_parts, cost, time), sell (selling_points, effort, note; no price), recommended (keep_fix, reuse, sell, give) and reason; profiled false when no profile is saved and the cards are written for an average person; kept on the item with its version, item_version, stale once the item is changed by anything but a decision, and steps_in_notes once the keep and fix steps of these cards are in the notes; refused to the demo visitor; 201 |
GET /api/items/{id}/decide |
one view to decide from: the item summary and accepted_fields; worth, the latest guidance in a line (typical, the strategy to start from, weak, stale) or null; the latest uses or null; the latest scenarios or null; the open searches from this item with candidates and handoff_url; the templates the rules say it fills; the intent; committed_to, the titles of the selected, building or done projects that count on the item; the last decision (choice, note, at, version) or null. Nothing is asked of a model |
POST /api/items/{id}/decision |
JSON choice (keep_fix, keep_build, sell, give, not_worth) and note (200 characters, one line): sets the intent (keep, build, sell, give, keep) and writes decided: <choice>: <note> to the history; keep_fix also adds the repair steps of the latest scenarios to the item's notes when the item is still at the version they were written from (checked in the same transaction), whole steps left out and a closing … (more in the scenarios) when they would pass the notes' 4000 characters; the version is bumped when the intent changes, a note is given or the notes gain the steps, not for the same choice said again; with version set, a choice made from an older version is refused with 409 and nothing is written; the item |
POST /api/searches, GET /api/searches, GET /api/searches/{id}, PATCH /api/searches/{id} |
a missing-item search: need_item_id or profile with spec (rule key to needed value), title, max_price, currency, radius_km, budget_left, notes; the answer carries handoff_url, the Marketplace search to run by hand, and the candidate count; PATCH takes status (open, found, closed) and notes; 422 without a profile |
POST /api/searches/{id}/candidates |
multipart request (JSON: source_kind text, screenshot or manual, text, values, observed_on, asking_price, currency, location, included_parts, url, note) and the screenshot file for a screenshot candidate: the listing read into fields with their source, the assessment, the state, the questions, observed_on and expires_on; 422 for a candidate without its source or a date in the future; 201 |
GET /api/searches/{id}/candidates, GET /api/candidates/{id} |
the candidates of a search, one candidate |
POST /api/candidates/{id}/recheck, POST /api/candidates/{id}/withdraw |
the same listing against the need as it stands now (422 once withdrawn); retire a candidate |
GET /api/candidates/{id}/questions |
the rules' open questions as a message to the seller: subject, message, questions |
POST /api/searches/{id}/plan |
one Muse Spark call: plan on the search with three to six phrasings (text, why, url, a Marketplace search link with the query and maxPrice), the pattern (empty with pattern_error when the written one fails the checks), the exclusions, max_price, radius_km, model, cost_usd; planned again over a pattern the owner saved, that pattern and the words stay and the written one is in suggested_pattern; a refused written pattern never empties a kept one; counted as spending |
POST /api/searches/{id}/web |
Search the web: one Muse Spark call with the web_search tool, at most PART_MAX_SEARCHES searches, from the plan's brief led by the device the part came from when it is recorded; each page the model names is kept only when a search result or a citation carries it and it is a web link (one per page, ten at most, a page already in the listings not again), and becomes a listing (source_kind web, source_url) scored like a pasted one; answers the ranked listings, how many were added and dropped, notes, searches, model, from_fixture, cost_usd; 404 for an unknown search, 422 for a closed one, refused before anything is counted; counted as spending; refused to the demo visitor |
PATCH /api/searches/{id}/plan |
JSON pattern and/or exclusions: the owner's pattern, checked before it is kept (422 with the reason), and every listing scored again; makes a plan without a model call when there is none. The pattern runs on RE2, linear in the text: at most 300 characters, case-insensitive, no lookarounds, backreferences, \C, comments or inline flags other than (?i), counted repeats expanding to at most 400, never matching empty text (see the pattern and its limits) |
POST /api/find/pattern |
JSON pattern, exclusions, max_price, text: error when the pattern is refused (the same checks as above), else the score of the text's first 4,000 characters; max_price 0 or null is no limit; no model call |
POST /api/searches/{id}/listings, GET /api/searches/{id}/listings |
JSON texts (1 to 20 listings) and observed_on: each kept up to its first 4,000 characters and scored by the pattern, the exclusion words and the price (verdict fits, maybe or no, highlights with code-point offsets, excluded, price, the first dollar amount stated, reasons); a price at the top price fits, a top price of 0 is no limit, and amounts on both sides of it leave the verdict at maybe; a search keeps at most 50 listings, the oldest unchecked dropped first, then the oldest checked; the answer is the ranked list; no model call; 201 |
POST /api/searches/{id}/listings/screenshots |
multipart screenshots (1 to 6) and observed_on: each read into text by the extractor, then scored; counted as spending, one call per screenshot: 429 for the whole batch when the visitor's hourly or the daily allowance has fewer left; an unknown search (404), a closed one, too many files (422) or a file too large (400) is refused before anything is counted; each listing is kept as it is read; 201 |
POST /api/searches/{id}/listings/{n}/check, DELETE /api/searches/{id}/listings/{n} |
Check fit: the listing read by Muse Spark and assessed as a text candidate, the same candidate when asked again, 409 while a Check fit on it is under way; counted as spending. Remove a listing |
GET /api/preferences, PUT /api/preferences |
the owner's profile and what they want from a build, kept once: interests, effort (low, medium, high), skills (a list of {skill, level}, level none, some, confident or pro; a plain name, sent or stored before the ratings, reads as some), tools, opens_devices, budget, currency, radius_km, purchase (used, new, either) |
GET /api/templates |
the projects that may be proposed, kept as data, each with its requirements |
POST /api/projects/propose |
JSON count (1 to 5) and note: Muse Spark reads the inventory and the templates through tools and proposes projects; only a proposal with at least one allocation the inventory backs is kept, every other allocation is dropped and said; each becomes a project with requirements (pass or unknown, blocking or not, the search opened for it), exploratory allocations, effort, spend_estimate, unknowns, the template's steps, safety and sources; 201; 422 when nothing in the inventory may be used |
GET /api/projects, GET /api/projects/{id} |
every project with the budget line (what the selected set, selected, building or done, expects to spend, what is left); one project |
POST /api/projects/{id}/shortlist, POST /api/projects/{id}/select, POST /api/projects/{id}/abandon |
move the project; select takes JSON take and commits the items, reserving them in the inventory; 409 with conflicts when an item is committed to another selected project, and take: true moves it here, unless the other project is building or done (then 422: that project keeps it) and opens the requirement there; abandoning releases what nobody else holds |
POST /api/projects/{id}/advance/{to} |
a selected project to building, then done; 422 otherwise |
POST /api/projects/{id}/requirements/{key}/search |
a missing requirement becomes a search request under the requirement's profile, with the budget left as its top price (a spent budget is a top price of 0); 422 when the requirement is filled, already searched or unknown |
POST /api/projects/{id}/requirements/{key}/fill |
JSON item_id and why: an item of yours for a requirement the proposal left open; it must be usable and of a kind the requirement takes, and not committed elsewhere; committed at once on a selected project; a search open for the requirement is marked found; 422 otherwise |
POST /api/projects/{id}/pack, GET /api/projects/{id}/packs, GET /api/packs/{id} |
the build pack for a selected, building or done project, written by the rules from what stands: version, digest of the inputs, stale when an item on it, the project or the budget changed since, the markdown; 201 on writing; 422 for a proposed or abandoned project |
GET /api/packs/{id}.md |
the same pack as a Markdown file (text/markdown, Content-Disposition: attachment) |
POST /api/projects/{id}/progress |
JSON step (position from 0) and done: tick or untick one step on a selected or building project; 422 for an unknown step or another state |
POST /api/projects/{id}/outcome |
JSON configuration, test, tested_on (a real past day, YYYY-MM-DD), working, limitations: the owner's record on a project being built; a working result moves it to done, a failed test keeps it building with the record kept; 422 otherwise |
POST /api/projects/{id}/outcome/share, GET /api/projects/{id}/outcome/share |
JSON shared: whether the outcome may be shown to others, off by default; the text for other people once shared (the kinds of items, the result, the set-up, the test and the limits; no names, ids, values, places or prices); 422 without an outcome or while not shared |
POST /api/projects/{id}/picture |
Muse Image draws how the project could look once built, from its title, its result and the kinds of the items allocated to it with up to three accepted values each (none that identify an item, no name, location, note or price); one image per call, only on this request; 201 with the picture (use is the project's title); 404 for a project that is not there; 503 while PICTURE_ENABLED is off. Counted as a call that spends; refused to the demo visitor |
GET /api/projects/{id}/pictures, GET /api/projects/{id}/pictures/{n} |
the pictures drawn for the project and enabled; one as JPEG, 1 first, never cached. Kept under DATA_DIR/projects/{id}/ |
POST /api/captures |
multipart from the phone: photos (up to MAX_PHOTOS, each at most MAX_UPLOAD_MB, re-encoded on arrival), clip (MP4 or 3GP, at most MAX_CLIP_MB), audio (WAV, at most VOICE_MAX_SECONDS) and request (JSON: note, name, find_label); kept privately; 201 with the capture (status received, the counts, audio_seconds); 422 for an empty bundle, too many photos, a file that is not what it says, or a note too long; 422 for an empty clip, a file that is not an MP4 or 3GP video, or an Inbox already holding MAX_CAPTURES; 400 for a part over its size cap (a note may be as long as VOICE_MAX_SECONDS of 16 kHz mono) |
GET /api/captures, GET /api/captures/{id} |
every capture not deleted, newest first; one capture |
GET /api/captures/{id}/photos/{n}, .../clip, .../note |
the media as kept (JPEG, MP4, WAV); 404 for a photo or a file the capture does not have |
POST /api/captures/{id}/process |
read the capture into an item: the label found first when find_label, the photos read, the note transcribed onto the item as the owner's words, the line kept as its notes; the capture gets item_id, the model and the cost_usd; a failure is recorded on the capture (status failed, error) and answered like the other model routes; 422 once processed |
DELETE /api/captures/{id} |
the files go and the capture is hidden; an item made from it stays; 204 |
POST /api/items/{id}/voice |
multipart audio (one WAV, at most VOICE_MAX_SECONDS): Muse Voice Transcribe turns it into text, kept on the item as condition_notes with source voice_note, accepted as the owner's words; returns the transcript, the seconds heard, the cost and the item; 400 for anything that is not a WAV file or is too long |
Errors: 400 for a bad photo, 404 for an unknown item, measurement or profile, 422 for a
request that does not parse, a profile that does not exist, or a feature key that is not
snake_case.
Operational endpoints¶
| Path | Purpose |
|---|---|
GET /api/pair?origin=&api=, GET /api/pair.svg?origin=&api= |
what the phone needs to talk to this backend: the web origin given, or api when the backend has an origin of its own (a scheme, a host, an optional port, nothing more), and, when the demo is gated, a pairing as token with expires_at (a session of PAIR_TOKEN_MINUTES, at most 60, that claims the phone's code) for a browser through the door, or an empty token for the demo visitor and for a pairing, whose phone pairs as the demo visitor; as JSON or as a QR code, the image with X-Pair-Expires-At (the pairing's end, Unix seconds) and X-Pair-Expires-In (the seconds it has left by the server's clock, for a page whose own clock is off) when it carries one, both readable across origins; the access code never leaves the server and nothing is stored; 422 for an origin that is malformed or not one this server is reached at (the request's Host, a proxy's X-Forwarded-Host with the scheme in X-Forwarded-Proto, its Origin or Referer only when the browser marks it Sec-Fetch-Site: same-origin, or CORS_ORIGINS) |
POST /api/pair/code |
the phone's own access code, claimed with the pairing as the bearer token and shown once: code, label (phone and the first eight characters of the pairing's id), expires_at (PAIR_CODE_DAYS, never past the code that signed the page in); one claim per pairing; the code stands while that code does (revoked, expired, or a changed master code ends it); gated: false and no code without a gate; 429 once the address's code attempts are spent; 401 without a live pairing; a successful claim clears the address's count; 403 for a browser's session, a second claim, a phone's own code, when the code that paired the phone has ended, or when that code has PAIR_CODE_MAX phones with current codes already |
GET /healthz |
process is up |
GET /readyz |
config loaded, Model API mode (live, record or fixture; fixture (key failed) when the key did not answer the startup probe; ... for the code, recorded otherwise behind a gate with a key), data directory writable, spend so far against the cap, paused once the cap is reached, and the gate under demo: gated, live_models, paused (the operator's switch), daily_used, daily_cap, visitor_cap_per_hour |
GET /api/version |
package version, the IDs of the models the app calls (spark, image, voice, sam), the upload limits (max_photos, max_upload_mb, max_clip_mb) the web forms follow, and pictures, whether an item's picture may be drawn (PICTURE_ENABLED) |
GET /api/spend |
the spend ledger: total, cap, remaining, calls per model with token counts, live versus fixture calls |
Every response carries X-Request-ID. Send your own to have it echoed back and used in the logs
and in the spend ledger.