# Amy API and MCP

Amy exposes an external API for user-authorized clients. The first release is
designed for Claude, ChatGPT custom connectors, MCP-compatible clients, and
small custom tools that need to work with a user's Amy nutrition journal. MCP
connectors can also log food, correct a logged food item, merge existing food
lines, or delete one logged item when the user explicitly asks.

Production base URL:

```text
https://connect.amyfoodjournal.com
```

Hosted docs:

```text
https://connect.amyfoodjournal.com/docs
```

OpenAPI:

```text
https://connect.amyfoodjournal.com/openapi.json
```

## What Is Available

REST:

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/api/v1/me` | Profile, nutrition goals, water settings, timezone, and health profile. |
| `GET` | `/api/v1/nutrition/summary` | Daily totals, goals, water, and contributing entries. |
| `GET` | `/api/v1/food-entries` | Recent journal entries with item-level nutrition. |
| `GET` | `/api/v1/weight-history` | Recent weight history entries. |

MCP:

```text
POST https://connect.amyfoodjournal.com/mcp
```

Tools:

| Tool | Description |
| --- | --- |
| `log_food` | Log one food item or up to 12 food lines, with optional detailed analysis context, then return logged nutrition and updated day totals. |
| `edit_food_item` | Correct exactly one existing logged item, update the visible journal line, and return updated nutrition and day totals. |
| `merge_food_items` | Combine 2 to 8 existing items from the same journal entry into one visible line while preserving summed nutrition. |
| `delete_food_item` | Remove exactly one existing logged item, delete the visible journal line, and return updated day totals. |
| `get_nutrition_summary` | Daily totals, goals, water, and entries. |
| `list_food_entries` | Recent journal entries with item-level nutrition. |
| `get_profile` | Profile, goals, timezone, water, and health settings. |
| `list_weight_history` | Recent weight entries. |

REST endpoints are read-only. MCP exposes read tools plus explicit nutrition
write tools for logging, correcting, merging, and deleting food. Amy API keys
use `amy.read` for reads and `nutrition.write` for food logging, food-item
corrections, merges, and deletes. OAuth connectors currently request
Supabase's standard `openid email profile` scopes, and Amy enforces connector
permissions, premium/beta access, duplicate detection, and food logging safety
limits in the resource server.

## Authentication

Hosted connectors should use OAuth. API keys remain available for scripts,
local tools, and MCP clients that support custom Authorization headers.

OAuth metadata:

```text
https://connect.amyfoodjournal.com/.well-known/oauth-protected-resource
```

OAuth consent page:

```text
https://connect.amyfoodjournal.com/oauth/consent
```

Authorization server:

```text
https://jabgxipuolfxmkwtgrii.supabase.co/auth/v1
```

OAuth requests use:

```text
openid email profile
```

Production OAuth setup checklist:

- Enable Supabase Auth OAuth 2.1 Server.
- Set Supabase Auth Site URL to `https://connect.amyfoodjournal.com`.
- Set OAuth Server Authorization Path to `/oauth/consent`.
- Enable Dynamic Client Registration for MCP clients, or pre-register specific
  ChatGPT/Claude OAuth clients.
- Prefer asymmetric JWT signing (`RS256` or `ES256`) before requesting `openid`.
- The consent page shows **Sign in with Google** and **Sign in with Apple** by
  default, plus email/password sign-in and a Forgot Password flow. Keep the
  matching Supabase web OAuth provider client IDs and secrets configured before
  using those buttons in production.
- Add an access-token hook later if we want strict `aud` binding to
  `https://connect.amyfoodjournal.com/mcp`.

The OAuth access token is sent as a bearer token:

```bash
curl -H "Authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN" \
  "https://connect.amyfoodjournal.com/api/v1/nutrition/summary"
```

For API keys, create a key in Amy Settings under **Amy API**. Copy it
immediately; Amy shows the full key once and stores only a keyed hash
server-side.

Send the key as a bearer token:

```bash
curl -H "Authorization: Bearer YOUR_AMY_API_KEY" \
  "https://connect.amyfoodjournal.com/api/v1/nutrition/summary"
```

The API also accepts this fallback header:

```text
X-Amy-API-Key: YOUR_AMY_API_KEY
```

Key format:

```text
amy_live_<public_id>_<secret>
```

Security properties:

- The raw API key is returned only once at creation time.
- The database stores a non-secret public id, a display prefix, and an HMAC
  hash of the full key.
