in inTouch Docs
Get your key
Capability

🍽 Recipes stable

Send meal suggestions into a user's feed and read back their preferences, so an assistant can learn what they actually like to cook.

The user swipes through your suggestions per meal category and the ones they like become a day plan in the app. Every decision is readable through the history endpoint, which is what makes the loop useful: suggest, observe, suggest better.

The app keeps the last plan it loaded. Once suggestions have arrived they stay readable without a connection — the phone falls back to the stored plan only when the server is unreachable, so an empty day stays empty rather than resurrecting yesterday.

The daily loop

Nothing about the user's diet is stored on the server — no profile endpoint, no settings screen. What they eat, avoid and cook for lives in your prompt file on their own machine. Once a day the assistant runs three moves:

1. GET  /recipe/history?date=<today>   are today's suggestions already there?
2. GET  /recipe/history?state=liked    what did they cook, what did they reject
3. POST /recipe/suggest                today's dishes, per meal category

Step 1 is the guard. summary.total > 0 for today means the run stops before it writes anything — important here, because suggest appends: a second run would double every meal in the feed instead of replacing it.

Not interested in the plumbing? Skip to the lazy path and let an assistant build all of it for you.

What lives where

Two places, and the split is deliberate:

On the serverIn your prompt file
Every dish you were ever sent, its meal category and date, and whether you liked it, rejected it or left it open. The distilled version: household, diet, allergies, hard no-gos — and the learned: lines the assistant writes after reading your decisions.
Durable. Readable any time with your key. Survives a reinstall, a new phone and a new laptop. Yours. A plain text file you can read, correct or delete — open it in any editor.

The rule the assistant follows: raw events on the server, conclusions in the file. It never copies the history into the file — it re-reads the history and rewrites the conclusions, so a wrong assumption is one deleted line away from being gone.

The lazy path: hand the whole setup to your AI

Copy this into Claude Code or Codex CLI, replace the key, and answer the questions. It reads the docs itself and comes back when the first dishes are in your feed and tomorrow's run is scheduled.

Set up my daily inTouch meal suggestions, end to end.

Read these two pages first — they are plain text, written for you, and contain
everything you need:
  https://docs.in-touch.cc/recipes.txt
  https://docs.in-touch.cc/llms.txt
On the recipes page, follow the section "self-setup" — it is the job description.

My inTouch API key: itk_PASTE_YOUR_KEY_HERE

Ask me how I eat, then do the rest yourself: write the prompt file that holds my
diet, allergies and household, push a first day of suggestions, and install the
daily automation for my operating system. Show me what you are going to create
before you create it, and never print my key.
What it does with that. It asks how many people it cooks for, your diet, allergies, hard no-gos and which meals you want each day, writes them into ~/intouch/recipe-prompt.md, pushes a first day into the app, adds a guard script and registers a scheduler — launchd, systemd, Task Scheduler or a GitHub Actions cron. Then it runs the script once so you can watch the guard refuse a second push.

The prompt stays short on purpose: the instructions live in recipes.txt under self-setup, so they improve without you ever copying a new prompt. What you paste is a pointer, not a program.

What it needs — and where it stops. An assistant with web access and a terminal: Claude Code and Codex CLI both qualify, and Codex additionally needs network access allowed in its sandbox. A browser chat can push a first day of dishes, but it cannot write a prompt file or install a scheduler — it will say so and stop there.

The manual path: set it up yourself

1 · Get your key

In the app: Settings → App & Media → inTouch API, then Generate key. The same key already works for Vocabulary — you never need a second one.

2 · Pick your AI

Identical to the vocabulary setup: npm install -g @anthropic-ai/claude-code or npm install -g @openai/codex, sign in once, then run the daily prompt headless with claude -p "$(cat ~/intouch/recipe-prompt.md)" --allowedTools "Bash(curl:*)" or codex exec --full-auto "$(cat ~/intouch/recipe-prompt.md)". Codex needs network access allowed in its sandbox — see the vocabulary page for the details.

3 · Save the daily prompt

Put this in ~/intouch/recipe-prompt.md. The header block is the only place your preferences exist — edit those lines whenever your week changes.

You keep my inTouch meal suggestions up to date. Run this once a day and do not ask
me anything — decide on your own and report in one line at the end.

