---
name: airtable-routine-trigger
description: Bauanleitung, um eine Airtable-Feldänderung mit einer Claude-Code-Remote-Routine zu verbinden, die automatisch läuft (z.B. check-versicherung / check-ust-idnr, wenn ein Status auf "Ausstehend" geht). Deckt beide Hälften ab: (1) Remote-Routine + API-Trigger, (2) Airtable-Automation-Script, das die Routine per Fire-URL feuert — inkl. des exakten funktionierenden Scripts mit den nötigen anthropic-beta-Headern und einer 401-Troubleshooting-Tabelle. Nutze diesen Skill, wenn der User eine Airtable-Prüfung automatisch bei Statuswechsel auslösen, einen API-/Webhook-Trigger einrichten, eine Routine bauen oder einen 401 beim Routine-Fire debuggen will.
---

# Airtable → Claude-Routine automatisch triggern (Bauanleitung)

Verbindet eine **Airtable-Feldänderung** (z. B. `Status = Ausstehend`) mit einer
**Claude-Code-Remote-Routine**, die ein Dokument-Prüf-Skill unbeaufsichtigt ausführt
und das Ergebnis zurückschreibt.

```
Airtable: Feld → Zielwert
   ↓  (Automation "Run a script": fetch POST an die Fire-URL)
Routine läuft remote (Claude-Agent)  →  schreibt nach Airtable zurück
```

Es gibt **zwei Hälften**, beide nötig: **A** = Routine, **B** = Airtable-Auslöser.

---

## A — Remote-Routine + API-Trigger

1. Routine über den **`/schedule`-Skill** (bzw. `RemoteTrigger`-Tool) anlegen. Sie läuft remote in Anthropics Cloud, **ohne Zugriff auf lokale Dateien**. Daher entweder:
   - Prüflogik **komplett in den Routine-Prompt einbetten** (feste Airtable-IDs, 4 Kriterien, Entscheidungs-Gates, Dokument-Handling via PyMuPDF/Pillow, Datenschutz-Regel, Rückschreib-Felder), **oder**
   - **(besser, sobald Repo auf GitHub):** Routine mit **Repo-Checkout** konfigurieren und im Prompt nur sagen „klone Repo, wende `.claude/skills/<skill>/SKILL.md` auf alle Zielrecords an" → **eine** Quelle der Wahrheit.
2. **Airtable-MCP-Connector** anhängen (`mcp_connections`), damit der Agent lesen/schreiben kann.
3. Optional **stündlichen Cron** (`0 * * * *`) als Sicherheitsnetz behalten.
4. **API-Trigger aktivieren:** Routines-UI → „Add another trigger → **Call via API**". Liefert:
   - **Fire-URL:** `https://api.anthropic.com/v1/claude_code/routines/<TRIGGER_ID>/fire`
   - **Token:** `sk-ant-oat01-…`

> Bestehende Versicherungs-Routine: `Versicherung-Ausstehend-Check` = `trig_01KeXDX35v9WghEXfv3WWbAX`.

### Unbeaufsichtigt-Regeln im Routine-Prompt (zwingend)
- Nie nachfragen — nach festen Gates entscheiden.
- Kein Dokument → Record **überspringen**, Status unverändert.
- Genuin unklar → `In Prüfung` + `[ESKALATION]` (nicht raten).
- **Datenschutz:** sensible Beifang-Daten (Schulden/Mahnungen/Steuerrückstände) nie in partner-sichtbare Felder.