- Keys can be revoked without deleting historical usage events.
- Last-used time and a keyed IP hash are stored for auditability.
- API keys that need MCP food logging, item correction, item merging, or item deletion should include both `amy.read` and
  `nutrition.write`.

## Rate Limits

Default limits:

| Limit | Default |
| --- | --- |
| Requests per key | `120` per hour |
| OAuth connector requests | `240` per user/client per hour |
| MCP nutrition mutations | `12` per 10 minutes, `25` per hour, `60` per day |
| MCP log items per call | `12` visible food lines |
| MCP created food lines | `30` per 10 minutes, `60` per hour, `100` per day, `40` per journal date per day |
| MCP merge items per call | `2` to `8` existing food items |
| MCP edit/delete items per call | `1` existing food item |
| MCP nutrition write safety cap | Rolling monthly backstop, configured server-side |
| Active keys per user | `10` |

The request rate limit is counted from persistent usage events, so it applies
across service restarts and instances. MCP nutrition writes have a separate
audit table for duplicate detection, abuse analysis, total mutation caps,
created-line caps, cooldowns, and internal cost tracking. Tool responses return
nutrition details, not provider costs. If a
bulk request exceeds a tool cap, Amy returns a structured error with the relevant
limit so the client can ask the user to split or narrow the request.

## Quickstarts

### ChatGPT custom connector

Create a custom connector with:

```text
Connector URL: https://connect.amyfoodjournal.com/mcp
```

ChatGPT discovers Amy's protected-resource metadata, starts OAuth with PKCE,
and sends `Authorization: Bearer <token>` on subsequent MCP requests.

### Claude custom connector

Use the custom connector dialog:

```text
Name: Amy
Remote MCP server URL: https://connect.amyfoodjournal.com/mcp
OAuth Client ID: leave blank unless you pre-register a client
OAuth Client Secret: leave blank for public/DCR clients
```

### REST with OAuth

```bash
curl -H "Authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN" \
  "https://connect.amyfoodjournal.com/api/v1/nutrition/summary?date=2026-06-14"
```

### REST with an API key

Get today's nutrition summary in the user's Amy timezone:

```bash
curl -H "Authorization: Bearer YOUR_AMY_API_KEY" \
  "https://connect.amyfoodjournal.com/api/v1/nutrition/summary"
```

Get a specific day:

```bash
curl -H "Authorization: Bearer YOUR_AMY_API_KEY" \
  "https://connect.amyfoodjournal.com/api/v1/nutrition/summary?date=2026-06-14"
```

List recent food entries:

```bash
curl -H "Authorization: Bearer YOUR_AMY_API_KEY" \
  "https://connect.amyfoodjournal.com/api/v1/food-entries?start_date=2026-06-01&end_date=2026-06-14&limit=14"
```

### Claude Code with an API key

```bash
claude mcp add --transport http amy https://connect.amyfoodjournal.com/mcp \
  --header "Authorization: Bearer YOUR_AMY_API_KEY"
```

### MCP JSON with an API key

```json
{
  "mcpServers": {
    "amy": {
      "type": "http",
      "url": "https://connect.amyfoodjournal.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_AMY_API_KEY"
      }
    }
  }
}
```

### MCP food logging

When the user says something like "I ate two eggs and toast, log it," MCP
clients should call `log_food`:

```json
{
  "food_text": "two eggs\ntoast with butter",
  "date": "2026-06-14"
}
```

If the user gives a concise title plus extra detail, keep `food_text` short and
put the richer context in `detailed_description`:

```json
{
  "food_text": "iced matcha latte",
  "detailed_description": "large iced matcha latte from Da Vien, oat milk, light ice, about 16 oz",
  "date": "2026-06-14"
}
```

`food_text` should be only the visible journal title or lines: food/drink name,
amount, and essential visible modifiers. Put non-visible context in
`detailed_description`, including portion or prep details, brand or restaurant
context, photo-like observations, timestamps, ids, test labels, and debugging or
workflow notes.

`date` is optional and defaults to today in the user's Amy timezone. The tool
supports multiple food lines so end-of-day catch-up logging can happen in one
call. Keep the visible `food_text` concise; the MCP schema caps it at 500
characters, 12 non-empty lines, and caps `detailed_description` at 1,498
characters so the combined analysis text stays within Amy's 2,000-character
limit. If the user asks to log more than 12 items at once, split the request or
ask them which 12 to do first. Amy analyzes `food_text` by itself when no details
are provided, or analyzes `food_text` plus `detailed_description` when details
are provided. The visible journal line stays as the concise `food_text`, and Amy
stores the optional detailed description separately so the app can show it on
the nutrition detail sheet. Amy detects duplicate retries for the same
title/details/date and applies cooldowns plus a rolling monthly safety cap. The
response includes logged items, macros, calories, confidence, and updated day
totals; it never includes internal provider cost.