HOW I EAT  (edit these lines, they live here and nowhere else)
  cooking for:      2 adults
  diet:             vegetarian, fish is fine
  allergies:        hazelnuts — never suggest them, not even as a topping
  hard no:          coriander, blue cheese
  meals per day:    breakfast, lunch, dinner
  options per meal: 2
  effort:           weekdays max 30 min, weekends anything
  learned:          (you append one line here when you notice a pattern)
  last read:        (cursor from the history; you overwrite it every run)

API base:  https://api.in-touch.cc
Auth:      header "X-API-Key" with the value of the INTOUCH_API_KEY environment variable
Reference: https://docs.in-touch.cc/recipes.txt

STEP 1 - IS TODAY ALREADY DONE?
  GET /api/addons/recipe/history?date=<today, UTC>
  If summary.total is greater than 0, stop. suggest APPENDS - a second run would
  double every meal in the feed. Never push twice for the same day.

STEP 2 - READ WHAT WORKED  (only what is new)
  GET /api/addons/recipe/history?since=<last read>&limit=200
    - no "last read" yet? Leave since out for the first run.
    - keep calling with the returned cursor while has_more is true; this brings
      new dishes AND hearts tapped on older ones.
    - write the final cursor into "last read:" at the top of this file.
  Every few weeks, or when the picture feels thin, read the whole thing again:
  GET /api/addons/recipe/history?state=liked
  GET /api/addons/recipe/history?state=disliked
  Look for patterns, not single dishes: cuisines, main ingredients, effort,
  which meal categories they never decide on at all.

STEP 3 - BUILD TODAY'S DISHES
  - One entry per meal category from the header block, "options per meal" each.
  - Respect diet, allergies and hard no-gos absolutely. An allergy is not a
    preference: never send it, in any form, in any dish.
  - Lean towards what was liked, avoid what was rejected, and do not repeat a
    dish they already saw this week.
  - Fill in the recipe object properly: ingredients, steps, duration, servings
    matching "cooking for", plus tags and nutrition when you know them.

STEP 4 - PUSH IT
  POST /api/addons/recipe/suggest  with {"plan_date": "<today>", "recipes": [ ... ]}
  Each recipe needs meal_category and title; everything else is optional but the
  card looks poor without image, duration and steps.
  The response reports inserted, rejected and errors[] per index. Fix rejected
  items and push ONLY those again - suggest appends, so never resend the batch.

STEP 5 - REMEMBER
  If step 2 showed a clear pattern, append ONE line to "learned:" at the top of
  this file. That is how this file gets better without me having to tell you.

Finish with one line: date, how many dishes, and what you changed since yesterday.

4 · Wrap it in a guard script

Same shape as the vocabulary guard, different question: has today already been filled?

#!/usr/bin/env bash
# ~/intouch/recipes.sh
set -euo pipefail

export INTOUCH_API_KEY="itk_YOUR_KEY"
BASE="https://api.in-touch.cc"
TMP="$(mktemp)"; trap 'rm -f "$TMP"' EXIT

today="$(date -u +%F)"                      # the API works in UTC

# No network yet (the machine just woke up) is not an error. A rejected key is:
# it has to fail loudly, or the schedule quietly does nothing for weeks.
code="$(curl -sS -o "$TMP" -w '%{http_code}' \
        -H "X-API-Key: $INTOUCH_API_KEY" \
        "$BASE/api/addons/recipe/history?date=$today")" || {
  echo "inTouch: API not reachable, leaving it for the next run"; exit 0
}
case "$code" in
  200)     ;;
  401|403) echo "inTouch: key rejected ($code) - fix it in this script"; exit 1 ;;
  *)       echo "inTouch: API returned $code, trying again next run"; exit 0 ;;
esac

if [ "$(jq -r '.summary.total // 0' "$TMP")" -gt 0 ]; then
  echo "inTouch: suggestions for $today already exist"
  exit 0
fi

claude -p "$(cat "$HOME/intouch/recipe-prompt.md")" --allowedTools "Bash(curl:*)"
# Codex:  codex exec --full-auto "$(cat "$HOME/intouch/recipe-prompt.md")"

Run it every day

The schedulers are exactly the four from the vocabulary page — launchd on macOS, a systemd timer on Linux and a VPS, Task Scheduler on Windows, GitHub Actions when no machine of yours is awake. All of them are written to catch up after a missed run, which is what makes a laptop schedule work.

Take the snippets from there and change two things: the script path (recipes.sh) and the job name (com.intouch.recipes, intouch-recipes.timer, inTouch Recipes). Running both capabilities side by side is two independent jobs — and a good reason to give them distinct names.

