agent_api: de API voor de Freeze AI-agent¶
De AI-agent op de Freeze AI-VM (offerte-assistent, later de chat) leest via een eigen, alleen-lezen API producten, pagina's, prijzen en offerteaanvragen. Hij komt nooit via de gewone shop-API en kan niets wijzigen.
Ontwerp: repo Voorman/watchtower, docs/plans/2026-11-02-freeze-ai-ontwerp.md §4.1.
Todo: .planning/todos/pending/agent-api.md. Code: backend/apps/agent_api/.
Endpoints (/api/agent/v1/)¶
| Scope | Endpoint | Inhoud |
|---|---|---|
catalog:read |
GET products/, GET products/{id}/ |
actieve, geliste producten (zoals de shop, zonder inkoopprijs) |
catalog:read |
GET categories/ |
de categorieboom met intro en FAQ |
catalog:read |
GET decoration-methods/ |
actieve bedrukkingsmethoden, zonder tarieven |
content:read |
GET pages/, GET pages/{slug}/ |
actieve CMS-pagina's |
pricing:read |
POST pricing/quote-estimate/ |
prijsindicatie via de prijsmotor (simulate_price); met product_id telt de basisprijs van dat product |
quotes:read |
GET quotes/{id}/ |
één open offerteaanvraag (status requested) met de vraag van de klant, zonder e-mail, telefoon, adres, bedrijfsnaam, interne notities of de namen en nummers voor op de kleding (alleen has_personalization) |
Beveiliging (drie lagen)¶
- nginx:
location /api/agent/laat alleen100.110.246.113toe (de VM in het tailnet); de rest krijgt 403. Op staging zonder basic auth, want de agent stuurt die niet. - Django:
AGENT_API_ALLOWED_IPS(komma-gescheiden IP's of netwerken; leeg = niemand), viaget_client_ip. - Sleutel: header
Authorization: Api-Key fai_<prefix>.<geheim>, met scopes. Alleen de SHA-256 staat in de database. Een sessie-login telt niet.
Daarnaast een throttle per sleutel (AGENT_API_THROTTLE_RATE, standaard 600/uur) en een
auditlog per call (Django-admin → Agent-API-calls).
Een sleutel maken¶
Per omgeving en per toepassing een eigen sleutel. De sleutel verschijnt één keer, zonder andere tekst, dus stuur hem direct door naar de VM of 1Password:
python manage.py create_agent_key staging-offertes --scope catalog:read --scope pricing:read --scope quotes:read
Intrekken: Django-admin → Agent-API-sleutels → "Is active" uit. Toevoegen via de admin kan niet, zodat een sleutel nooit in de admin of in een log staat.
Een nieuw VM-adres¶
Verandert het tailnet-IP van de VM (tailscale ip -4 op de VM), pas dan zowel de allow
in nginx/nginx.conf en nginx/nginx.staging.conf aan als AGENT_API_ALLOWED_IPS in de
env-bestanden van productie en staging.
Offerte-assistent (concepten)¶
Bij elke nieuwe offerteaanvraag vraagt de shop de agent om een beoordeling
(apps/agent_api/review.py: na de commit een Celery-taak agent.request_quote_review, tot
3 keer opnieuw). De agent leest de aanvraag via deze API en slaat één concept op via
POST suggestions/ (scope suggestions:write; payload strikt gecontroleerd, alleen voor
open aanvragen). Het eerste concept wint: elke volgende post voor dezelfde offerte krijgt
409, zodat een retry of een run met een prompt-injectie een concept nooit vervangt.
- Aan de kant van de agent: het offertenummer staat vast in de deps van de run (geen
vrije ID's); de agent zet
target_iddaaruit, nooit uit de uitvoer van het model. Het endpoint/v1/quotes/{id}/reviewantwoordt direct 202 en werkt op de achtergrond. -
Retries: bij een 5xx of verbindingsfout probeert Celery het tot 3 keer opnieuw; een 4xx (bv. een verkeerde token) wordt gelogd en niet herhaald. Een storing bij het inplannen (broker weg) wordt gelogd en raakt de klant nooit.
-
Admin: op de offertepagina staat rechts "Concept van de assistent", als platte tekst, met de knoppen Bruikbaar / Niet bruikbaar (voor de evaluatie; er gebeurt verder niets). Ook in de Django-admin onder Agent-concepten.
- Melding: ntfy-topic
freeze-approve, met alleen het offertenummer en een klik naar de admin. Geen naam, e-mail of bedrag. De ntfy-gebruikerwebshopmag al schrijven opfreeze-*; abonneer je in de ntfy-app opfreeze-approve. - Instellingen:
FREEZE_AI_AGENT_URL(http://100.110.246.113:8000),FREEZE_AI_AGENT_TOKEN(1Password "ntfy Freeze AI — tokens", veldagent-bearer) enFREEZE_AI_ENV(stagingofprod). Zonder URL of token gebeurt er niets. - De sleutel van de agent heeft voor deze flow
quotes:read,catalog:read,pricing:readensuggestions:writenodig.