### ⚠️ Netzwerk-Zugriff des Environments (KRITISCH — sonst keine Analyse möglich)
Der Routine-Container hat eine **Netzwerk-Allowlist** (Environment-Setting **„Network access"**). Stufen:

| Stufe | Bedeutung | Folge |
|---|---|---|
| **None** | kein Internet | blockt alles |
| **Trusted** (Default) | nur verifizierte Paket-Quellen (pip ok) | **blockt die Airtable-Attachment-CDN!** |
| **Full** | uneingeschränkt | funktioniert |
| **Custom** | eigene Domain-Allowlist | funktioniert, least-privilege |

**Das Problem:** Mit „Trusted" scheitert der Dokument-Download mit `Host not in allowlist` (`v5.airtableusercontent.com`). Der Agent kann nichts analysieren und setzt nur „In Prüfung". **Kein Code-/Repo-Fix hilft** — es ist reine Netzwerk-Policy.

**Die Lösung:** „Network access" des Environments auf **`Full`** (schnell, garantiert) oder **`Custom`** mit dieser Liste:
```
*.airtableusercontent.com    # Partner-Dokumente (der Blocker)
pypi.org                     # pip: pymupdf
files.pythonhosted.org       # pip: pymupdf/pillow
```
**Setup script** des Environments (Tools vorinstallieren, schneller/zuverlässiger als im Prompt):
```bash
#!/bin/bash
pip install -q pymupdf pillow
```
Entweder das **bestehende** Environment bearbeiten (Routine behält ihre `environment_id`) **oder** ein neues anlegen und die Routine per `RemoteTrigger update` auf die neue `environment_id` umhängen.

---

## B — Airtable-Automation (der Trigger)

**Automation:** „When record matches conditions" → Zielfeld = Zielwert (z. B. `Versicherung Status` ist `Ausstehend`) → Action **„Run a script"**.

### Funktionierendes Script (genau so)

```js
// Token-Zugriff hängt davon ab, WIE der Token hinterlegt ist:
//   Input-Variable -> input.config().NAME
//   echtes Secret  -> input.secret("NAME")   (im Script-Input maskiert = sicherer)
const token = (input.config().CLAUDE_ROUTINE_TOKEN || "").trim();
const url   = "https://api.anthropic.com/v1/claude_code/routines/<TRIGGER_ID>/fire";

console.log("token-länge:", token.length, "| endet auf:", token.slice(-4));

const res = await fetch(url, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "anthropic-version": "2023-06-01",
    "anthropic-beta": "experimental-cc-routine-2026-04-01",   // ← ZWINGEND, sonst 401
    "Content-Type": "application/json"
  },
  body: JSON.stringify({})   // {} = Routine läuft mit ihrem Prompt; {"text":"…"} hängt einen Extra-Turn an
});

const text = await res.text();
console.log("HTTP", res.status, text);
if (!res.ok) throw new Error(`Fire fehlgeschlagen: ${res.status} ${text}`);   // Fehler laut machen
```

### Pflicht-Header (alle vier)
| Header | Wert |
|---|---|
| `Authorization` | `Bearer <token>` |
| `anthropic-version` | `2023-06-01` |
| `anthropic-beta` | `experimental-cc-routine-2026-04-01` |
| `Content-Type` | `application/json` |

---

## Verifikation
- **Test output** zeigt `HTTP 200`/`202`.
- In der **Routine-Run-Historie** erscheint ein Lauf vom Typ **„API"** (nicht nur Manual/Scheduled).
- Der Ziel-Record wird verarbeitet (Felder ändern sich).

## 401-Troubleshooting (alle hier real aufgetretenen Ursachen)
| Symptom | Ursache | Fix |
|---|---|---|
| `401 authentication_error`, Token sieht korrekt aus | **`anthropic-beta`-Header fehlt** | Header `experimental-cc-routine-2026-04-01` ergänzen |
| `401`, `Bearer ` geht leer raus | `input.secret("NAME")` = undefined (Token war **Input-Variable**) | `input.config().NAME` nutzen (oder echtes Secret anlegen) |
| `TypeError: ... reading 'trim'` | Token-Zugriff liefert undefined | falscher Accessor (config vs secret) |
| `401` trotz korrektem Accessor | Token **regeneriert**, Secret/Variable hält den alten | Regenerate → Wert sofort aktualisieren |
| `401` sporadisch | Whitespace/`\n` im Token | `.trim()` auf den Token |
| `404` | falsche Fire-URL | exakte URL aus „Call via API"-Panel kopieren |
| „Test erfolgreich", aber nichts passiert | `res.ok` nicht geprüft (fetch wirft nicht bei 4xx/5xx) | `if (!res.ok) throw …` + `console.log(res.status)` |
| Routine läuft, setzt aber nur „In Prüfung"; Log zeigt `Host not in allowlist` | Environment „Network access" = **Trusted/None** → Airtable-CDN blockiert | Environment auf **`Full`** oder **`Custom`** (`*.airtableusercontent.com`) stellen — siehe Netzwerk-Sektion |

## Sicherheit
- Token **nie hardcoden**, **nie in Screenshots** zeigen — bei Leak sofort „Regenerate".
- Bevorzugt als **echtes Secret** (`input.secret()`) speichern → im Script-Input maskiert.

## Bekannte Stolpersteine (Stand 2026-06)
1. **Netzwerk-Block = Hauptursache für „nur In Prüfung":** Ohne `Full`/`Custom` Network access kommt der Agent nicht an die Dokumente (Airtable-CDN blockiert). Siehe Netzwerk-Sektion oben. **Dies zuerst lösen** — kein anderer Fix greift sonst.
2. **Eingebettete Logik vs. Repo-Checkout:** Ist die Prüflogik im Prompt eingebettet (kein Repo-Checkout), baut der Remote-Agent die Pipeline neu nach. Sobald das Repo auf GitHub liegt → Routine auf Repo-Checkout umstellen (eine Quelle der Wahrheit). **Achtung:** löst NICHT den Netzwerk-Block — beides ist nötig.

Siehe Memory „versicherung-ausstehend-cron" und [[check-versicherung-skill]].