### MCP food item editing

When the user asks to correct an existing item, MCP clients should call
`edit_food_item`. The tool edits exactly one item per call. Prefer an exact
`item_id` returned by `list_food_entries` or `get_nutrition_summary`; otherwise
provide `date` and `item_query` so Amy can find one clear matching item. If the
user just logged, edited, merged, or deleted food, refresh with
`list_food_entries` before using an old `item_id`. If several items match, Amy
returns candidates instead of editing.

```json
{
  "date": "2026-06-14",
  "item_query": "toast",
  "replacement_text": "two slices sourdough toast",
  "correction": "use the sourdough nutrition instead",
  "detailed_description": "two slices of thick sourdough toast, lightly buttered"
}
```

`replacement_text` is the concise visible journal line. Put explanations,
edit instructions, and direct corrections in `correction` so Amy can use them
for smart analyze without turning them into the journal title. Put longer
portion/prep/brand context, timestamps, ids, test labels, photo-like
observations, and other non-visible food details in `detailed_description`.
`detailed_description` is optional and backward-compatible: older clients can
omit it, and Amy preserves/reuses any existing detailed description for the item.

Amy reuses the Gemini smart-analyze correction path, including for amount-only
updates such as "one egg" to "two eggs". Existing or supplied detailed
descriptions are factored into the edit analysis and stored separately from the
visible journal line. The visible journal text line and item nutrition are
updated together, itemized nutrition is refreshed, and day totals are
recalculated. The tool is guarded by the same connector write scope,
premium/beta gate, cooldowns, and rolling monthly safety cap as `log_food`.

### MCP food item merging

When the user asks to combine existing lines without changing nutrition, MCP
clients should call `merge_food_items`. Prefer exact `item_ids` from
`list_food_entries` or `get_nutrition_summary`; otherwise provide `date` and
`item_queries` so Amy can find clear matches.

```json
{
  "date": "2026-06-14",
  "item_queries": ["eggs", "rice"],
  "merged_text": "eggs and rice"
}
```

Amy preserves nutrition by summing the selected existing items, soft-deletes the
extra items, rewrites the visible journal text into one line, reindexes the
remaining live lines, and recalculates day totals. This tool does not re-run
nutrition analysis or return internal cost. At most 8 items can be merged in one
call.

### MCP food item deletion

When the user asks to remove an existing logged item, MCP clients should call
`delete_food_item`. The tool deletes exactly one item per call. Prefer an exact
`item_id` from `list_food_entries` or `get_nutrition_summary`; otherwise provide
`date` and `item_query` so Amy can find one clear match. If the user just
changed the day, refresh with `list_food_entries` before deleting so item ids and
line indexes are current.

```json
{
  "date": "2026-06-14",
  "item_query": "toast"
}
```

Amy soft-deletes the selected item, removes the matching visible journal text
line, reindexes the remaining live lines, and recalculates day totals. This
tool is explicitly destructive, does not re-run nutrition analysis, and returns
candidates instead of deleting when the match is ambiguous. Do not use MCP
deletion for broad wipe requests such as deleting all data, deleting an account,
clearing history, deleting all food logs, or deleting everything. Amy MCP can
only remove specific food items. Clients should not call tools or ask for
confirmation for broad wipe requests; they should tell the user to contact Amy
support for broad data, account, or history deletion.

### MCP date-range food analysis

For questions like "summarize what I ate this week" or "what did I eat from
Monday through Friday?", MCP clients should call `list_food_entries` with
inclusive `start_date` and `end_date`:

```json
{
  "start_date": "2026-06-08",
  "end_date": "2026-06-14",
  "limit": 90
}
```

The returned entries include item-level nutrition, day dates, entry totals, and
current item ids. The client can summarize trends, repeated foods, estimated
protein/calorie patterns, or specific days from that data. Use the same read
before bulk edit/delete requests so write tools operate on current item ids.

## REST Reference

### `GET /api/v1/me`

Returns profile and settings.

Example response:

