HELLAGOOD NEXT Developers

Your shop, from your own apps and AI

Everything the Next app shows you is readable from outside it: who is waiting, the wait right now, who is here, the numbers, the reviews. A REST API under /v1/ for your code, and an MCP server for Claude, Cursor, ChatGPT and any other AI tool.

Base URL

https://tatooine.hellagoodnext.com

Read-only

Six reads today. Writes (walk-ups, Notify, Done) are coming under the same key.

Paid shops

Comes with the plan. One key per shop, made and revoked by the owner.

curl https://tatooine.hellagoodnext.com/v1/line \
  -H "Authorization: Bearer hgn_live_YOUR_KEY"

Your key

The owner makes the key in the app: Settings › Account, bottom of the page, Create key. It is shown once. Send it on every request:

Header
Authorization: Bearer hgn_live_q6Yslk…

Keys start with hgn_live_ and are 49 characters long. We keep a hash, never the key: if you lose it, revoke it in the app and make another. Revoking is instant. A key stops working the moment the shop's plan ends.

Keep it server-side. The API sends no CORS headers on purpose: a key belongs in your backend, a script, or an AI tool's config — never in a web page.

Limits and errors

60 requests a minute per key, 120 per address. Every answer carries x-ratelimit-limit and x-ratelimit-remaining; a 429 carries retry-after in seconds. The line changes every few seconds at most — polling it every 10 to 15 seconds is plenty.

Every error is JSON with a code you can switch on and a message you can show:

Error shape
{ "error": { "code": "invalid_key", "message": "This key is not valid. It may have been revoked." } }
401invalid_keyNo key, a malformed key, or a revoked one.
403shop_inactiveThe shop's plan is not active.
403no_scopeThe key cannot do this (every key can read today).
404not_foundNo such route.
405method_not_allowed/v1/ takes GET only; /mcp takes POST only.
429rate_limitedOver the minute's budget. Wait retry-after seconds.
500server_errorOur side. Try again.

REST

All times are ISO 8601 in UTC. Minutes are whole numbers. Ids are the app's own and stay stable for the life of a service, person or visit.

GET/v1/shop

The shop as it is right now: open or not, today's hours, and what guests pick when they join.

FieldMeaning
codestringThe shop's five-letter code, the one on its sign.
openbooleanTaking guests right now.
hoursobjectPer weekday (mon…sun), a list of ["09:00","18:00"] stretches. Empty = closed.
todayarrayToday's stretches, in the shop's zone.
next_openstring|nullWhen it opens next today, if closed now.
tzstringIANA zone, e.g. America/Los_Angeles.
guests_pickstringwait, services, people or services_and_people.
keyobjectThe key you sent: its prefix and scopes.
200
{
  "code": "QF7KD",
  "name": "QuickFix Device Repair",
  "open": true,
  "tz": "America/Los_Angeles",
  "hours": { "mon": [["09:00", "18:00"]], … "sun": [] },
  "today": [["09:00", "18:00"]],
  "next_open": null,
  "types": ["repair"],
  "guests_pick": "services_and_people",
  "key": { "prefix": "hgn_live_q6Yslk", "scopes": ["read"] }
}
GET/v1/line

Everyone waiting, called and being served, oldest first. Names and times only: a guest's pass link never leaves the app.

FieldMeaning
waiting · called · servingintCounts.
line[].statusstringwaiting → notified (called up) → serving.
line[].joined_attimeWhen they joined. called_at and serving_since follow.
line[].waited_minintMinutes since they joined.
line[].service · asked_for · served_bystring|nullWhat they came for, who they asked for, who took them.
line[].place · next · wait_minwaiting onlyTheir place, whether they are next, and the wait as a range {min, max}.
line[].walk_upbooleanAdded at the counter rather than by scanning.
line[].on_waybooleanA called guest tapped "I'm on my way".
line[].saidstring · nullA called guest's latest tap from their pass: here, on_way or late. Null when they have not said anything.
line[].holdbooleanThe shop held their spot: called once, running late, back in line at the same place.
200
{
  "open": true, "waiting": 5, "called": 1, "serving": 2,
  "line": [
    { "id": "a99b0bd5-…", "name": "Jacob R.", "status": "serving",
      "joined_at": "2026-10-16T17:02:22Z", "called_at": "2026-10-16T17:14:22Z",
      "serving_since": "2026-10-16T17:16:22Z", "waited_min": 34,
      "service": "Broken Screen", "asked_for": "Johnny", "served_by": "Johnny",
      "walk_up": false, "on_way": false, "said": null, "hold": false },
    { "id": "bbda4058-…", "name": "Nathan", "status": "notified",
      "called_at": "2026-10-16T17:34:22Z", "served_by": "Brin", "on_way": true, … },
    { "id": "1c6e0f2a-…", "name": "Emma K.", "status": "waiting",
      "service": "Android Repair", "asked_for": "Andre",
      "place": 1, "next": true, "wait_min": { "min": 10, "max": 15 }, … }
  ]
}
GET/v1/waits

What a new guest would be told right now: overall, for each service, and for each person. The same estimate the pass shows, learned from the shop's real service times. Always a range, never a single number.