The full snippets are also in recipes.txt under self-setup: working snippets, already carrying the recipe names — that is the copy an assistant reads.

Submit suggestions

POST /api/addons/recipe/suggest X-API-Key

Body

FieldTypeDescription
recipes[]arrayOne or more recipes, max 50 per request
plan_datestringOptional YYYY-MM-DD, defaults to today. Applies to recipes without their own plan_date.

A bare array, or a single recipe object, is accepted too — the recipes wrapper is the clearest form.

Recipe object

FieldTypeDescription
meal_categorystringOne of the categories below
titlestringDish name
imagestringImage URL
descriptionstringShort description
durationstringe.g. "25 min"
difficultystringe.g. "Easy"
servingsnumberPortions
ingredientsstring[]Ingredient list
stepsstring[]Preparation steps
tagsstring[]e.g. ["vegan","gluten-free"]
nutritionobject{ calories, protein, carbs, fat }

Unknown fields are stored and handed back verbatim by the history endpoint, so you can attach your own metadata.

Meal categories

breakfastbrunchlunch dinnersnackdrink dessertsidedish

Anything else is rejected for that item — the rest of the batch still goes through.

Example

curl -X POST https://api.in-touch.cc/api/addons/recipe/suggest \
  -H "X-API-Key: itk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_date": "2026-07-25",
    "recipes": [
      {
        "meal_category": "breakfast",
        "title": "Overnight Oats with Berries",
        "image": "https://example.com/oats.jpg",
        "description": "Creamy oats soaked overnight.",
        "duration": "5 min + overnight",
        "difficulty": "Easy",
        "servings": 1,
        "ingredients": ["50g oats", "150ml milk", "berries"],
        "steps": ["Mix", "Chill overnight", "Top with berries"],
        "tags": ["vegetarian", "meal-prep"],
        "nutrition": { "calories": 320, "protein": "12g", "carbs": "48g", "fat": "8g" }
      }
    ]
  }'
{ "success": true, "inserted": 1, "rejected": 0, "plan_date": "2026-07-25" }

Read preferences

GET /api/addons/recipe/history X-API-Key

Every dish ever sent to this user, with what they did about it. This is the durable memory of the capability: it survives new pushes, reinstalls and a new laptop, and it is what lets an assistant rebuild someone's taste from scratch.

QueryDescription
stateliked · disliked · open · all (default)
dateOptional YYYY-MM-DD filter
limit1–500, default 100
sinceCursor from a previous response — returns only what was added or decided since then, oldest first
curl "https://api.in-touch.cc/api/addons/recipe/history?since=2026-07-26+06:00:00&limit=50" \
  -H "X-API-Key: itk_YOUR_KEY"
{
  "success": true,
  "state": "liked",
  "summary": { "total": 42, "liked": 9, "disliked": 12, "open": 21, "returned": 9 },
  "cursor": "2026-07-26 18:04:11",
  "has_more": false,
  "dishes": [
    { "id": 123, "meal_category": "dinner", "plan_date": "2026-07-25",
      "state": "liked", "recipe": { "title": "Oven Salmon", "...": "..." } }
  ]
}
Reading only what changed. Store the cursor from the response and pass it back as since next time. You then get new suggestions and decisions on older dishes — a heart tapped on last month's dinner shows up, because the cursor tracks when something was decided, not when it was created. Keep calling while has_more is true, each time with the newest cursor. Nothing is ever deleted; you are simply not re-reading it.

The cursor is opaque — hand back exactly the string you were given. Without since the response is the current picture, newest first.

summary always describes the whole picture (the whole date scope, every state), never just the page you asked for — otherwise ?state=liked would report disliked: 0 and an assistant would read the taste as one-sided. returned is how many dishes came back in this response.

StateMeaning
likedThe user tapped the heart — "I'll cook this today"
dislikedExplicitly rejected
openDelivered but not decided yet

Errors

StatusMeaning
401Missing X-API-Key header
403Invalid API key
400No valid recipe — missing title or invalid meal_category
500Server error
inTouch API · https://api.in-touch.cc · keep your key private.
# intouch api
base    https://api.in-touch.cc
auth    X-API-Key: itk_<64 hex>   one key per user, no expiry, no scopes
docs    https://docs.in-touch.cc
format  every response is JSON and carries "success"

# recipes            status: stable
Send meal suggestions into a user's feed, read back what they liked.
The user swipes through the suggestions per meal category; the ones they like
become a day plan. Loop: suggest, observe, suggest better.