```json
{
  "profile": {
    "id": "user_uuid",
    "name": "Amy User",
    "email": "user@example.com",
    "has_premium": true
  },
  "settings": {
    "nutrition_goals": {
      "daily_calorie_goal": 2200,
      "protein": 150,
      "carbs": 220,
      "fat": 73,
      "sugar": null,
      "fiber": 30,
      "sodium": 2300
    },
    "water": {
      "enabled": true,
      "unit": "ml",
      "daily_goal_ml": 2500
    },
    "timezone": "America/Chicago"
  }
}
```

### `GET /api/v1/nutrition/summary`

Query parameters:

| Name | Description |
| --- | --- |
| `date` | Optional `YYYY-MM-DD`. Defaults to today in the user's Amy timezone. |

Example response:

```json
{
  "date": "2026-06-14",
  "timezone": "America/Chicago",
  "totals": {
    "calories": 1820,
    "protein": 132.5,
    "carbs": 181,
    "fat": 58.2,
    "sugar": 42,
    "fiber": 28,
    "sodium": 2100,
    "water_milliliters": 1750
  },
  "goals": {
    "calories": 2200,
    "protein": 150,
    "carbs": 220,
    "fat": 73,
    "sugar": null,
    "fiber": 30,
    "sodium": 2300,
    "water_milliliters": 2500
  },
  "entries": []
}
```

### `GET /api/v1/food-entries`

Query parameters:

| Name | Description |
| --- | --- |
| `start_date` | Optional inclusive `YYYY-MM-DD` start date. |
| `end_date` | Optional inclusive `YYYY-MM-DD` end date. |
| `limit` | Optional entry limit. Defaults to `14`, maximum `90`. |

Example response:

```json
{
  "entries": [
    {
      "id": "entry_uuid",
      "date": "2026-06-14",
      "food_text": "Greek yogurt, blueberries, granola",
      "timezone": "America/Chicago",
      "totals": {
        "calories": 420,
        "protein": 32,
        "carbs": 48,
        "fat": 11,
        "sugar": 25,
        "fiber": 6,
        "sodium": 140,
        "water_milliliters": 0
      },
      "items": [
        {
          "id": "item_uuid",
          "description": "Greek yogurt",
          "calories": 180,
          "protein": 20,
          "carbs": 8,
          "fat": 4,
          "sugar": 7,
          "fiber": 0,
          "sodium": 65,
          "is_water_entry": false,
          "water_milliliters": 0
        }
      ]
    }
  ],
  "limit": 14
}
```

### `GET /api/v1/weight-history`

Query parameters:

| Name | Description |
| --- | --- |
| `limit` | Optional entry limit. Defaults to `30`, maximum `365`. |

Example response:

```json
{
  "entries": [
    {
      "id": "weight_uuid",
      "weight_kg": 82.1,
      "recorded_at": "2026-06-14T13:00:00.000Z",
      "recorded_date": "2026-06-14",
      "source": "manual",
      "notes": null
    }
  ],
  "limit": 30
}
```

## Error Format

Errors return a stable machine-readable code plus a human-readable message:

```json
{
  "error": "rate_limit_exceeded",
  "message": "API key rate limit exceeded (120 requests per hour)"
}
```

Common statuses:

| Status | Meaning |
| --- | --- |
| `400` | Invalid query parameter. |
| `401` | Missing, malformed, revoked, or expired key. |
| `403` | Missing scope or forbidden MCP origin. |
| `429` | Per-key, OAuth connector, or MCP food logging safety limit exceeded. |
| `500` | Unexpected server error. |

## ChatGPT And OAuth

Authenticated ChatGPT connectors use OAuth 2.1 with PKCE. Amy exposes
protected-resource metadata at:

```text
https://connect.amyfoodjournal.com/.well-known/oauth-protected-resource
```

ChatGPT then discovers the Supabase OAuth authorization server, redirects the
user through Amy's consent page, and sends the issued access token to the MCP
server as `Authorization: Bearer <token>`.

Supabase's OAuth server does not currently support custom Amy scopes, so Amy
uses `openid email profile` for the OAuth flow and enforces connector
permissions in the MCP/API server. When Supabase adds custom scopes, Amy can
map OAuth to `amy.read` and `nutrition.write` directly.

## Design Principles

- Keep this API as a user-centered facade, not a raw database mirror.
- Return normalized nutrition and journal concepts.
- Omit internal sync fields, deleted rows, prompts, and private storage URLs.
- Keep MCP tools literal and conservative.
- Keep write tools narrow, explicit, audited, and protected by abuse and cost
  controls.
