Let ChatGPT, Claude, a shortcut or your own script log workouts, cardio, meals and body measurements straight into a user's AIron account. Scoped tokens, no password sharing, no browser automation.
If you use ChatGPT, Claude or another assistant, copy this block into the chat. Any capable agent can take it from there — it will ask AIron for a connection, show you a link to approve, and start logging.
Connect my AIron account so you can log and read my training and nutrition.
1. Call POST https://airon.app/api/functions/authorizeApiClient with
{"action":"start","client_name":"<your app name>"} and show me the connect_url.
2. When I say I've approved, collect my token with
{"action":"poll","device_code":"<device_code from step 1>"}.
3. From then on call the REST API at https://airon.app/api/functions/apiV1
(plain GET on that URL describes every endpoint) or the MCP server at
https://airon.app/api/functions/mcp — with "Authorization: Bearer <token>".
Full machine-readable guide: https://airon.app/llms.txtThe pairing flow is the recommended path: your app never sees a password and the user never copies a secret between windows. Start it with no authentication at all.
POST https://airon.app/api/functions/authorizeApiClient
Content-Type: application/json
{"action":"start","client_name":"ChatGPT","scopes":["workouts:write","nutrition:write"]}You get back a connect_url and a private device_code. Send the user to the URL — they approve on a screen that names your app and lists exactly what you asked for — then poll every 3 seconds until the token appears.
POST https://airon.app/api/functions/authorizeApiClient
{"action":"poll","device_code":"airon_dev_…"}
202 → {"status":"authorization_pending"} keep waiting
200 → {"access_token":"airon_pat_…"} store it, shown once
410 → expired / already claimed start overCodes expire after 15 minutes. Tokens minted through pairing never expire. A user can also mint a token by hand at /ApiAccess.
Authorization: Bearer airon_pat_…The token is the identity. There is no user or email parameter anywhere in this API — sending one has no effect. Each token carries only the scopes its owner granted; a call outside them returns 403 naming the missing scope so you can tell the user what to enable. Limit: 120 requests per minute.
Paste this URL as a custom connector in ChatGPT (Settings → Connectors → Add) or Claude (Settings → Integrations → Add custom connector). It speaks JSON-RPC 2.0 — initialize, tools/list, tools/call — and only lists the tools your token is allowed to use, so the model never sees an affordance it will be refused on.
https://airon.app/api/functions/mcpThe connector will ask for a token. The clean way to get one is the pairing flow in section 1 — tell the assistant to run it before you add the connector, or mint one manually at /ApiAccess and paste it.
Base URL https://airon.app/api/functions/apiV1. A plain GET on the base URL returns a machine-readable index with the JSON schema of every endpoint — point an agent at it and it can discover the whole API by itself. An OpenAPI 3.1 document lives at ?path=/openapi.json.
Send the resource in the path query parameter; filters, dates and ids ride as ordinary siblings of it:
GET …/apiV1?path=/workouts&from=2026-08-01&to=2026-08-07
GET …/apiV1?path=/meals&date=2026-08-03
POST …/apiV1?path=/meals {"items":[…]}
DELETE …/apiV1?path=/meals/<id>Sub-paths such as …/apiV1/workouts are not routed by the host and return 404 — always use ?path=.
Call as {base}?path=/workouts — the resource goes in the path query parameter.
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /profile | profile:read | Goals, units, daily calorie and macro targets |
| GET | /workouts | workouts:read | List sessions and plans in a date range |
| GET | /workouts/{id} | workouts:read | One session with every set and interval |
| POST | /workouts | workouts:write | Log a workout — strength, cardio, rest, RPE |
| PATCH | /workouts/{id} | workouts:write | Edit a session |
| DELETE | /workouts/{id} | workouts:write | Soft-delete a session |
| GET | /meals | nutrition:read | Meals with daily totals |
| POST | /meals | nutrition:write | Log food — one entry per item |
| DELETE | /meals/{id} | nutrition:write | Delete a meal entry |
| POST | /water | nutrition:write | Log hydration in millilitres |
| GET | /metrics | metrics:read | Weight, body fat, circumferences, photos |
| POST | /metrics | metrics:write | Log a measurement or progress photo |
| DELETE | /metrics/{id} | metrics:write | Delete a measurement |
GET /profile returns the user's display preference so you can speak in their units while sending canonical ones.Send one entry per set — three sets of bench press are three entries. Cardio intervals live in the same array with an activity_type.
curl -X POST "https://airon.app/api/functions/apiV1?path=/workouts" \
-H "Authorization: Bearer airon_pat_…" \
-H "Content-Type: application/json" \
-d '{
"date": "2026-08-02",
"title": "Push day",
"exercises": [
{"exercise_name":"bench press","set_number":1,"reps":8,"weight":80,"rpe":7,"rest_seconds":120},
{"exercise_name":"bench press","set_number":2,"reps":8,"weight":80,"rest_seconds":120},
{"exercise_name":"treadmill run","activity_type":"run","set_number":1,
"distance_m":5000,"duration_seconds":1500,"avg_hr":152}
]
}'Add "completed": false to create a planned session for a future date instead of a performed one.
Paste the 30-second block at the top, approve the connection, then just talk: "log 3×8 bench at 80 kg and a 5k run in 25 minutes". The assistant converts units, splits sets, and shows you a link to the logged session. Add the MCP URL as a connector for the smoothest experience.
Mint a token at /ApiAccess with nutrition:write only. Shortcut: Dictate Text → Get Contents of URL (POST to …/apiV1?path=/water with the Bearer header and {"amount_ml":250}). One tap on the watch face logs a glass of water; same pattern works for weight every morning.
A cron script with a read-only token: GET ?path=/workouts&from=…&to=…, GET ?path=/meals&date=… and GET ?path=/metrics give you everything for a personal Grafana sheet or a coach's weekly review — no CSV exports.
Point any LLM at GET on the base REST URL — the discovery document is the contract: endpoints, schemas, units, error codes. The agent needs nothing else to operate the API correctly.
Tokens are bearer secrets, not sessions — there is no refresh flow by design. A 401 carries a machine-readable reason: invalid_token, revoked_token or expired_token. The answer is always the same: start a new pairing (section 1) and have the user approve again — 30 seconds, and only the human can do it. Lost tokens cannot be recovered (only hashes are stored), so a lost token is revoked and replaced, never "found".
Two ways. The user can edit a token in place at /ApiAccess (pencil icon): toggle permissions, save — the token string does not change, so nothing has to be re-pasted anywhere; the new permissions apply from the next request. Or the app can start a fresh pairing requesting the extra scopes, which mints a new token — swap it in and the user can revoke the old one.
A 403 insufficient_scope response names the missing scope in required_scope — tell the user exactly which permission to enable. Do not retry in a loop.
Any token dies instantly at /ApiAccess; the very next request with it returns 401 revoked_token. The user also gets an email the first time a new token is used, so a silent connection cannot stay silent.
Every error body is {"ok":false,"error":<code>,"message":<for the user>}. The error code is stable — branch on it, not on the message text.
| Code | HTTP | What to do |
|---|---|---|
| missing_token / invalid_token | 401 | You have no usable credential. Run the pairing flow (section 1). |
| revoked_token / expired_token | 401 | The credential is dead. Re-pair; the user must approve again. |
| insufficient_scope | 403 | Read required_scope, ask the user to enable it at /ApiAccess. Never retry in a loop. |
| rate_limited | 429 | Wait retry_after_seconds, then resume. |
| unknown_endpoint | 404 | Wrong path — GET the base URL for the index. Use ?path=, not sub-paths. |
| not_found | 404 | That id does not exist on this account (or is not yours — they look identical on purpose). |
| api_write_unavailable | 503 | Temporary server-side condition; reads still work. Try later. |
| internal_error | 500 | Nothing was saved. Retry once; if it persists, report it. |