Developers
Your plan is yours to build on: an API with tokens you make yourself, webhooks that say when anything changes, and a connector that puts Madison inside Claude and ChatGPT.
Start with a token
In the planner, open Settings and find Developers. Name a token for what will use it, choose whether it may make changes, and copy it: Madison keeps only a fingerprint, so it is shown once. A token belongs to one planner (Students or Professional), acts as you, and stops working the moment you revoke it. Each one may make 1,200 requests an hour.
curl https://www.usemadi.com/api/v1/today \ -H "Authorization: Bearer mad_pat_…" \ -H "X-Timezone: America/New_York"
Dates are
YYYY-MM-DDand timesHH:MM, in your own time zone, exactly as the planner keeps them. SendX-Timezone(or?tz=) so “today” is your today; otherwise the zone your reminders use is assumed.The REST API
Everything lives under
https://www.usemadi.com/api/v1. Answers are{ data }; a problem is{ error, message }with a status that means it (400, 401, 402 when a trial has ended, 403 for a read-only token, 404, 409, 429 withRetry-After). Changes are saved straight to your account, merge with edits on your other devices, and reach your webhooks. The whole description, for code generators and Zapier, is at /api/v1/openapi.json (OpenAPI 3.1).Method Path What it does get /me Who the token belongs to get /terms Every term get /courses Courses (or projects) in a term get /courses/{id} One course: due in the next three weeks, this week's planned study, time studied get /items Items, filtered post /items Add an item get /items/{id} One item patch /items/{id} Change an item delete /items/{id} Delete an item (to the trash, for 30 days) post /items/{id}/complete Tick an item off (or back on) get /commitments Fixed commitments in a term post /commitments Add a fixed commitment delete /commitments/{id} Remove a fixed commitment get /sessions The study log: time actually spent post /sessions Log a study session get /grades Grades so far, per course get /today Today: due, overdue, next, sessions, fixed hours get /upcoming Open items due soon get /snapshot Everything a widget shows, in one answer get /forecast The time cushion: hours needed against hours free, week by week get /timer The focus timer, if one is running or paused post /timer Start, pause, resume or stop the focus timer post /capture Add to Madison from a page or a selection get /webhooks This planner's webhooks post /webhooks Subscribe an https address delete /webhooks/{id} Unsubscribe Add an item in plain words, the way quick add reads them, or field by field:
curl -X POST https://www.usemadi.com/api/v1/items \ -H "Authorization: Bearer mad_pat_…" -H "Content-Type: application/json" \ -d '{"text": "Lab report 2 thu 5pm #chem"}' curl -X POST https://www.usemadi.com/api/v1/items \ -H "Authorization: Bearer mad_pat_…" -H "Content-Type: application/json" \ -d '{"title": "Essay 2", "dueDate": "2026-11-03", "course": "Modern Lit", "kind": "assignment"}'Filter items with
from,to,course,done,kindandq;sort=createdlists the newest first, which is what a polling trigger in Zapier or Make wants. Deleting sends an item to the trash for thirty days, as it does in the planner.Webhooks
Register an https address in Settings, or with
POST /webhooks(andDELETE /webhooks/{id}to stop, the REST-hooks pattern Zapier uses). Madison postsitem.created,item.updated,item.completed,item.deletedwhenever an item changes, whether through the API, from an assistant, or in the planner on any device.POST /your/endpoint Madison-Event: item.completed Madison-Delivery: evt_6f1c… Madison-Signature: t=1760450000,v1=5e0b… { "id": "evt_6f1c…", "type": "item.completed", "created": "2026-10-14T15:20:00.000Z", "product": "students", "source": "app", "data": { "item": { "id": "item_…", "title": "Problem set 4", "dueDate": "2026-10-16", "done": true, … }, "previous": { … } } }The signature is an HMAC-SHA256 of the timestamp, a full stop and the raw body, under the secret shown when you added the webhook. Check it, and refuse anything older than five minutes:
import { createHmac, timingSafeEqual } from "node:crypto"; function fromMadison(secret, rawBody, header) { const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("="))); if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const want = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest(); const got = Buffer.from(v1, "hex"); return got.length === want.length && timingSafeEqual(got, want); }Answer with any 2xx within ten seconds. Anything else is tried again after a minute, five minutes, half an hour, two hours, eight and a day; deliveries are at least once, so use
Madison-Deliveryto ignore a repeat. Madison never follows a redirect and only sends to public https addresses. A webhook that fails fifty times running is switched off, and Settings says so.Madison in Claude
Madison is a remote MCP server at
https://www.usemadi.com/api/mcp. In Claude, open Settings, then Connectors, choose Add custom connector, name it Madison and paste that address. Press Connect: Claude sends you to Madison, you sign in if you need to, choose the planner and press Allow. That is all; there is no token to copy. Then ask “what’s due this week?”, “move the lab report to Friday” or “plan my week”.In Claude Code, or anything that takes a command line:
claude mcp add --transport http madison https://www.usemadi.com/api/mcp # or with a token instead of signing in claude mcp add --transport http madison https://www.usemadi.com/api/mcp \ --header "Authorization: Bearer mad_pat_…"
What an assistant can do with it:
get_today: What is due today, what is overdue, the next thing due, today's planned study sessions and the fixed parts of the day.list_upcoming: Open items due in the next few days, soonest first, optionally for one course.search_items: Find items in any term by words in their title, notes, course or type.grades_summary: Each course's grade so far (percent and letter), how much of it is settled, the target, and what is needed on the rest to reach it.add_item: Add a dated item to a course. Either give title and due_date (and course when there is more than one), or give text in quick-add style, e.g. "Lab report 2 thu 5pm #chem", and Madison reads the date, time, type and course from it.complete_item: Mark an item done (or not done again).move_item: Give an item a new due date, and optionally a new time.plan_week: Lay out study sessions for a week around the fixed commitments and deadlines, with Madison's planner, and save it as that week's plan (replacing any plan the week had).log_study_session: Record time actually spent studying, so the plan and the week's numbers learn from it.
Each of your terms is also offered as a resource the assistant can read whole. A connection allowed as read-only sees only the tools that read. Disconnect it in Settings, under Developers, and every token it was given stops at once.
Madison in ChatGPT
In ChatGPT, open Settings, then Apps & Connectors, and under Advanced turn on Developer mode. Choose Create, name it Madison, paste
https://www.usemadi.com/api/mcp, choose OAuth for authentication, and connect: you are sent to Madison to sign in and press Allow, as with Claude. Madison then appears in the tools of a new chat. Thesearchandfetchtools that ChatGPT’s deep research looks for are there too, over your items.Shortcuts on iPhone
Recipes, each a few actions in the Shortcuts app. Make a token in Settings first, and put it in a Text action at the top of each shortcut. Every request uses Get Contents of URL with a header
Authorizationset toBearerfollowed by your token, andX-Timezoneset to your zone (Europe/London,America/New_York). Name a shortcut what you will say to Siri.Add to Madison. Ask for Input (“What’s due?”), then Get Contents of URL:
https://www.usemadi.com/api/v1/items, method POST, Request Body JSON with one key,text, set to the input. Then Get Dictionary Valuedata.titleand Show Notification. Say “Lab report thursday 5pm chem” to Siri and it is in.What’s next. Get Contents of URL:
https://www.usemadi.com/api/v1/today. Get Dictionary Valuedata.next.titleanddata.next.dueDate, then Speak Text: “Next is … , due …”. Add Repeat with Each overdata.dueto hear everything due today.Today’s agenda, as a notification. Get Contents of URL:
https://www.usemadi.com/api/v1/snapshot. Get Dictionary Valuedata.today.items; Repeat with Each item, Get Dictionary Valuetitleand add it to a variable with Add to Variable; after the repeat, Combine Text with new lines and Show Notification, titled withdata.today.dueCount“due today”. Adddata.cushion.warningas a second line: it is empty unless a crunch is coming. In the Automation tab, run it every weekday at 7:30 with Run Immediately on.Log study time. Ask for Input (Number, “How many minutes?”). Get Contents of URL
https://www.usemadi.com/api/v1/courses, Get Dictionary Valuedata, then Choose from List over the names (Repeat with Each,name). Get Contents of URL:https://www.usemadi.com/api/v1/sessions, POST, JSON{"minutes": <input>, "course": <chosen>}. It counts towards the course’s time on the overview and teaches the planner how long its work really takes.Add an exam. Ask for Input (Text, “Which exam?”), Ask for Input (Date and Time, “When?”), and choose the course as above. Format Date the date as Custom
yyyy-MM-ddand again asHH:mm. Get Contents of URL:https://www.usemadi.com/api/v1/items, POST, JSON{"title": <exam>, "kind": "exam", "dueDate": <date>, "dueTime": <time>, "course": <chosen>}. It joins the exam countdown on your widgets.Start and stop focus. Get Contents of URL:
https://www.usemadi.com/api/v1/timer, POST, JSON{"action": "start", "minutes": 50}; it starts on the next thing due unless you additemId. The planner, the menu bar app and the browser extension’s site blocker all see it at once. Add Set Focus (a Study focus) on if you like. A second shortcut posts{"action": "stop"}, which logs the time and answers withdata.minutes; turn the Focus off after it.Widgets, the menu bar and the browser
Madison’s own widgets, menu bar app and browser extension use this API and nothing else, so anything they do, yours can. Three endpoints are made for them:
GET /snapshot: today’s list, the next thing due, the next exam, this week’s time cushion and the focus timer, in one answer. It sends anETag; ask again withIf-None-Matchand an unchanged day is a bodiless 304, so a widget can ask every minute for almost nothing.GETandPOST /timer: the focus timer, shared by every device.start,pause,resumeandstop; stopping logs the session. While it runs,startedAtandelapsedSeclet a clock count on by itself.POST /capture: add from a page. A line with a date becomes an item at once; a paragraph or a page is read the way forwarded email is, and what is found waits on the overview to be accepted.
curl https://www.usemadi.com/api/v1/snapshot -H "Authorization: Bearer mad_pat_…" -i # HTTP/1.1 200 OK # ETag: "s1-…" curl https://www.usemadi.com/api/v1/snapshot -H "Authorization: Bearer mad_pat_…" -H 'If-None-Match: "s1-…"' # HTTP/1.1 304 Not Modified
Native apps sign in with OAuth 2.1 and PKCE, registering themselves first (dynamic client registration at
/api/oauth/register); a browser extension’schromiumapp.orgaddress, a loopback address and an app’s own scheme are all accepted redirects. Everything there is to install is on Get the apps.Forward mail to Madison
Where the deployment has it switched on, Settings gives you a private address. Forward a teacher’s email, a changed schedule or an invitation to it, and the dated things in it are read out (by Claude, or by Madison’s own rules where there is no key) and wait on your overview under From your inbox to be accepted, edited or dismissed. Nothing reaches your plan until you say so. A new address stops the old one.
What we promise
Version 1 will not change under you: new fields and endpoints may appear, nothing is renamed or removed without a new version and notice in the changelog. Tokens and codes are stored as hashes, webhook secrets sealed. Nothing you do through the API is shared with anybody else. Questions, or something you wish it did: hello@madisonplanner.app.