# the daily loop
1. GET  /api/addons/recipe/history?date=<today>  are today's dishes already there?
2. GET  /api/addons/recipe/history?state=liked   what worked, what was rejected
3. POST /api/addons/recipe/suggest               today's dishes per meal category

IDEMPOTENCY GUARD - this one matters more than in most APIs:
suggest APPENDS, it does not replace. If history for today already returns
summary.total > 0, STOP. A second run doubles every meal in the user's feed.

THERE IS NO SERVER-SIDE PROFILE. Diet, allergies, household size and the meals
the user wants live in the agent's own prompt file on the user's machine. The
server stores only the suggestions themselves and the like/dislike decisions.

# endpoints
POST /api/addons/recipe/suggest    X-API-Key    push suggestions
GET  /api/addons/recipe/history    X-API-Key    read decisions

# POST /api/addons/recipe/suggest
body:
  recipes[]    array   REQUIRED   one or more recipes, max 50 per request
  plan_date    string             YYYY-MM-DD, defaults to today; applies to
                                  recipes without their own plan_date
A bare array or a single recipe object is accepted too; the "recipes" wrapper
is the clearest form.

# recipe object
  meal_category  string     REQUIRED   one of the categories below
  title          string     REQUIRED   dish name
  image          string                image URL
  description    string                short description
  duration       string                "25 min"
  difficulty     string                "Easy"
  servings       number                portions
  ingredients    string[]              ingredient list
  steps          string[]              preparation steps
  tags           string[]              ["vegan","gluten-free"]
  nutrition      object                { calories, protein, carbs, fat }
Unknown fields are stored and handed back verbatim by history - use them for
your own metadata.

# meal_category (exact values)
breakfast  brunch  lunch  dinner  snack  drink  dessert  sidedish
Anything else rejects that ITEM; the rest of the batch still goes through.

# example
curl -X POST https://api.in-touch.cc/api/addons/recipe/suggest \
  -H "X-API-Key: itk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_date": "2026-07-25",
    "recipes": [
      { "meal_category": "breakfast",
        "title": "Overnight Oats with Berries",
        "image": "https://example.com/oats.jpg",
        "description": "Creamy oats soaked overnight.",
        "duration": "5 min + overnight",
        "difficulty": "Easy",
        "servings": 1,
        "ingredients": ["50g oats","150ml milk","berries"],
        "steps": ["Mix","Chill overnight","Top with berries"],
        "tags": ["vegetarian","meal-prep"],
        "nutrition": { "calories": 320, "protein": "12g",
                       "carbs": "48g", "fat": "8g" } }
    ]
  }'

200 { "success": true, "inserted": 1, "rejected": 0, "plan_date": "2026-07-25" }

# GET /api/addons/recipe/history
The durable memory of this capability: every dish ever sent, with what the user
did about it. Survives new pushes, reinstalls and a new machine.
query:
  state   liked | disliked | open | all   (default all)
  date    optional YYYY-MM-DD filter
  limit   1-500, default 100
  since   cursor from a previous response: only what was ADDED or DECIDED after
          it, oldest first. Opaque - hand back exactly the string you got.

curl "https://api.in-touch.cc/api/addons/recipe/history?state=liked&limit=50" -H "X-API-Key: itk_YOUR_KEY"

200 {
  "success": true,
  "state": "liked",
  "summary": { "total": 42, "liked": 9, "disliked": 12, "open": 21,
               "returned": 9 },
  "cursor": "2026-07-26 18:04:11",
  "has_more": false,
  "dishes": [
    { "id": 123, "meal_category": "dinner", "plan_date": "2026-07-25",
      "state": "liked", "recipe": { "title": "Oven Salmon", "...": "..." } }
  ]
}

# reading only what changed
Store "cursor" from the response, pass it back as ?since= next time, and keep
calling with the newest cursor while has_more is true. You get new suggestions
AND decisions on older dishes - a heart tapped on last month's dinner appears,
because the cursor follows when something was DECIDED, not when it was created.
Nothing is ever deleted; you are simply not re-reading it. Without since you get
the current picture, newest first.

summary always covers the WHOLE date scope and every state, never just the page
you asked for - otherwise ?state=liked would report disliked: 0 and you would
read the taste as one-sided. "returned" is how many dishes are in this response.

state meanings:
  liked     the user tapped the heart - "I'll cook this today"
  disliked  explicitly rejected
  open      delivered, not decided yet

