Your drops, from your own apps and AI
Everything the Drops app shows you is readable from outside it: every drop, who signed up, where everyone is in line, how each list turned up, what guests said. A REST API under /v1/ for your code, and an MCP server for Claude, Cursor, ChatGPT and any other AI tool.
https://dagobah.hellagooddrops.com
Six reads. An AI or an app can see your drops; it cannot change them.
Included in the price. One key per account, made and revoked by the owner.
curl https://dagobah.hellagooddrops.com/v1/drops \
-H "Authorization: Bearer hgd_live_YOUR_KEY"
const r = await fetch("https://dagobah.hellagooddrops.com/v1/drops", { headers: { Authorization: "Bearer hgd_live_YOUR_KEY" } }); const { drops } = await r.json(); console.log(drops.map(d => `${d.code} ${d.name}: ${d.here} here`));
import requests r = requests.get("https://dagobah.hellagooddrops.com/v1/drops", headers={"Authorization": "Bearer hgd_live_YOUR_KEY"}) for d in r.json()["drops"]: print(d["code"], d["name"], d["here"], "here")
Your key
The owner makes the key in the app: ☰ › Account, bottom of the page, Create key. It is shown once. Send it on every request:
Authorization: Bearer hgd_live_q6Yslk…
Keys start with hgd_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.
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. A drop's line moves 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": { "code": "drop_not_found", "message": "No drop with that id or code on this account." } }invalid_keyNo key, a malformed key, or a revoked one.no_scopeThe key cannot do this (every key can read).drop_not_foundNo drop with that id or code on this account.not_foundNo such route.method_not_allowed/v1/ takes GET only; /mcp takes POST only.rate_limitedOver the minute's budget. Wait retry-after seconds.server_errorOur side. Try again.REST
All times are ISO 8601 in UTC; each drop carries its own time zone. :drop is a drop's id or its code, the letters at the end of its signup link (K7MRQ). Ids are the app's own and stay the same for the life of a drop or a guest. A drop's status is prelaunch (a test run), open, paused, nowait (No waiting), closed or finished.
/v1/accountThe business the key belongs to.
| Field | Meaning |
|---|---|
| name · slugstring | The business, as guests see it. |
| tagsstring[] | The lists it hands out with its signup links, e.g. vip. |
| drops{total, live, prelaunch, finished} | How many drops, by where they are. |
| keyobject | The key you sent: its prefix and scopes. |
{
"name": "Northside Sneakers", "slug": "northside-sneakers",
"tags": ["vip", "earlybird"],
"drops": { "total": 6, "live": 1, "prelaunch": 1, "finished": 4 },
"key": { "prefix": "hgd_live_q6Yslk", "scopes": ["read"] }
}/v1/dropsEvery drop on the account, newest first, with the four numbers on its home-screen card.
| Field | Meaning |
|---|---|
| code · name · statusstring | The drop, and where it is. |
| flowstring | ladder: waves climb steps to one door. lanes: several tables, each its own door. |
| starts_at · ends_at · tztime, string | The event's own times and zone. |
| addressobject | street, city, state, zip, parking, note. |
| signup_urlstring | The drop's signup link. |
| signed_up · turned_up · here · doneint | Signed up; checked in; checked in and not yet called in; called in (Lanes) or through the door (Ladder). |
| closes_attime|null | When the drop's 72 hours end, once its clock has started. |
{
"count": 6,
"drops": [
{ "id": "a4d98404-…", "code": "K7MRQ", "name": "Summer Collab Release",
"status": "open", "flow": "lanes", "tz": "America/Los_Angeles",
"starts_at": "2026-10-18T17:00:00Z", "ends_at": "2026-10-19T01:00:00Z",
"address": { "street": "1450 Market St", "city": "San Francisco", … },
"signup_url": "https://go.hellagooddrops.com/e/K7MRQ",
"signed_up": 269, "turned_up": 245, "here": 58, "done": 187, … }
]
}/v1/drops/:dropOne drop right now, as its board shows it. A Ladder drop answers steps: each step and the wave on it, the door step last. A Lanes drop answers lanes: how many are waiting for each one and its last call. Both carry the run card.
| Field | Meaning |
|---|---|
| countsobject | signed_up, turned_up, here, done, and ready_to_call: who could be called next. |
| steps[].waveLadder · object|null | The wave on that step: its number, letter, people, when it was made and told. door marks the step people go in through. |
| lanes[].waiting · last_callLanes | Waiting for this lane, and its last call: wave, letter, when, how many. |
| runobject | checked_in, waiting, in_line, finished, no_show; in Ladder also waves_left, minutes_per_wave and ends_about (the median pace times the waves left, once two waves are done). |
| check_in · tickets_shownboolean | The drop's own settings. Lanes adds lanes_are_different and one_turn_only. |
{
"drop": { "code": "K7MRQ", "name": "Summer Collab Release", "status": "open", "flow": "lanes",
"check_in": true, "lanes_are_different": true, … },
"counts": { "signed_up": 269, "turned_up": 245, "here": 58, "done": 187, "ready_to_call": 58 },
"lanes": [
{ "key": "hats", "name": "Hats", "max": 20, "waiting": 26,
"last_call": { "wave": 12, "letter": "M", "at": "2026-10-18T19:42:10Z", "people": 5 } },
{ "key": "tees", "name": "Tees", "max": 20, "waiting": 11, "last_call": { … } }
],
"run": { "checked_in": 245, "waiting": 58, "called": 187, "no_show": 24, "lanes": 4, … },
"now": "2026-10-18T19:45:00Z"
}/v1/drops/:drop/people?status=hereThe list: everyone who signed up, the ones still to come in first. Names and times only: a guest's phone number, email and pass never leave the app. status is all (default), here, not_here, in_wave or done; tag keeps one list; q finds part of a name; limit is 1 to 500, default 100.
| Field | Meaning |
|---|---|
| countsobject | all, here, not_here, in_wave, done, and how many on each list. |
| people[].tagstring|null | The list their signup link carried. |
| people[].checked_in_at · called_at · done_attime|null | When they checked in, were called, came through. |
| people[].via_signboolean | Signed up at the check-in sign rather than from a link. |
| people[].wave · stepLadder | The wave they are in, and the step it is on. |
| people[].lane · turnsLanes | The lane they picked, and each turn: lane, status, verified at the table. |
{
"drop": { "code": "K7MRQ", "name": "Summer Collab Release", "flow": "lanes" },
"status": "here",
"counts": { "all": 269, "here": 58, "not_here": 24, "in_wave": 187, "done": 0, "tags": { "vip": 28 } },
"people": [
{ "id": "b1e22775-…", "name": "Teresa A.", "tag": "vip", "via_sign": false,
"signed_up_at": "2026-10-15T22:29:09Z", "checked_in_at": "2026-10-18T17:12:40Z",
"called_at": null, "done_at": null, "door_scans": 0,
"lane": "Hats", "turns": [ { "lane": "Tees", "status": "done", "verified": true } ] }
]
}/v1/drops/:drop/metricsThe night in numbers, the app's Metrics screen. All timestamps, nothing estimated.
| Field | Meaning |
|---|---|
| totalsobject | registered, turned_up, came_inside, never_showed, at_sign. |
| by_list[]list, signed_up, turned_up, served | Per signup list. null is everyone with no list. The number to act on: send more of the links that turn up. |
| lists_with_no_signups[]list, closed_at | Links that brought nobody. |
| by_hour[]at, n | Check-ins per hour. |
| by_lane[]Lanes | Each lane: called, calls, last_at, waiting. |
| waves[]no, step, people, minutes | Every wave, with minutes from told to done. |
{
"drop": { "code": "Q3TZB", "name": "Closing Sale", "status": "finished", "flow": "ladder" },
"totals": { "registered": 304, "turned_up": 264, "came_inside": 220, "never_showed": 40, "at_sign": 0 },
"by_list": [
{ "list": null, "signed_up": 262, "turned_up": 227, "served": 213, "at_sign": 0 },
{ "list": "vip", "signed_up": 11, "turned_up": 9, "served": 8, "at_sign": 0 }
],
"by_hour": [ { "at": "2026-10-04T17:00:00Z", "n": 179 }, … ],
"waves": [ { "no": 1, "step": "Inside", "people": 25, "minutes": 18, … } ]
}/v1/drops/:drop/reviews?limit=50The drop's star ratings: the count, the average, the spread, and the newest first with what the guest wrote. limit is 1 to 500, default 50.
{
"drop": { "code": "K7MRQ", "name": "Summer Collab Release" },
"count": 41, "average": 4.7, "spread": { "1": 0, "2": 1, "3": 2, "4": 5, "5": 33 }, "commented": 12,
"reviews": [
{ "stars": 5, "comment": "Got coffee and came back when my phone said go.", "at": "2026-10-18T20:04:18Z", "guest": "Sophia P." }
]
}/mcpMCP: your drops 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://dagobah.hellagooddrops.com/mcp with your key in the same Authorization header, then ask it things like "how's the line at the drop right now?", "which list turned up best on Saturday?" or "any reviews mention parking?"
| Tool | Returns |
|---|---|
| get_account | The business, its lists, how many drops. |
| list_drops | Every drop, its status and its four numbers. |
| get_drop drop | One drop right now: steps or lanes, the waves, the run card. |
| get_drop_people drop, status, tag, q, limit | The list: names, lists, where each person is. |
| get_drop_metrics drop | By list, by hour, by lane, every wave. |
| get_drop_reviews drop, limit | Star ratings and comments. |
Each tool answers with text and structuredContent (the same JSON as the REST read). A drop that is not on the account answers isError. Same limits as REST. One JSON-RPC message per POST; the server keeps no session.
Connect it
claude mcp add --transport http drops https://dagobah.hellagooddrops.com/mcp \
--header "Authorization: Bearer hgd_live_YOUR_KEY"
// claude_desktop_config.json — through the mcp-remote bridge { "mcpServers": { "drops": { "command": "npx", "args": ["-y", "mcp-remote", "https://dagobah.hellagooddrops.com/mcp", "--header", "Authorization: Bearer hgd_live_YOUR_KEY"] } } }
// .cursor/mcp.json { "mcpServers": { "drops": { "url": "https://dagobah.hellagooddrops.com/mcp", "headers": { "Authorization": "Bearer hgd_live_YOUR_KEY" } } } }
curl https://dagobah.hellagooddrops.com/mcp \ -H "Authorization: Bearer hgd_live_YOUR_KEY" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_drops","arguments":{}}}'
- Make a key in the app (☰ › Account › Create key).
- 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.
- Ask. The tool list is small on purpose: an AI reads your drops, it does not run them.
Public text
Changes
| 2026-10-16 | First release: six reads under /v1/, MCP at /mcp, self-serve keys in the app. |
Questions, or something you need that is not here: hello@hellagooddrops.com.