agents · lucerna.json v1 · open spec

Shops that machines can read.

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

Four ways in, one document out.

hosted page

/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.

custom domain

/.well-known/lucerna.json

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.

platform root

lucernanoetica.com/.well-known/lucerna.json

On our own hosts the well-known path answers with the platform manifest ("kind": "platform"): where shop manifests live and where to sign up.

plain text

/at/<slug>/llms.txt

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.json, kind: shop

{
  "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.
  • Money is integers in minor units (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.
  • The document is a five-minute snapshot (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.
  • Fields describing the page (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

Seven endpoints: book an appointment, or earn a quote.

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

One URL. Nothing to install.

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

Two curls, and you have the shop.

$ 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.

Building an agent against this?

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