Reference
API keys
A personal API key lets a script or an AI agent act as you. It works for the REST API and the MCP server alike. Create one in the app, send it as a bearer token, revoke it when you are done with it.
Create a key
- Sign in to Tally and open Settings → API keys. Demo accounts can create keys too.
- Choose New key. Give it a label (1 to 80 characters) that says where it will live, pick Read only or Read & write, and an expiry: never, 30, 90 or 365 days.
- Copy the key from the banner. It is shown once. Tally keeps only a hash of it, so it cannot show it again. If you lose it, revoke it and create another.
Use a key
Send it in the Authorization header of every request, REST or MCP. Keep it in an environment variable or your agent's secret store, never in source control or a web page.
curl https://tally.art3m1s.me/api/me -H "Authorization: Bearer $TALLY_API_KEY"GET /api/me is a cheap way to check a key: it says whose it is and, for a demo account, today's quota (demo is null for the owner).
{
"email": "you@example.com",
"is_owner": false,
"demo": { "limit": 1, "used_today": 0, "resets_at": "2026-09-24T00:00:00+00:00" }
}Key format
tally_live_<keyId>_<secret>, all lowercase:
keyIdis 16 hex characters (8 random bytes). It names the key and is safe to show; the key list uses it.secretis 64 hex characters (32 random bytes, 256 bits).
As a regular expression: ^tally_live_([0-9a-f]{16})_([0-9a-f]{64})$. The fixed prefix makes a pasted key easy to recognise and to search for. Lists show a redacted form, tally_live_<keyId>_…<last 4>, which is enough to recognise a key and useless for calling the API.
Scopes
A key has one of two access levels. Write always includes read.
| Access | scopes | REST | MCP |
|---|---|---|---|
| Read only | ["read"] | GET, HEAD and OPTIONS, with the exceptions below | The 9 read tools |
| Read & write | ["read", "write"] | Every method | All 16 tools |
On REST, the HTTP method decides by default: GET, HEAD and OPTIONS need read; POST, PUT, PATCH and DELETE need write. Three routes set their own scope, because their method says the wrong thing: GET /api/logs/{log_id} needs write (reading a log can mark a stalled lookup not_found), GET /api/foods/barcode needs write (a new barcode's food is added to the shared food cache), and POST /api/settings/goals/compute needs only read (it computes targets and saves nothing). The OpenAPI document marks those three with x-tally-scope. A read-only key on a route that needs write gets 403 with {"detail": "this API key is read-only"}. Over MCP, a read-only key is served a tool list without the write tools, so an agent never sees them. A signed-in session in the app has both scopes.
Expiry and the 10-key cap
A key can expire after 30, 90 or 365 days, or never. After that it stops working like a revoked key. Short expiries suit agents you are trying out.
An account can hold at most 10 active keys (not revoked and not yet expired). Creating an eleventh answers 409; revoke one first.
Each key's last used time is updated at most once every five minutes, so a busy agent does not write on every call. Treat it as accurate to about five minutes.
Revoke a key
In Settings → API keys, open the key, choose Revoke and confirm. The key stops working on its next request; nothing caches it. The row stays in the list, dimmed, so you can still see what it was and when it was last used.
An account keeps at most 50 keys in all: active, revoked and expired. Creating a key past that deletes your oldest revoked or expired keys to make room. Active keys are never deleted.
A revoked, expired, unknown or malformed key always gets the same answer, so it never reveals which keys exist: 401 with {"detail": "invalid API key"} and the header WWW-Authenticate: Bearer.
Demo accounts
A key acts as its account, so it carries the account's limits. A demo account's key can log once a day, and that one attempt is shared with the app: a log made through the key uses up the day's log in the app too. The owner account has no quota. Limits & errors has the details.
The /api/keys endpoints
The Settings screen uses these. They accept a signed-in app session only: an API key calling them gets 403 with {"detail": "API keys cannot do this; sign in to the app"}. That way a leaked key can never mint a new key or outlive its own revocation.
- GET
/api/keyssession onlyYour keys, newest first, revoked and expired ones included, up to 50.
- POST
/api/keyssession onlyBody:
label(1 to 80 characters, trimmed),scopes("read"or"read_write", default"read_write"),expires_in_days(30,90,365ornullfor never). Answers201with{key, secret}: the key's record and the full plaintext key, the only time it is ever returned. It also deletes your oldest revoked or expired keys past 50 in all. - DELETE
/api/keys/{key_id}session onlyRevokes the key and returns its record. Revoking twice is harmless and returns the same record; a key id that is not yours is
404.
A key record, as the list and the other two calls return it:
{
"id": "3f9c2a7b1d4e6f80",
"label": "Claude Code on my laptop",
"scopes": ["read", "write"],
"redacted": "tally_live_3f9c2a7b1d4e6f80_…a41c",
"created_at": "2026-09-23T17:02:11Z",
"last_used_at": null,
"expires_at": "2026-12-22T17:02:11Z",
"revoked_at": null
}