/at/<slug>/lucerna.json
Next to every booking page. The page itself carries a <link rel="alternate" type="application/json"> pointing here, and a
footer line agents can read off the screen.
agents · lucerna.json v1 · open spec
Every shop hosted here publishes a manifest: what it sells, what things cost, and exactly which API endpoints book an appointment. No scraping, no guessing. One GET and your agent knows everything the booking page knows.
GET /at/<slug>/lucerna.json
Discovery
Next to every booking page. The page itself carries a <link rel="alternate" type="application/json"> pointing here, and a
footer line agents can read off the screen.
A shop on its own domain serves its manifest at the well-known root. Same document, shorter walk — start at any hostname and find the shop behind it.
On our own hosts the well-known path answers with the platform manifest
("kind": "platform"): where shop manifests live and where to sign up.
The same document in prose — for a model deciding whether to use a shop rather
than how to call it. It is rendered from the manifest, not written
alongside it, so the two can never disagree. /llms.txt at any host root
answers too: the platform brief on ours, the shop’s own on its domain.
The document
{
"lucerna": 1,
"kind": "shop",
"slug": "marrow-atelier",
"name": "Marrow Atelier",
"type": "services",
"human_url": "https://lucernanoetica.com/at/marrow-atelier",
"api": {
"base": "https://bookings.lucernanoetica.com",
"auth": "public bearer token — request one at https://lucernanoetica.com/agents",
"endpoints": {
"services": "/v1/t/marrow-atelier/services",
"availability": "/v1/t/marrow-atelier/availability?service_id&date",
"book": "POST /v1/t/marrow-atelier/bookings",
"ticket": "/v1/t/marrow-atelier/bookings/:ticket",
"event_ics": "/ics/e/:ticket.ics"
}
},
"capabilities": ["bookings", "deposits", "tips"],
"doors": { "book": "/", "tip": "/tip", "quote": "/quote", "page:terms": "/p/terms" },
"timezone": "America/Denver",
"about": { "tagline": "bones, well set", "location": "Denver, CO" },
"socials": [{ "network": "instagram", "url": "https://instagram.com/marrow" }],
"links": [
{ "label": "Instagram", "url": "https://instagram.com/marrow", "network": "instagram" },
{ "label": "hi@marrow.example", "url": "mailto:hi@marrow.example", "network": "email" }
],
"map": [
{ "at": 1, "kind": "hero", "title": "Marrow Atelier", "text": "bones, well set" },
{ "at": 2, "kind": "text", "title": "About", "text": "Two chairs. Walk-ins Thursdays." },
{ "at": 3, "kind": "collection", "title": "Services", "does": "Pick a service and a time and book it." },
{ "at": 4, "kind": "jar", "does": "Leave a tip." }
],
"concierge": {
"version": 3,
"flow": "https://platform.lucernanoetica.com/v1/t/marrow-atelier/concierge",
"walk": "POST https://platform.lucernanoetica.com/v1/t/marrow-atelier/concierge/walk",
"human": "/quote"
},
"payments": { "fiat": "stripe", "escrow": "preview" },
"services": [
{
"id": "svc_01…",
"name": "Consultation",
"duration_min": 30,
"price_cents": 5000,
"deposit_cents": 1000,
"currency": "usd",
"rails": "both"
}
],
"cli": "curl -H "authorization: Bearer $LUCERNA_TOKEN" https://bookings.lucernanoetica.com/v1/t/marrow-atelier/services",
"commands": [
{ "run": "curl -H "authorization: Bearer $LUCERNA_TOKEN" https://bookings.lucernanoetica.com/v1/t/marrow-atelier/services", "does": "list the live services" }
],
"generated_at": "2026-07-07T12:00:00.000Z"
} lucerna: 1 is the spec version. Breaking changes bump it; additive fields
don't.price_cents, deposit_cents) —
no floats, ever.api.endpoints are relative to api.base. Reads and writes both
take a public bearer token — ask us for one.cache-control: max-age=300, generated_at stamps it). Availability is the one thing you always fetch
live.map is the page itself, top to bottom — every section in the order a
visitor meets it, the owner’s own words with their line breaks intact, and a does line wherever there is something to act on. It is derived from the
same document the human page renders from, so it cannot drift from what a person
sees. A kind your client doesn’t recognise still arrives: report it,
don’t choke on it.capabilities is what the shop can do; doors is where — real pages, relative to human_url, one per live
capability. Join them (human_url + "/tip"); resolving "/tip" as a URL against the origin lands on a platform page instead of
the shop’s jar. A capability with no door is just a word, so we never list one we
can’t verify.links and socials are every outbound link on the page,
deduplicated, in page order — including an address the owner typed into their own
prose. network is set only where we recognise the host.timezone is the shop’s wall clock; every concrete time on availability and book sits on it. Absent means the shop
hasn’t set one — treat times as already-local rather than assuming a zone.map, links, socials, about, timezone) are omitted, never empty, when we couldn’t read them. Absent means “we
could not look”; [] would mean “we looked and there is nothing there”.commands lists only verbs the published CLI actually implements. We
don’t advertise a command that errors in your terminal, the same way we don’t
advertise a door that 404s.payments.escrow is per-shop. The non-custodial escrow rail is live on Stellar mainnet; a shop’s field reads "preview" until
that shop is on the live rail, then "stellar" — your agent reads the field, it
doesn't redeploy.The API behind it
GET /v1/t/<slug>/services Live services: id, name, duration, price and deposit in minor units, currency.
GET /v1/t/<slug>/availability?service_id&date Open ISO-8601 start times for a service on a day, with the shop’s timezone.
POST /v1/t/<slug>/bookings Make the booking. Returns a ticket, a status, and a deposit checkout URL when the service takes one.
GET /v1/t/<slug>/bookings/<ticket> Check a booking by its ticket — the reference the human also holds.
GET /ics/e/<ticket>.ics The booking as a calendar event — drop it straight into any calendar.
GET platform /v1/t/<slug>/concierge The shop’s guided intake as a document: every question, the catalog it prices against, the formula, how it ends.
POST platform /v1/t/<slug>/concierge/walk Walk it. Omit `walk` to start; then send {walk, input} per turn. Ends by emailing the quote and sealing the conversation as a receipt the owner reads.
If your client speaks MCP
The shop grammar is also an MCP server, hosted — so there is no package to install and no
version of ours to keep up with. Add the URL and your client gets shop_lookup, concierge_document and concierge_walk.
Send a shop's own agent key as a bearer token and it also gets that shop's operator tools —
including flow_publish, which is how an AI builds a shop's front-of-house from
what its owner tells it.
https://platform.lucernanoetica.com/v1/mcp
GET that URL in a browser for the tool list and the exact add command. An agent key can configure a shop and can never move its money — there is no tool here that charges, refunds, releases or pays out, by construction. Full write-up: the agent face.
Try it
$ curl -s https://lucernanoetica.com/at/marrow-atelier/lucerna.json
$ curl -s https://lucernanoetica.com/at/marrow-atelier/llms.txt
$ curl -H "authorization: Bearer $LUCERNA_TOKEN" \
https://bookings.lucernanoetica.com/v1/t/<slug>/services That is the whole agent face: one manifest to read, one HTTP API to act on, and no client library to trust — the full booking walkthrough. A packaged CLI and an MCP server are built but not yet published; until they are, this is the supported path.
lucerna.json v1 is an open spec — publish it for your own shop software if you like, and tell us what your agent needs that isn't here yet.
Talk to us