← AIron

AIron API for AI assistants

Let a script, Cursor, ChatGPT, or Claude log workouts, cardio, meals and body measurements into that user's AIron account — not their clients'. Scoped tokens, no password sharing. Paste the MCP URL into a ChatGPT or Claude custom connector (OAuth 2.1 + PKCE) or send Authorization: Bearer airon_pat_… from Cursor. This is your account, not your trainees.

The 30-second version — for a client that can make HTTP calls

Copy this block to an assistant that can actually issue HTTP requests — a coding agent, a shell, a shortcut, your own script. It has everything needed to pair, store the token and start logging.

This does not work in a plain chat turn. Pasting instructions into ChatGPT or Claude does not give the model a way to reach these URLs; without a tool that performs the request, it will either say it cannot or invent the result.

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 bearer-token MCP server at
   https://airon.app/api/functions/mcp — with "Authorization: Bearer <token>".
   (An OAuth connector such as ChatGPT or Claude uses /api/mcp instead — see section 3.)
Full machine-readable guide: https://airon.app/llms.txt

1. Connect an account

The 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":"My logging script","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 over

Codes expire after 15 minutes. Tokens minted through pairing never expire. A user can also mint a token by hand at /ApiAccess.

2. Authenticate

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; on REST a call outside them returns 403 naming the missing scope so you can tell the user what to enable. Over MCP, auth failures are HTTP 401 and rate limits are HTTP 429; missing scope stays HTTP 200 with isError — see section 9.

Rate limit. A token is allowed 100 requests per fixed 60-second window, counted per token rather than per account, and REST answers 429 rate_limited with retry_after_seconds once that is spent. Two caveats, because a client author sizes a bucket against this number: the window is fixed rather than sliding, so it resets on the first request after it lapses; and the counter is written without blocking the request, so it is best-effort and concurrent calls can slip past it. The hosting platform also applies its own limits in front of AIron's. Treat 100 as a ceiling to stay under, not an allowance to spend.

3. MCP server

AIron speaks JSON-RPC 2.0 at the URL below — 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.

There are two MCP endpoints and they are not interchangeable. Pick by how your client authenticates, not by which URL you saw first.

/api/mcp             ← OAuth 2.1 connectors (ChatGPT, Claude). Discoverable.
/api/functions/mcp   ← bearer PAT (Cursor, scripts). Discovery: its 401 only.

ChatGPT and Claude: add a custom connector pointing at /api/mcp. A connector does not accept a bare endpoint — it probes .well-known documents, and only that path has them. Measured 2026-09-11: /api/mcp → 401 with WWW-Authenticate … resource_metadata; /.well-known/oauth-protected-resource/api/mcp → 200 naming https://app.base44.com as the authorization server (registration, PKCE S256). The same document under /api/functions/mcp returns 404 “not configured for this app”, which is why pasting that path fails with “there was a problem connecting” however healthy the app is. Step-by-step per client: /connect. Disconnect at /ApiAccess.

Cursor and scripts that send Authorization: Bearer airon_pat_… from /ApiAccess keep using AIron's own server at /api/functions/mcp — it answers a PAT directly, with no OAuth round trip. That is the endpoint the error codes in section 9 describe.

The REST API in section 4 needs no MCP client at all: one header, one base URL. Its OpenAPI document describes logical paths, not routable ones (section 4), so importing it unchanged into a generic "actions" integration produces calls to sub-paths that return 404. Every request has to go to the base URL with ?path=.

4. REST endpoints

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.

Every list endpoint is capped and none returns a cursor: /workouts reads the newest 500 sessions before filtering by date and adds at most 200 sets per session with include_sets; /workouts/{id} returns at most 300 sets; /meals and /metrics read up to 500 rows per ownership field. Treat a full response as possibly truncated.

MethodPathScopePurpose
GET/profileprofile:readGoals, units, daily calorie and macro targets
GET/workoutsworkouts:readList sessions and plans in a date range (newest 500, no cursor)
GET/workouts/{id}workouts:readOne session with its sets and intervals (up to 300)
POST/workoutsworkouts:writeLog a workout — strength, cardio, rest, RPE
PATCH/workouts/{id}workouts:writeEdit a session
DELETE/workouts/{id}workouts:writeSoft-delete a session
GET/mealsnutrition:readMeals with daily totals
POST/mealsnutrition:writeLog food — one entry per item
DELETE/meals/{id}nutrition:writeDelete a meal entry
POST/waternutrition:writeLog hydration in millilitres
GET/metricsmetrics:readWeight, body fat, circumferences (photo URLs need photos:read)
POST/metricsmetrics:writeLog a measurement (attaching a photo needs photos:write)
DELETE/metrics/{id}metrics:writeDelete a measurement (a photo entry needs photos:write)

5. Units — convert before sending

  • Weight in kilograms, body lengths in centimetres.
  • Distance in metres (5 km → 5000), pace in seconds per km (5:30/km → 330).
  • Durations and rest in seconds, dates as YYYY-MM-DD.
  • GET /profile returns the user's display preference so you can speak in their units while sending canonical ones.

