POST /v1/keys/trial

Self-Service-Trial-Key minten (7 Tage, read-only)

beta seit v0.1.0 keys auth: none

POST /v1/keys/trial — Self-Service-Trial-Key

Mintet ohne Registrierung einen befristeten Trial-API-Key (tier trial). Der Request-Body ist optional und kennt zwei freiwillige Felder:

FeldTypBedeutung
sourceSlug [a-z0-9_-], max. 64Kampagnen-/Kanal-Zuordnung (z.B. "dachgold", "landing"). Keine PII.
emailE-Mail-AdresseKontaktadresse zur Key-Zuordnung. Optional, ungeprüft, kein Mailversand.

Der Key wird genau einmal im Klartext zurückgegeben (Cache-Control: no-store) und ist danach nicht wiederherstellbar — gespeichert wird nur SHA-256-Hash + Prefix.

# PII-frei (DACHGOLD-Muster)
curl -X POST https://api.lodapi.de/v1/keys/trial \
  -H "Content-Type: application/json" \
  -d '{"source": "dachgold"}'

# mit Lead-Zuordnung (lodapi.de/key-Muster)
curl -X POST https://api.lodapi.de/v1/keys/trial \
  -H "Content-Type: application/json" \
  -d '{"source": "landing", "email": "konsti@scenerii.com"}'

Response (200):

{
  "key": "lod_td8j5b86m6s32ujqbdmf8wwah5sv99xd",
  "tier": "trial",
  "expires_at": "2026-07-14T10:12:01Z",
  "limits": { "requests_per_minute": 60, "requests_per_day": 5000 },
  "scopes": ["read"],
  "docs_url": "https://lodapi.de/docs"
}
  • TTL: 7 Tage ab Mint. Abgelaufene Keys werden beim Lookup wie revoked behandelt (401 auf der Daten-API); die Row bleibt für Conversion-Analytics.
  • Limits: 60 req/min, 5000 req/Tag — deklariert, Enforcement soft (ADR-0014 „Quota = soft”): sichtbar über X-Lodapi-Quota-Used / X-Lodapi-Quota-Limit (Budget = 7 × 5000 = 35 000).
  • Scope: read-only — die gesamte /v1-Daten-API ist read-only, das scopes-Feld dokumentiert den Contract.
  • email (seit 2026-08-17, Konsti-Entscheidung „Mail gegen Key”): landet in lodapi_meta.api_key.contact_email — dieselbe Spalte wie beim Admin-Mint, dadurch ohne Extra-Code in GET /v1/admin/api-keys sichtbar. Keine Verifikation, kein Double-Opt-in, kein Versand aus diesem Endpoint. Ein Mint ohne Adresse liefert exakt denselben Key — die Adresse ist nie Voraussetzung. Die Antwort spiegelt sie nicht zurück (Response-Contract unverändert). Rechtsgrundlage/Speicherdauer/Löschweg: 05_operations/trial-key-email-capture.md (Prüfauftrag Themis).
  • Abuse-Schutz auf dem Mint: max. N Keys pro IP pro 24 h (LODAPI_TRIAL_MINTS_PER_IP_PER_DAY) → 429 mit Retry-After-Header und retry_after-Member (Sekunden) im problem+json-Body.
  • Vom Keyless-Tageslimit ausgenommen (seit T-0079): /v1/keys/** zählt nicht gegen die 1.000 keyless Requests pro IP und Tag und wird auch nie mit der Keyless-429 beantwortet. Der Weg zum Key darf nicht hinter dem Limit liegen, aus dem der Key herausführt — siehe Limits.
  • Kill-Switch: LODAPI_TRIAL_KEYS_ENABLED=0 → 503. Clients sollen still degradieren (Angebot nicht zeigen, später erneut versuchen) — der Endpoint darf jederzeit ausfallen, ohne Consumer zu brechen.
  • Kontext: eingeführt für die DACHGOLD-Promo-Integration (Key als „Take-away” nach dem ersten Spiel-Run, Konsti-Entscheidung „Key nach erstem Wow, kein Gatekeeper”); generisch für jede Kampagne nutzbar. Zweiter Consumer: die Self-Serve-Seite lodapi.de/key (source: "landing").

Stolperdrähte

  • Das Per-IP-Limit greift auf die echte Peer-Adresse, nicht auf request.client.host: uvicorn läuft mit --forwarded-allow-ips "*" und liefert dort den ersten X-Forwarded-For-Eintrag — den kann der Client selbst schreiben, die Mint-Quote wäre per Header beliebig oft zurücksetzbar (T-0080, gefixt 2026-08-19). Gezählt wird der rechteste Eintrag, den Caddy anhängt; gemeinsamer Helper keyless_limit.client_ip(), denselben nutzt das Keyless-Tageslimit. Regressionstest tests/test_trial_keys.py::TestMintIpIsSpoofSafe fährt dafür den echten Prod-ASGI-Stack inklusive ProxyHeadersMiddleware — ohne die Schicht ist der Spoof im Test gar nicht nachstellbar und ein Test dagegen wertlos.
  • Hängt ein eigener Reverse-Proxy des Nutzers davor (statt Direkt-Call vom Browser), zählt dessen Ausgangs-IP für alle seine Nutzer zusammen. CORS-Origins sind offen (*): Browser können direkt gegen api.lodapi.de minten, ein Proxy ist nicht nötig.
  • CORS-Preflight: ein JSON-POST (Content-Type: application/json) ist kein „simple request” — der Browser schickt vorher OPTIONS. POST muss dafür in allow_methods der CORSMiddleware stehen (seit 2026-08-17, davor ["GET", "OPTIONS"] → jeder browser-seitige Mint scheiterte am Preflight mit 400 Disallowed CORS method). curl merkt davon nichts — der Fehler ist nur im Browser sichtbar. Festgenagelt in tests/test_trial_keys.py::test_cors_preflight_allows_browser_mint. Ein bodyloser POST ohne Header bleibt preflight-frei und funktioniert auch ohne diese Einstellung — dann aber ohne source und ohne email: ein Body braucht Content-Type: application/json, und der macht den Request preflight-pflichtig. Lead-Capture aus dem Browser hängt damit am deployten CORS-Fix — es gibt keinen preflight-freien Weg, der die Adresse mitnimmt.
  • Kein email in der URL. Ein ?email=-Query-Param wäre der naheliegende Trick, um den Preflight zu umgehen — er schreibt die Adresse aber in Caddy-Access-Logs und api_key_usage.path, also an zwei Stellen ohne Löschkonzept. Bewusst nicht gebaut.
  • Zusätzliche, unbekannte Body-Felder werden von Pydantic still verworfen (kein extra="forbid") — ein Tippfehler wie mail statt email fällt also nicht als 422 auf, die Adresse ist dann einfach weg.
  • source ist ein Slug ([a-z0-9_-], max. 64) — alles andere gibt 422. Eine syntaktisch kaputte email gibt ebenfalls 422 (kein Key wird geminted).

Limit-Stand (2026-08-19): Code-Default 3 Mints/IP/24 h; prod läuft auf 10 (LODAPI_TRIAL_MINTS_PER_IP_PER_DAY=10 in code/prod/.env, Decision 2026-08-17-06). Frontend nennt bewusst keine Zahl.

Verwandt

  • /v1/keys/trial/{key}
  • /v1/admin/api-keys

Bereit?

Der erste Call geht sofort.

LoD2-Gebäude für alle 16 Bundesländer, kostenlos und ohne Konto. Pro auf Anfrage 30 Tage testen.