# what lives where
server (durable, readable with the key, survives everything):
  every suggestion, its meal category, its date, and liked/disliked/open
local prompt file ~/intouch/recipe-prompt.md (the user owns and can edit it):
  the DISTILLED preferences - diet, allergies, household, the "learned:" lines
  you write after reading the history, and "last read:" (your history cursor)
Rule: raw events belong on the server, conclusions belong in the file. Never
copy the whole history into the file; re-read it and rewrite the conclusions.

# offline behaviour
The app keeps the last plan it successfully loaded and shows it without a
connection. It falls back to that copy ONLY when the server is unreachable, so a
day you deliberately left empty stays empty instead of resurrecting yesterday.

# self-setup - read this when a user asks you to "set up my meal suggestions"
They pasted a short prompt and their key; the whole job is below. Work through it
in order and do not invent extra steps, files, services or repositories.

0. THE KEY IS A CREDENTIAL. Never print it, never echo it into a log, never put it
   in the prompt file. It belongs in the guard script or an environment variable.

1. ASK - one message, with defaults so short answers are enough:
   how many people you cook for, diet (omnivore / vegetarian / vegan / other),
   allergies, hard no-gos, which meals per day (default breakfast, lunch, dinner),
   how many options per meal (default 2), and how much effort on a weekday.
   If the user already answered in their prompt, skip the questions.

2. WRITE THE PROMPT FILE - ~/intouch/recipe-prompt.md
   This file IS the profile; the server stores none. Put the answers in a header
   block ("cooking for", "diet", "allergies", "hard no", "meals per day",
   "options per meal", "effort", plus empty "learned:" and "last read:" lines)
   and below them the daily loop, the recipe object and the meal categories from
   this page, so a future run needs nothing but this file. Every later run reads
   only what is new (?since=<last read>), refreshes "learned:" and overwrites the
   cursor. Show it to the user before writing it.

3. PUSH A FIRST DAY
   Skip if history for today already has summary.total > 0.
   One entry per meal category, "options per meal" each, respecting diet and
   allergies absolutely. Read the response: fix rejected items and push ONLY
   those again - suggest appends, so never resend the whole batch.

4. INSTALL THE DAILY AUTOMATION
   Show the user the file paths and the schedule BEFORE creating anything, then:
   a) the prompt file from step 2 is already in place.
   b) ~/intouch/recipes.sh (chmod +x) - the guard:
      - read GET /api/addons/recipe/history?date=<today, date -u +%F>
      - API unreachable -> exit 0 (a machine that just woke has no network yet)
      - 401/403 -> exit 1 loudly, the key is wrong
      - summary.total > 0 -> exit 0, today is done
      - otherwise run your own CLI headless with the prompt file
   c) the scheduler that fits the user's OS, from "working snippets" below.
      If the user also runs the vocabulary capability, these are two independent
      jobs: keep the distinct names below so neither replaces the other.

5. VERIFY - run the guard script once by hand. It must report that today's
   suggestions already exist, because step 3 created them.

6. HAND OVER - name the two files, the scheduler entry and the next run time, and
   tell the user that changes ("more vegetarian", "no lunch", "I moved in with
   someone") are a few lines at the top of ~/intouch/recipe-prompt.md - by talking
   to you again, or in any text editor.

If you cannot run shell commands (browser chat, no terminal): do step 1, push a
first day (step 3), then tell the user plainly that the prompt file and the daily
schedule need Claude Code, Codex CLI or a cloud runner.

# self-setup: working snippets
Adapt paths and the CLI line; the shape is what matters.

--- ~/intouch/recipes.sh -----------------------------------------------------
#!/usr/bin/env bash
set -euo pipefail

export INTOUCH_API_KEY="itk_THE_USERS_KEY"
BASE="https://api.in-touch.cc"
TMP="$(mktemp)"; trap 'rm -f "$TMP"' EXIT

today="$(date -u +%F)"

# No network yet (the machine just woke up) is not an error. A rejected key is:
# it has to fail loudly, or the schedule quietly does nothing for weeks.
code="$(curl -sS -o "$TMP" -w '%{http_code}' \
        -H "X-API-Key: $INTOUCH_API_KEY" \
        "$BASE/api/addons/recipe/history?date=$today")" || {
  echo "inTouch: API not reachable, leaving it for the next run"; exit 0
}
case "$code" in
  200)     ;;
  401|403) echo "inTouch: key rejected ($code) - fix it in this script"; exit 1 ;;
  *)       echo "inTouch: API returned $code, trying again next run"; exit 0 ;;