6. Log a workout

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.

7. Recipes

Log training by talking to an assistant that can call an API

Give a client that can make HTTP calls — a coding agent, a shortcut, your own MCP client — 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". It converts units, splits sets, and shows you a link to the logged session. A plain ChatGPT or Claude chat cannot do this: it has no tool that reaches these URLs, and neither product's connector flow can authenticate against AIron (section 3).

Siri / Apple Watch via Shortcuts

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.

Nightly sync into a spreadsheet or dashboard

A cron script with a read-only token: GET ?path=/workouts&from=…&to=…, GET ?path=/meals&date=… and GET ?path=/metrics feed a personal Grafana sheet or a coach's weekly review without CSV exports.

These are a recent window, not a full-history export, and none of them returns a cursor. /workouts reads the newest 500 sessions recorded against the account and filters that set by date, so a range older than the window comes back empty rather than paginated — and it matches on the account's target field only, so an older session recorded under just its creator can be missing at any date. /meals and /metrics read up to 500 rows per ownership field and merge the two, which is no longer date-ordered once merged. Sync often and keep your own history; do not treat one call as the archive.

Your own agent or app

Point any LLM at GET on the base REST URL — the discovery document lists every endpoint with its JSON schema, the unit conventions and the ?path= calling convention. It does not list the error codes or the response caps; those are in section 9 and the recipe above.

8. Reauth, rescope, revoke — the token lifecycle

The HTTP statuses in this section are the REST contract. Over MCP, missing or invalid credentials are HTTP 401; rate limits are HTTP 429; scope and tool failures stay HTTP 200 with isError — section 9.

If the token stops working (reauth)

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".

If you need more (or fewer) permissions — rescope

Two ways. For a manual or paired token, the user can edit it in place at /ApiAccess (pencil icon): toggle permissions, save — the token string does not change. An OAuth row cannot gain new permissions (including photos) that way — disconnect and approve again. Or start a fresh pairing requesting extra scopes.

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.

Revoke

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.

9. Errors an agent can act on

REST (apiV1). The HTTP status is real, and 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.

CodeHTTPWhat to do
missing_token / invalid_token401You have no usable credential. Run the pairing flow (section 1).
revoked_token / expired_token401The credential is dead. Re-pair; the user must approve again.
insufficient_scope403Read required_scope, ask the user to enable it at /ApiAccess. Never retry in a loop.
rate_limited429Wait retry_after_seconds, then resume.
unknown_endpoint404Wrong path — GET the base URL for the index. Use ?path=, not sub-paths.
not_found404That id does not exist on this account (or is not yours — they look identical on purpose).
api_write_unavailable503Temporary server-side condition; reads still work. Try later.
internal_error500Nothing was saved. Retry once; if it persists, report it.

MCP auth and rate limits use the real HTTP status. Missing, invalid, revoked or expired credentials → HTTP 401 with this challenge, as sent by airon.app and airon.coach (one header line, wrapped here):

WWW-Authenticate: Bearer realm="AIron",
  resource_metadata="https://airon.app/api/functions/oauth?p=resource",
  authorization_uri="https://airon.coach/.well-known/oauth-authorization-server"

Follow resource_metadata exactly as given; do not derive it from the host you called. Do not build a .well-known URL yourself either. On airon.app, the one under the MCP path answers 404, and the root /.well-known/oauth-protected-resource is Base44's /api/mcp server, not this one. Rate limiting → HTTP 429 (do not treat that as an invitation to re-register). JSON-RPC -32001 is still in the body. Scope and tool failures stay HTTP 200 with isError: true:

  • Authentication, revocation and expiry → HTTP 401 plus a JSON-RPC error with code -32001. Rate limiting → HTTP 429 and the same -32001 body. The {ok:false,error,message} body from the table above travels in error.data.
  • A missing scope → an ordinary result with isError: true, whose content[0].text is that same body as a JSON string.
  • A tool failure → a result with isError: true whose content[0].text is {"error":<code>,"message":<for the user>} — with no ok field. Parse defensively.

initialize is answered whenever an Authorization header is present, valid or not, so a successful handshake does not mean your token is valid. With no Authorization header at all it returns the 401 challenge above. Call tools/list to find out whether the token works.

Safety and privacy

  • Tokens are stored only as SHA-256 hashes — AIron cannot show a token again, and a database leak cannot be replayed.
  • A user can revoke any token instantly at /ApiAccess; access stops on the next request.
  • Deleting a workout is reversible (soft delete); deleting a meal or measurement is not.
  • This API reaches health data. Connecting a third-party assistant sends training, nutrition, body-measurement and progress-photo data — plus the profile fields /profile returns, including the account email — to whoever operates that assistant. They are a separate company deciding their own purposes, not an AIron subcontractor. The privacy policy (section 6) names them; read it before connecting one.
  • Revoking a token at /ApiAccess stops future requests, but it cannot recall data that has already been sent — and a progress photo is shared as a link that stays reachable.

We use cookies for sign-in, analytics and core features. Privacy Policy