theuniversaldevelopers

The Dial Dictionary and the live commons, open to build on.

theuniversal is the shared road network that Dialin, and any app you build, drives on: what people are tasting, signalling and looking for, right now, without anyone being singled out. The Dial Dictionary (CC BY 4.0) and DIOS (Apache 2.0, being published) are open, so anyone can build their own on-ramp. Accounts, trust, points and tastes stay governed on theuniversal — see the terms.

Open now API v1 with a key — the map and the dictionary at /api/v1, never cached, so it's live. Get a key (any Dialin account). The keyless endpoints below stay open while v1 settles.
Open now Offers through the API for verified businesses and verified people: make, change and close offers, see how many people a target reaches, and hear reserves and redeems by webhook the moment they happen.
Coming On-ramps: your own app where people broadcast what they're into and receive offers, signed in with their universal account.

API v1

GET /api/v1
The routes, what each one needs, and what it answers. No key needed for this one. The API also lives at its own name: https://api.theuniversal.io/<route> is the same as https://dialin.io/api/v1/<route>.
curl https://dialin.io/api/v1/map/live \ -H "Authorization: Bearer tu_xxxxxxxx_…" {"data":{…},"meta":{"api":"v1","route":"map/live","attribution":"Data: theuniversal by Dialin","at":"…"}}
Live: GET /api/v1/stream — every pulse as it happens, as server-sent events: dial (a category and its core four, e.g. Italian · Pizza), signal.lit / signal.dim, search (the words only), offer.open / offer.close and meet, each on a randomized ~2 km cell. Resume with Last-Event-ID. Browsers' EventSource can't send a key, so read it from your server. Or poll /api/v1/map/pulses?after=<last id>. Pulses are kept 24 hours.
curl -N https://dialin.io/api/v1/stream -H "Authorization: Bearer tu_…" id: 1042 event: dial data: {"id":1042,"at":"…","kind":"dial","cell":"h2:-3:7","lat":41.9,"lng":-87.66,"cat":"food","axes":{"cuisine":["Italian"]}}
Routes: me · stream · map/pulses · dictionary · dictionary/dials · map/live · map/flow · map/searches · map/snapshot · map/pulse · map/axes · rank — the same answers as the keyless endpoints below, with the same blur. A key buys accountability and a budget, not a wider view.
Each key: 120 calls a minute, 20,000 a day; map/snapshot 20 a minute, 1,000 a day; making offers 10 a minute, 200 a day. Over that you get a 429 with Retry-After. Keep keys on your server; revoke one any time at your keys.

Offers

For keys made by a verified business (owns a verified place) or a verified person (verified phone) — keys from your keys carry offers:read and offers:write. A key made for one place only sees and makes offers there. Offers made here are ordinary Dialin offers: people find, reserve and redeem them in the app like any other.
curl -X POST https://api.theuniversal.io/offers \ -H "Authorization: Bearer tu_…" -H "Content-Type: application/json" \ -H "Idempotency-Key: chai-2026-11-08" \ -d '{"title":"Free chai with any thali","points_cost":1,"inventory":null, "ends_at":"2026-11-30T22:00:00-06:00", "target":{"dials":["cuisine:Indian"],"facets":{"age_band":["25-34","35-44"]}}}' 201 {"data":{"id":"…","live":true,"reserved":0,"redeemed":0,"made_by":"Your app",…}}
GET offers (?live=1) · POST offers · GET offers/:id · PATCH offers/:id (anything but points_cost and place; {"active":false} pauses) · DELETE offers/:id (closes; codes already out still redeem) · POST offers/reach.
Fields: title (required), points_cost 1–1000, inventory (null = unlimited, default 1), place (fsq_id; a place key fills it in), item_id (something you own, offered from a place), starts_at, ends_at, min_trust 0–100, description, fine_print, photo_url (https), target. A target takes facets (who: {"age_band":["25-34"]}) and dials (tastes: "cuisine:Korean", or a bare slug for any). It can't name a person — that stays in the app. Unknown fields are refused, so typos don't fail quietly. Send Idempotency-Key to retry a create safely.
Reach: POST offers/reach {"target":{…},"min_trust":0} answers how many real people match, in fives — or just "fewer than 5". 10 a minute, 100 a day.

Webhooks

POST webhooks {"url":"https://…","events":["offer.reserved","offer.redeemed","offer.released"]} — sent the moment someone reserves, redeems, or gives a code back, for your offers (at the key's place, if it has one). The secret comes back once. Also GET webhooks, POST webhooks/:id/ping, DELETE webhooks/:id. Up to 5 per app; https to a public host name only.
POST https://you.example.com/hooks X-Universal-Event: offer.redeemed X-Universal-Signature: t=1793140000,v1=5f2c… {"id":88,"event":"offer.redeemed","at":"…","data":{"offer":{"id":"…","title":"Free chai","inventory":null},"ref":"9a41c0…","points":1}}
Check it: hex(hmac_sha256(secret, t + "." + raw_body)) == v1, on the raw bytes, before parsing, and refuse old t. ref is the same from reserve to redeem and different for every webhook — it's never a person. Answer 2xx within 5 seconds; otherwise we retry after 1 min, 5 min, 30 min, 2 h and 6 h, then stop.

What you can never get

The dictionary

GET /api/dictionary
Every kind of thing and the dials it's described by — names, slugs, types and the named positions on each dial. The shared language: use the same words and you mean the same thing.
{"schemaVersion":2,"count":205,"subtypes":[{"typeSlug":"restaurant","dials":[{"name":"Cuisine","slug":"cuisine","type":"multi-choice","frames":["Italian","Mexican",…]}…]}…]}
GET /api/exchange/dials?category=food
The dial table for a category: the core four, the most-dialed dials people added, and the rating every thing carries.
GET /api/exchange/rank?dial=spice&lat=41.88&lng=-87.63&radius=8000&q=egg%20roll&order=desc&limit=20
Things near a point ranked by any dial's community consensus — "the spiciest egg rolls", "the least greasy fries".

The live commons

GET /api/commons/live
Right now: every lit bat signal (its taste and a randomized cell), every open offer (a randomized cell only — never what it is), the pairs that could meet, and recent meets. Refreshes every few seconds.
GET /api/commons/snapshot?tastes=food,pizza&facets={"age_band":["25-34"]}
A blurred snapshot of the people: how many match, and the tastes and demographics 5 or more of them share, in fives.
GET /api/commons/flow?since=2026-10-01T00:00:00Z&limit=500
Groups of 5 or more released into the commons over time — cell, kind, taste, how many, and who they are in shared demographics.
GET /api/commons/searches?hours=24
What people are looking for in Dialin, with how many looked — never who or where.
GET /api/commons/pulse · GET /api/commons/axes
Coarse totals (people on Dialin, released this week), and the core dials as the world is dialing them.

Fair use