API for AI agents
Free, no key. JSON over HTTPS. Read only for AI agents: a swap means a person will be at the place, so swaps come from people in the app.
Read
GET /api/language/summary?city=dc|nyc|sf[&speak=fr&want=en]: open swaps per place this week.{places:{id:{swaps,people,open_seats,matches}},swaps,people,matches,is_ai_counted:false}GET /api/language/place?city=sf&id=w162518029[&speak=fr&want=en&level=beginner]: one place.{name,kind,swaps:[{id,host,speak,want,level,starts,ends,seats_left,match}],nearby,people,ai_partner,week_options}. Handles are random words, never names.
A swap is one hour, two seats: the host and one partner. match is true when the swap’s speak is your want and its want is your speak. nearby lists open matching swaps at other places within 2 km. Language codes: en es fr de it pt zh ja ko ar. Levels: beginner intermediate advanced.
When nobody matches you here or nearby, ai_partner is {"is_ai":true,"counted":false,"label":"AI practice partner",…}: scripted phrase prompts that run on the phone, with no AI model and no network. It is not a person and is not in any count. Otherwise it is null.
Place ids, names and facts come from the static snapshot at /language/data/<city>.json (© OpenStreetMap contributors, ODbL 1.0), the libraries and cafés from Cowork who else?. hours is the raw OSM opening_hours string or null.
Writes
POST /api/language/post
content-type: application/json
{"city":"sf","id":"w162518029","speak":"en","want":"fr",
"level":"beginner","day":"2026-10-31","time":"18:00",
"client_id":"my-agent-0123456789","is_ai":true}
→ 403 {"code":"ai_read_only"}
- Every write with
"is_ai": truereturns 403ai_read_only. AI agents never count as people. - People post, take, leave and cancel swaps in the app, after confirming they are 18 or older.
dayis one of the next 7 days in the city’s time zone;timeis on the hour or half hour, 8am to 9pm, at least 15 minutes away. Taking a swap needs matching languages. We send no messages, emails or notifications. - Allowed fields only: city, id, slot, speak, want, level, day, time, adult, device, client_id, is_ai. Any other field (name, note, phone…) returns 422
no_free_textand nothing is stored. Files and non-JSON bodies return 415. - Other refusals: 422
bad_speak,bad_want,same_language,bad_level,bad_time,place_closed(the place’s mapped hours don’t cover the whole slot; unknown hours are allowed),adult_required,no_match(taking a swap whose languages don’t fit yours); 409full,started,own_slot,already_seated,overlap,max_open(3 upcoming swaps per person); 429 when a daily limit is reached; 503language_offwhen the kill switch is on (reads keep working,writes_open:false).
Limits
- Per device per day: 6 posts, 12 takes, 20 leaves, 20 cancels; at most 3 upcoming swaps you host, and nothing that overlaps your other swaps. 80 writes per network per day, 5,000 writes per day for the app.
- Swaps disappear when they end. Rows are deleted about 2 days later.
A who else? lab experiment. Place data © OpenStreetMap contributors, ODbL 1.0.