esac

if [ "$(jq -r '.summary.total // 0' "$TMP")" -gt 0 ]; then
  echo "inTouch: suggestions for $today already exist"
  exit 0
fi

# your own CLI, headless, allowed to reach the network. For example:
#   claude -p "$(cat "$HOME/intouch/recipe-prompt.md")" --allowedTools "Bash(curl:*)"
#   codex exec --full-auto "$(cat "$HOME/intouch/recipe-prompt.md")"
------------------------------------------------------------------------------

--- macOS: ~/Library/LaunchAgents/com.intouch.recipes.plist --------------------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>              <string>com.intouch.recipes</string>
  <key>ProgramArguments</key>   <array>
    <string>/bin/bash</string>
    <string>/Users/YOU/intouch/recipes.sh</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict><key>Hour</key><integer>7</integer><key>Minute</key><integer>0</integer></dict>
  <key>RunAtLoad</key>          <true/>
  <key>StandardOutPath</key>    <string>/tmp/intouch-recipes.log</string>
  <key>StandardErrorPath</key>  <string>/tmp/intouch-recipes.log</string>
</dict>
</plist>

launchctl load -w ~/Library/LaunchAgents/com.intouch.recipes.plist
StartCalendarInterval catches up after sleep; RunAtLoad adds a run at login.
------------------------------------------------------------------------------

--- Linux / VPS: systemd user timer ------------------------------------------
# ~/.config/systemd/user/intouch-recipes.service
[Unit]
Description=inTouch vocabulary set
[Service]
Type=oneshot
ExecStart=%h/intouch/recipes.sh

# ~/.config/systemd/user/intouch-recipes.timer
[Unit]
Description=Daily inTouch vocabulary set
[Timer]
OnCalendar=*-*-* 07:00:00
Persistent=true
[Install]
WantedBy=timers.target

systemctl --user daemon-reload
systemctl --user enable --now intouch-recipes.timer
# VPS without a login session: loginctl enable-linger $USER
Persistent=true is the catch-up after a missed run.
------------------------------------------------------------------------------

--- Windows: Task Scheduler --------------------------------------------------
$action  = New-ScheduledTaskAction -Execute "powershell.exe" `
             -Argument "-File C:\intouch\recipes.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 7am
$set     = New-ScheduledTaskSettingsSet -StartWhenAvailable -WakeToRun
Register-ScheduledTask -TaskName "inTouch Recipes" -Action $action `
  -Trigger $trigger -Settings $set

-StartWhenAvailable is the catch-up. If the CLI lives in WSL, keep the bash
script and let the task call:  wsl -e ~/intouch/recipes.sh
------------------------------------------------------------------------------

--- No local machine: GitHub Actions -----------------------------------------
# .github/workflows/recipes.yml   (private repo, secrets for both keys)
name: inTouch Recipes
on:
  schedule:
    - cron: "0 5 * * *"
  workflow_dispatch:
jobs:
  push-set:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @anthropic-ai/claude-code
      - run: claude -p "$(cat recipes-prompt.md)" --allowedTools "Bash(curl:*)"
        env:
          ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
          INTOUCH_API_KEY:   \${{ secrets.INTOUCH_API_KEY }}

Scheduled runs can start a few minutes late, and GitHub disables the schedule
after 60 days without repository activity.
------------------------------------------------------------------------------

# errors
401  missing X-API-Key header
403  invalid API key
400  no valid recipe: missing title or invalid meal_category
500  server error

# pages
/                  overview, capability list
/quickstart        get a key, first request
/authentication    key format, rotation, failure modes
/conventions       envelope, status codes, limits, dates
/recipes           recipes capability
/vocabulary        vocabulary capability
/roadmap           capability anatomy, what is planned
every page is also plain text: append .txt  (e.g. https://docs.in-touch.cc/vocabulary.txt)
all pages at once: /llms.txt (index) and /llms-full.txt (everything)

# for agents
- Use only the endpoints listed here; do not invent paths, fields or values.
- Check "success" in the body, not just the HTTP status.
- Error strings are German and developer-facing; branch on the status code.
- Batches are accepted partially: read "rejected" and "errors[].index".
- Dates are UTC. Calendar days are YYYY-MM-DD, timestamps are ISO 8601.
- The API key is a credential: never log it, never pass it to a third party.
- Ask the user before writing content into their app for the first time.