Luna Tracker API
Log and read the family's events: feeds, sleep, nappies, solids, notes, measurements, supplements.
Log and read the family's events: feeds, sleep, nappies, solids, notes, measurements, supplements.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/getmyluna-app:getmyluna-app-tracker-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Luna Tracker API
description: 'Luna is a baby tracker for feeds, sleep, nappies, solids and routines, shared between parents and invited caregivers.
This REST API is the same one the Luna web app uses. Every request is scoped to **one family**: the family is resolved from the bearer token (or session cookie) and there is no way to address another family''s data.
## Authentication
* **Bearer token** (`Authorization: Bearer `), the way agents and scripts should call the API. Create one in the app (Settings → Connect an assistant) or `POST /api/vega/token` from a signed-in session. Tokens are stored hashed and can carry `read` or `read`+`write` scopes. A `read` token may call every `GET` (and `HEAD`) route plus `POST /api/ask` (asking only; a `remember:` question saves a note and needs `write`). Any other `POST`, `PUT`, `PATCH` or `DELETE` with a token that lacks `write` returns `403 {"error": "insufficient_scope", "required_scope": "write"}`. Each operation''s `x-required-scope` says which it needs.
* **Session cookie** (`luna_session`), what the browser uses after `POST /login` or `POST /api/vega/onboard`.
Only `GET /api/health` and `POST /api/vega/onboard` are reachable without credentials. Unauthenticated calls to anything else return `401 {"error": "unauthorized"}`.
## Conventions
* Timestamps are ISO 8601 in the family''s local time (`YYYY-MM-DDTHH:MM:SS`); omit `ts` to log "now". Future timestamps are rejected.
* Volumes are millilitres, weights kilograms, lengths centimetres. Families with `units: imperial` still store metric.
* Errors are JSON `{"error": ""}` with a 4xx status.
* Luna is not a medical device: nothing here diagnoses, assesses symptoms or advises on medication.
## Other ways in
* **MCP connector** at `https://getmyluna.app/mcp` (streamable HTTP; OAuth sign-in, or the same bearer token): 23 tools, resources and prompts for Meta Muse, Claude Code and any MCP client. Manifest: `/.well-known/luna-connector.json`.
* **Agent instructions**: `/llms.txt`. **Developer portal**: `/developers`. **CLI**: `/cli/luna.py`.'
termsOfService: https://getmyluna.app/terms
contact:
name: Luna support
url: https://getmyluna.app/contact
email: hello@getmyluna.app
version: 1.1.0
servers:
- url: https://getmyluna.app
description: Production
tags:
- name: Tracker
description: 'Log and read the family''s events: feeds, sleep, nappies, solids, notes, measurements, supplements.'
paths:
/api/vega/log:
post:
tags:
- Tracker
summary: Log an event
description: 'Append a feed, sleep/wake, nappy, solid meal, note, supplement or measurement. Validates ranges (milk 0-500 ml, weight 0.5-30 kg, length 40-120 cm) and rejects future timestamps. Returns the saved event, the refreshed live status, and a soft `warning` if the time conflicts with a recorded sleep span.
**Safe retries:** send an `Idempotency-Key` header (or body `client_id`), a random value you make once per entry and resend unchanged on every retry. Keys are scoped to the family. The first request logs the entry; any later request with the same key logs nothing and returns `200` with `replayed: true` and the original entry, even when two retries arrive at once. Keep `ts` as the time it happened, not the time you send it. A key reused for a different `type` returns `409 idempotency_key_reused`. Keys are kept for 30 days, and a keyed entry with `ts` more than 30 days ago is refused (`400`). The Luna app uses this for logs saved while offline.'
operationId: log_event
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LogEventRequest'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/LogEventResponse'
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Bad Request
'409':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Conflict
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: '`insufficient_scope`: the bearer token lacks the `write` scope'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
parameters:
- name: Idempotency-Key
in: header
required: false
description: 'Random per-entry key (8-128 characters: letters, digits, `_ - . :`). Resend the same key on retries.'
schema:
type: string
minLength: 8
maxLength: 128
pattern: ^[A-Za-z0-9][A-Za-z0-9_.:-]{7,127}$
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: write
/api/vega/food/suggestions:
get:
tags:
- Tracker
summary: Recently logged foods
description: 'Foods logged in the last 14 days, most recent first: handy for autocomplete.'
operationId: food_suggestions
parameters:
- name: limit
in: query
required: false
schema:
type: integer
maximum: 100
minimum: 1
description: Maximum number of foods to return.
default: 12
title: Limit
description: Maximum number of foods to return.
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/FoodSuggestionsResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: read
/api/vega/event/edit:
post:
tags:
- Tracker
summary: Edit an event
description: Change editable fields (ts, ml, foods, note, meal_type, kind, author) on one event by id.
operationId: edit_event
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EventEditRequest'
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/EventMutationResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: '`insufficient_scope`: the bearer token lacks the `write` scope'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: write
/api/vega/event/delete:
post:
tags:
- Tracker
summary: Delete an event
description: Remove one event by id.
operationId: delete_event
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EventIdRequest'
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/EventMutationResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: '`insufficient_scope`: the bearer token lacks the `write` scope'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: write
/api/vega/event/react:
post:
tags:
- Tracker
summary: React to an event
description: Add an emoji reaction from the current caregiver to an event.
operationId: react_to_event
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EventReactRequest'
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/EventMutationResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: '`insufficient_scope`: the bearer token lacks the `write` scope'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: write
/api/vega/recipes:
post:
tags:
- Tracker
summary: Save a recipe
description: Store a named ingredient list so it can be logged as one solid meal.
operationId: save_recipe
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RecipeRequest'
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/RecipeResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: '`insufficient_scope`: the bearer token lacks the `write` scope'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: write
/api/vega/undo:
post:
tags:
- Tracker
summary: Undo the last event
description: 'Removes the most recently logged event. Returns `removed: null` when the log is empty.'
operationId: undo_last_event
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/UndoResponse'
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: '`insufficient_scope`: the bearer token lacks the `write` scope'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: write
/api/vega/today:
get:
tags:
- Tracker
summary: Today's events
description: Shortcut for `GET /api/vega/events` with today's date.
operationId: today_events
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/EventsResponse'
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: read
/api/vega/events:
get:
tags:
- Tracker
summary: Events for a day
description: All events for `date` (YYYY-MM-DD), oldest first. Defaults to today.
operationId: list_events
parameters:
- name: date
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Day to list, YYYY-MM-DD. Defaults to today (family-local).
examples:
- '2026-09-20'
title: Date
description: Day to list, YYYY-MM-DD. Defaults to today (family-local).
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/EventsResponse'
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Bad Request
'401':
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
- sessionCookie: []
x-required-scope: read
components:
schemas:
EventEditRequest:
properties:
id:
examples:
- af73aec9
title: Id
type: string
changes:
additionalProperties: true
description: 'Editable fields only: ts, ml, foods, note, meal_type, kind, author.'
examples:
- ml: 180
title: Changes
type: object
required:
- id
- changes
title: EventEditRequest
type: object
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
type: object
required:
- loc
- msg
- type
title: ValidationError
RecipeResponse:
properties:
status:
type: string
const: ok
title: Status
recipe:
additionalProperties: true
type: object
title: Recipe
type: object
required:
- status
- recipe
title: RecipeResponse
LogEventRequest:
description: Body for `POST /api/vega/log`. Provide the fields that make sense for `type`.
properties:
type:
enum:
- milk
- sleep
- wake
- nappy
- poop
- solid
- note
- supplement
- weight
- length
examples:
- milk
title: Type
type: string
ts:
anyOf:
- type: string
- type: 'null'
default: null
description: ISO 8601 local time; defaults to now. Cannot be in the future.
examples:
- '2026-09-20T14:30:00'
title: Ts
ml:
anyOf:
- maximum: 500
minimum: 0
type: integer
- type: 'null'
default: null
description: '`milk`: millilitres, 0-500. Use 0 (or omit) for a breastfeed.'
examples:
- 150
title: Ml
kind:
anyOf:
- type: string
- type: 'null'
default: null
description: '`nappy`: wet|dirty|both|dry. `supplement`: vitd|iron|multivitamin.'
examples:
- wet
title: Kind
foods:
anyOf:
- items:
type: string
type: array
- type: 'null'
default: null
description: '`solid`: foods eaten.'
examples:
- - scrambled egg
- toast
title: Foods
meal_type:
anyOf:
- enum:
- meal
- snack
type: string
- type: 'null'
default: null
description: '`solid` only.'
title: Meal Type
kg:
anyOf:
- type: number
- type: 'null'
default: null
description: '`weight`: kilograms, 0.5-30.'
examples:
- 8.45
title: Kg
cm:
anyOf:
- type: number
- type: 'null'
default: null
description: '`length`: centimetres, 40-120.'
examples:
- 72.5
title: Cm
ug:
anyOf:
- type: integer
- type: 'null'
default: null
description: '`supplement`: micrograms.'
examples:
- 10
title: Ug
note:
anyOf:
- maxLength: 300
type: string
- type: 'null'
default: null
description: Free text, up to 300 characters.
title: Note
author:
anyOf:
- type: string
- type: 'null'
default: null
description: Defaults to the calling user's username.
title: Author
timezone:
anyOf:
- type: string
- type: 'null'
default: null
description: IANA zone if `ts` is in a different zone from the family's.
title: Timezone
client_id:
anyOf:
- type: string
- type: 'null'
default: null
description: 'Same as the `Idempotency-Key` header, for clients that can''t set headers. 8-128 characters: letters, digits, `_ - . :`. If both are sent they must match.'
examples:
- lq_3f2b7c9e-1d4a-4e0b-9a51-7c2d8e6f0a13
title: Client Id
required:
- type
title: LogEventRequest
type: object
Warning_:
properties:
type:
type: string
title: Type
examples:
- asleep_at_time
message:
type: string
title: Message
sleep_span:
anyOf:
- additionalProperties: true
type: object
- type: 'null'
title: Sleep Span
type: object
required:
- type
- message
title: Warning_
UndoResponse:
properties:
status:
type: string
const: ok
title: Status
removed:
anyOf:
- $ref: '#/components/schemas/Event'
- type: 'null'
description: '`null` when there was nothing to undo.'
message:
anyOf:
- type: string
- type: 'null'
title: Message
live:
anyOf:
- additionalProperties: true
type: object
- type: 'null'
title: Live
type: object
required:
- status
title: UndoResponse
RecipeRequest:
properties:
name:
examples:
- lentil dahl
title: Name
type: string
ingredients:
examples:
- - red lentils
- carrot
- coconut milk
items:
type: string
title: Ingredients
type: array
required:
- name
- ingredients
title: RecipeRequest
type: object
EventIdRequest:
properties:
id:
examples:
- af73aec9
title: Id
type: string
required:
- id
title: EventIdRequest
type: object
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
Event:
properties:
id:
type: string
title: Id
description: 8-hex-char id, unique within the family.
examples:
- af73aec9
ts:
type: string
title: Ts
description: ISO 8601 local timestamp.
examples:
- '2026-09-20T09:49:53'
type:
type: string
enum:
- milk
- sleep
- wake
- nappy
- poop
- solid
- note
- supplement
- weight
- length
title: Type
author:
type: string
title: Author
description: 'Who logged it: the caregiver''s first name, or `Family admin` / `Partner` until they''ve set one (`app`/`agent` for non-human). Never an email or username.'
default: app
author_name:
anyOf:
- type: string
- type: 'null'
title: Author Name
description: Same as `author` (display name).
author_is_me:
anyOf:
- type: boolean
- type: 'null'
title: Author Is Me
description: 'Web session/token only: true when the current caregiver logged it.'
ml:
anyOf:
- type: integer
maximum: 500.0
minimum: 0.0
- type: 'null'
title: Ml
description: '`milk` only: millilitres (0 for a breastfeed).'
kind:
anyOf:
- type: string
- type: 'null'
title: Kind
description: '`nappy`: wet|dirty|both|dry. `supplement`: vitd|iron|multivitamin.'
foods:
anyOf:
- items:
type: string
type: array
- type: 'null'
title: Foods
description: '`solid` only: what was eaten.'
meal_type:
anyOf:
- type: string
enum:
- meal
- snack
- type: 'null'
title: Meal Type
description: '`solid` only.'
kg:
anyOf:
- type: number
- type: 'null'
title: Kg
description: '`weight` only.'
cm:
anyOf:
- type: number
- type: 'null'
title: Cm
description: '`length` only.'
ug:
anyOf:
- type: integer
- type: 'null'
title: Ug
description: '`supplement` only: micrograms.'
note:
anyOf:
- type: string
maxLength: 300
- type: 'null'
title: Note
reactions:
anyOf:
- items:
additionalProperties: true
type: object
type: array
- type: 'null'
title: Reactions
description: 'Emoji reactions from caregivers: `emoji`, `ts`, `user` (display name), `mine`.'
type: object
required:
- id
- ts
- type
title: Event
description: One logged event. Fields beyond `id`/`ts`/`type`/`author` depend on `type`.
FoodSuggestionsResponse:
properties:
suggestions:
items:
type: string
type: array
title: Suggestions
description: Recently logged foods, most recent first.
type: object
required:
- suggestions
title: FoodSuggestionsResponse
LogEventResponse:
properties:
status:
type: string
const: ok
title: Status
event:
anyOf:
- $ref: '#/components/schemas/Event'
- type: 'null'
description: The saved entry. On a replay, the entry the first request made (null if it has since been deleted).
replayed:
type: boolean
title: Replayed
description: 'True when this `Idempotency-Key` was already used: nothing new was logged.'
default: false
live:
additionalProperties: true
type: object
title: Live
description: The refreshed live-status block (same shape as `GET /api/vega/live`).
unknown_foods:
items:
type: string
type: array
title: Unknown Foods
description: '`solid` foods with no nutrient data.'
warning:
anyOf:
- $ref: '#/components/schemas/Warning_'
- type: 'null'
description: Soft conflict, e.g. a feed logged inside a recorded sleep span. The event was still saved.
type: object
required:
- status
- event
- live
title: LogEventResponse
EventReactRequest:
properties:
id:
examples:
- af73aec9
title: Id
type: string
emoji:
examples:
- ❤️
title: Emoji
type: string
required:
- id
- emoji
title: EventReactRequest
type: object
EventMutationResponse:
properties:
status:
type: string
const: ok
title: Status
event:
anyOf:
- $ref: '#/components/schemas/Event'
- type: 'null'
removed:
anyOf:
- $ref: '#/components/schemas/Event'
- type: 'null'
live:
anyOf:
- additionalProperties: true
type: object
- type: 'null'
title: Live
type: object
required:
- status
title: EventMutationResponse
ErrorResponse:
properties:
error:
type: string
title: Error
description: Machine-readable code (e.g. `unauthorized`, `not_found`) or a short message.
examples:
- unauthorized
message:
anyOf:
- type: string
- type: 'null'
title: Message
description: Optional human-readable detail.
type: object
required:
- error
title: ErrorResponse
description: Every error is a JSON object with an `error` code or message.
EventsResponse:
properties:
events:
items:
$ref: '#/components/schemas/Event'
type: array
title: Events
type: object
required:
- events
title: EventsResponse
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque
description: 'Token from Settings → Connect an assistant or `POST /api/vega/token`. Scopes: `read`, `write`. A `read` token gets `403 insufficient_scope` on any operation whose `x-required-scope` is `write`.'
sessionCookie:
type: apiKey
in: cookie
name: luna_session
description: Browser session set by `POST /login` or `POST /api/vega/onboard`.
externalDocs:
description: Developer portal
url: https://getmyluna.app/developers