Post and manage your own notes, events and lists from a script
Open the map, then your profile โ Edit profile โ API keys โ New key. Name it after what will use it ("seeding script"). The key is shown once: copy it somewhere safe. You can have up to 10 keys, and revoke any of them from the same screen.
A key acts as you, on your own notes only. It can't read anyone else's private notes, and it can't do anything an admin can.
Send the key in an Authorization header, and JSON bodies with Content-Type: application/json. Check that a key works:
curl https://leelamaps.com/api/v1/me \
-H "Authorization: Bearer YOUR_KEY"
Errors come back as {"error": "โฆ", "field": "โฆ"} with a 4xx status: 400 (a field is wrong โ field names it), 401 (no key, or a revoked one), 403, 404 (no such note of yours), 409, 422 (a place we couldn't find for sure โ reason says why, see Where a note goes), 429 (over a limit), 503 (the place lookup is down โ try again).
| Request | Does |
|---|---|
| GET/api/v1/me | Whose key this is. |
| POST/api/v1/notes | Create a note โ or update yours with the same external_id. Returns 201 with "created": true, or 200 with "updated": true. |
| GET/api/v1/notes | Your notes, newest first. Filters: list_id, external_id. Paging: limit (max 100), then cursor = the next_cursor you were given. |
| GET/api/v1/notes/<id> | One of your notes. |
| PATCH/api/v1/notes/<id> | Change or move a note. Only the fields you send change. |
| DELETE/api/v1/notes/<id> | Delete a note. |
| POST/api/v1/notes/<id>/publish | Publish a draft now. |
| GET/api/v1/lists | Your lists. |
| POST/api/v1/lists | Make a list: {"name", "description"}. |
| PATCH/api/v1/lists/<id> | Rename a list or change its description. |
A whole example โ a free seed library, filed into a list:
curl https://leelamaps.com/api/v1/notes \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "ACLD Seed Library",
"description": "Free seeds, no card needed. Take a few, bring some back.",
"emoji": "๐ฑ",
"address": "401 E University Ave, Gainesville, FL",
"tags": ["free", "seeds"],
"list_ids": [123],
"external_id": "acld-seed-library"
}'
Move it later:
curl -X PATCH https://leelamaps.com/api/v1/notes/4567 \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"lat": 29.6519, "lng": -82.3226}'
| Field | Meaning |
|---|---|
title, description | The note's first line, and the words under it. One of the two is required to create. |
lat, lng or address | Where it is: coordinates, or a street address or venue name we look up. See Where a note goes. |
near | Where your places are, roughly: {"lat": โฆ, "lng": โฆ} or a town ("Gainesville, FL"). Lets a venue name be found, and refuses any match more than 150 km from it. |
emoji | The pin's emoji. Default ๐. |
marker_color | The pin's colour: red, orange, yellow, green, teal, blue, purple, pink, dark, or #rrggbb. Empty or left out: colour by privacy, as in the app. |
marker_style | The pin's shape: circle, glow, ring, diamond, dot or pin. |
privacy | public (default), friends, friends_of_friends or private. |
locals_only, locals_radius_km | Only here: only people who are, or were recently, within the radius (default 5 km) can open it. |
hide_location | No location: no pin. It reaches people near where it was written, and your friends, as a card; nobody but you is told the exact spot. |
draft, publish_at | See Drafts. |
tags | A list of words (up to 15). Sending tags on a PATCH replaces them. |
list_ids | Lists of yours to file it into. |
events | See Events. |
items | Photos and links in the note's library. See Photos, links and prices. |
archive_after | A date it leaves the map (it stays in your archive), or "never". Empty: an event's note archives after its last day; a plain note stays. See Timing and status. |
repeat_every | weekly, monthly or yearly: it reposts on that cycle. Empty to stop. |
season | When it is in season: "Mar 15 - Jun 30", "June-August", "summer" (or season_start/season_end as MM-DD); season_off dim (default) or hide. Empty to clear. |
status | A live status on the pin: "๐ข Open now", or {"emoji", "label", "color", "for": "2 hours"} (a week at most; left out = until cleared). null clears it. |
media_contrib | Who besides you may add items to its library: anyone (the default), friends, locals (people there), or off. |
media_contrib_review | true (the default): a reader's item waits for your OK; false: it shows straight away. |
external_id | Your own id for it (up to 120 characters). See Re-running a batch. |
Send lat and lng and that is where it goes. Otherwise we look up address โ or, with no address, the first event's venue โ and only accept a match we're sure of. We never guess: a venue name alone matches streets of the same name on other continents, so it is refused rather than pinned there.
"1309 NW 192nd Ave, Gainesville, FL") is found anywhere."Bo Diddley Plaza, Gainesville FL") or with near โ and the place we find is called what you asked for."placed": {"by": "address" | "venue", "label": "โฆ"} (null for coordinates).When we can't be sure you get a 422 with a reason:
| reason | Meaning |
|---|---|
needs_near | A venue name and nothing else. Add its town, or send near. |
too_vague | Only a town or region matched. Add the street address. |
far_from_hint | The best match is more than 150 km from near. |
not_found | Nothing matched. Send coordinates. |
curl https://leelamaps.com/api/v1/notes \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"title": "Jazz on the Green", "near": "Gainesville, FL",
"events": [{"date": "2026-10-10", "start_time": "7pm", "venue": "Celebration Pointe Promenade"}]}'
events is a list; each one becomes an Event in the note's library, exactly as one made in the app โ with RSVP, a place in people's day plans, and the note archiving itself after the date.
"events": [{
"title": "Pumpkin Bash",
"date": "2026-10-25",
"start_time": "4pm",
"end_time": "8:30 pm",
"venue": "Depot Park",
"rsvp": true,
"capacity": 200
}]
date is required. 2026-10-25, 10/25/2026 and Oct 25, 2026 all work. Also: end_date, description, capacity, recurrence (weekly or monthly).19:00, 7pm, 7:30 pm, noon. A bare 7 is refused โ morning or evening?events replaces the note's events. Leave it out to keep them; send [] to remove them.ticket_price: a number (15), or "$10-25" (a sliding scale) or "pay what you can"; 0/"free" for none. People request a ticket and you arrange payment; card payment for an event is turned on in the app. Anything unreadable is a 400.
recurrence takes weekly (on the event's own weekday), monthly, or days: "weekly: Tue, Thu" / "every Saturday" โ or "recurrence": "weekly", "recurrence_days": [1, 3] (0 = Monday). Anything else is a 400.
{"title": "Farm stand", "lat": 29.65, "lng": -82.32,
"publish_at": "2027-03-01T08:00:00-05:00", "season": "Mar 1 - Oct 31", "season_off": "dim",
"repeat_every": "weekly", "archive_after": "never",
"status": {"emoji": "๐ข", "label": "Open now", "color": "green", "for": "8 hours"}}
Anything unreadable is a 400 naming the field; nothing is guessed.
items is a list of up to 10 (at most 5 photos), each becoming an item in the note's library, as if added in the app:
"items": [
{"type": "image", "url": "https://example.org/stage.jpg", "title": "The stage",
"price": 12, "stock": 20},
{"type": "link", "url": "https://example.org/tickets", "title": "Tickets"}
]
price (dollars) and stock are optional; a priced item shows its price on the card.items replaces the items the API added before; ones added in the app stay. Leave it out to keep them.reason: image_failed, blocked_url, bad_price, too_many_imagesโฆ) the whole note is refused and nothing is saved, so a retry never leaves half a note.Give every note an external_id โ the row number in your spreadsheet, the event's id on its own site. POSTing the same external_id again updates that note instead of making a second one, so a script can be run again after fixing a typo, or every week as a calendar changes. GET /api/v1/notes?external_id=โฆ finds it.
POST /api/v1/notes/batch takes up to 50 notes, each exactly as POST /api/v1/notes would, and answers for each one. One bad note never stops the others. A near on the batch applies to every note that gives none.
curl https://leelamaps.com/api/v1/notes/batch \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"near": "Gainesville, FL", "notes": [
{"external_id": "ev-101", "title": "Swing night", "events": [{"date": "2026-10-15", "start_time": "8pm", "venue": "Rosa B. Williams Center"}]},
{"external_id": "ev-102", "title": "Open mic", "address": "4908 NW 34th Blvd, Gainesville, FL"}]}'
{"created": 2, "updated": 0, "error": 0, "not_attempted": 0,
"results": [{"index": 0, "status": "created", "id": 4567, "external_id": "ev-101", "placed": {"by": "venue", โฆ}, "duplicates": []}, โฆ]}
status is created, updated (that external_id was already yours), error (with error, field, reason), or not_attempted: a batch works for about 45 seconds, and notes it didn't reach are untouched. Send those again โ with external_ids, re-sending is always safe. Each note counts toward the hourly limits.
Send "draft": true and the note is saved but seen by nobody else until you publish it โ from the app, or with POST /api/v1/notes/<id>/publish. Or send publish_at (an ISO date-time, e.g. 2026-10-20T09:00:00-04:00) and it goes live by itself then.
Every create and update answers with duplicates: notes within about 100 m whose title looks like yours ([{"note_id", "title", "distance_m", "mine"}]). It's a warning, not a refusal โ look before you post the same place twice.
429 and nothing is saved.