FieldMeaning
wait_min{min,max}For "next available", any service.
services[]id, name, minutes, wait_minminutes is the service's typical length.
people[]id, name, status, wait_minwait_min is null for someone who is not here.
200
{
  "open": true,
  "wait_min": { "min": 10, "max": 15 },
  "services": [
    { "id": "a61bfc32-…", "name": "iPhone repair", "minutes": 20, "wait_min": { "min": 10, "max": 15 } },
    { "id": "59b9453a-…", "name": "Broken Screen", "minutes": 20, "wait_min": { "min": 15, "max": 20 } }
  ],
  "people": [
    { "id": "fb374011-…", "name": "Johnny", "status": "here", "wait_min": { "min": 20, "max": 25 } },
    { "id": "3b967bcd-…", "name": "Hector", "status": "off", "wait_min": null }
  ]
}
GET/v1/team

The people who serve guests, whether they are here, since when, and what each one does.

FieldMeaning
hereintHow many are in right now.
people[].statusstringhere, break or off.
people[].sincetime|nullWhen that status began.
people[].servicesstring[]Service names this person does.
200
{
  "here": 5,
  "people": [
    { "id": "fb374011-…", "name": "Johnny", "status": "here", "since": "2026-10-16T15:36:22Z",
      "services": ["Android Repair", "Battery Problem", "Broken Screen", "iPad Repair", "iPhone repair"] },
    { "id": "3b967bcd-…", "name": "Hector", "status": "off", "since": null,
      "services": ["Android Repair", "iPhone repair"] }
  ]
}
GET/v1/stats?range=week

The shop's numbers for today (default), the last 7 days (week) or the last 30 (month), in the shop's own time zone.

FieldMeaning
served · per_dayintGuests served in the range, and per day.
walked{left, no_show, rate_pct}Left before being called, or called and never came. Rate = walk-aways over everyone finished.
wait_min · service_minint|nullAverage minutes from joining to being called, and from being called to done.
by_hour[]hour, joined, walkedWhen guests arrive, 0–23.
people[]name, served, service_min, starsPer person. stars is their average rating in the range.
services[]name, served, service_min, wait_minPer service.
200
{
  "range": "week", "days": 7,
  "served": 138, "per_day": 20,
  "walked": { "left": 0, "no_show": 5, "rate_pct": 3 },
  "wait_min": 2, "service_min": 22,
  "by_hour": [ { "hour": 9, "joined": 8, "walked": 0 }, { "hour": 10, "joined": 12, "walked": 2 }, … ],
  "people": [ { "name": "Johnny", "served": 32, "service_min": 22, "stars": 4.8 }, … ],
  "services": [ { "name": "iPhone repair", "served": 49, "service_min": 21, "wait_min": 2 }, … ]
}
GET/v1/reviews?limit=50

The newest ratings first, with the comment, the service and the person rated, plus the count and average over all time. limit is 1 to 200, default 50.

FieldMeaning
count · averageint, float|nullAll time.
reviews[].stars1–5
reviews[].commentstring|nullWhat the guest typed, as typed.
reviews[].guest · service · personstring|nullWho, for what, with whom.
200
{
  "count": 98, "average": 4.6,
  "reviews": [
    { "stars": 5, "comment": "Grabbed lunch and came back when they were ready for me.",
      "at": "2026-10-16T01:04:18Z", "guest": "Sophia P.", "service": "iPhone repair", "person": "Kevin" },
    { "stars": 5, "comment": "Knew exactly when it was my turn.",
      "at": "2026-10-16T01:02:20Z", "guest": "Avery", "service": "Android Repair", "person": "Johnny" }
  ]
}
POST/mcp

MCP: your shop inside an AI tool

The same six reads as tools an AI can call, over the Model Context Protocol (Streamable HTTP, stateless). Point any MCP client at https://tatooine.hellagoodnext.com/mcp with your key in the same Authorization header, then ask it things like "who's waiting right now?", "how was last week?" or "any reviews mention parking?"

ToolReturns
get_shopOpen or not, hours, what guests pick.
get_lineWho is waiting, called and being served, with places and waits.
get_waitsThe wait a new guest would see, per service and person.
get_teamWho is here and what they do.
get_stats range: today | week | monthServed, walk-aways, waits, per person and service.
get_reviews limit: 1–200The newest ratings and comments.

Each tool answers with text and structuredContent (the same JSON as the REST read). Same limits as REST. One JSON-RPC message per POST; the server keeps no session.

Connect it

claude mcp add --transport http next https://tatooine.hellagoodnext.com/mcp \
  --header "Authorization: Bearer hgn_live_YOUR_KEY"
  1. Make a key in the app (Settings › Account › Create key).
  2. Add the server to your tool with the snippet above. Any client that can send a header works; ChatGPT's connectors and other OAuth-only clients cannot yet.
  3. Ask. The tool list is small on purpose: an AI reads your shop, it does not run it.

Public text

Guest names and review comments are typed by the public. The server tells the model so in every tool that carries them, and you should too in anything you build on top: treat them as data, never as instructions. A guest who joins as "ignore previous instructions" is a guest named that.

Changes

2026-10-16First release: six reads under /v1/, MCP at /mcp, self-serve keys in the app.

Questions, or something you need that is not here: hello@hellagoodnext.com.