Med API'et kan scripts, Home Assistant og andre systemer læse et steds lager og, hvis du tillader det, nedskrive eller rette varer. Alt sker gennem samme versionerede API, som appen selv bruger. Siden her kan læses af alle; selve lageret kræver altid en gyldig nøgle til det konkrete sted.
Base-URL: https://lager.steen.xyz/api/v1
Kontrakten: forraad-api-v1.yaml (OpenAPI 3.1) beskriver hver rute, hvert felt og hver fejlkode. Den er den samme fil, som Forråds egne tests kontrollerer serveren mod, så den følger det, serveren faktisk gør.
fzg_.Gem nøglen i systemets lager til hemmeligheder, fx Home Assistants secrets.yaml eller en miljøvariabel på din server. Læg den aldrig i en app, et browserprogram eller på en hjemmeside, som andre kan hente: alle med nøglen kan bruge den, indtil den udløber eller tilbagekaldes. Mister du den, så tilbagekald den under Forbundne apps og opret en ny; tilbagekaldelsen virker fra næste kald.
Send nøglen i hvert kald som Authorization: Bearer fzg_…. Den må aldrig stå i en adresse.
| Valg i appen | Scopes | Må |
|---|---|---|
| Kun læse | inventory:read | Liste opbevaringer og varer, hente en vare og dens billede. |
| Læse og nedskrive | + inventory:adjust | Også ændre antal (POST …/adjustments) og fortryde en ændring (POST …/undo). |
| Fuld adgang | + inventory:write | Også oprette, rette og slette varer. |
404, som om det ikke fandtes.403 insufficient_scope.Eksemplerne bruger to miljøvariabler. Udskift STED-ID, VARE-ID osv. med rigtige id'er fra svarene.
export FORRAAD="https://lager.steen.xyz/api/v1"
export FORRAAD_KEY="fzg_…" # fra dit lager til hemmeligheder
# Stedet, nøglen hører til: id, navn og de aktive typer (enabledKinds)
curl -s -H "Authorization: Bearer $FORRAAD_KEY" "$FORRAAD/workspaces"
Svaret har ét sted med fx "enabledKinds": ["freezer", "fridge"]. Typerne er freezer (fryser), fridge (køleskab) og pantry (kolonial); et sted kan godt være uden fryser.
# Opbevaringerne (fx "Kummefryser", "Køleskab") med deres placeringer
curl -s -H "Authorization: Bearer $FORRAAD_KEY" "$FORRAAD/workspaces/STED-ID/storage-places"
# Det aktive lager (højst 100 pr. side); kun køleskabet med kind=fridge
curl -s -H "Authorization: Bearer $FORRAAD_KEY" "$FORRAAD/workspaces/STED-ID/items?limit=100"
curl -s -H "Authorization: Bearer $FORRAAD_KEY" "$FORRAAD/workspaces/STED-ID/items?kind=fridge&limit=100"
quantity er antal pakker eller stk., 0–100000 med højst 3 decimaler (0.5 er en halv). Det gemmes præcist; et tal som 0.30000000000000004 afvises med 422 i stedet for at blive afrundet.amount og unit er indholdet af én enhed, fx 500 og "g". amount er null, når det er ukendt.storageKind er varens type, storagePlaceName opbevaringens navn og location placeringen (fx "Øverste skuffe").addedDate, frozenDate, expiryDate) er ÅÅÅÅ-MM-DD uden tid. expiryKind er best_before (bedst før), use_by (sidste anvendelsesdato) eller null. Tidsstempler er UTC, fx 2026-10-16T08:30:00.123Z.imageUrl er null uden billede. Begynder den med /api/v1/, så hent den med https://lager.steen.xyz foran og samme Authorization-header. En ældre vare kan have en gammel, fuld adresse, som API'et ikke leverer.version, som stiger ved hver ændring (bruges ved rettelser, se afsnit 6).Send ikke helbredsoplysninger (diagnoser, medicin, allergier) gennem API'et; varenavne og noter gemmes som almindelig tekst.
En nedskrivning er en relativ ændring af antallet med en årsag: used (brugt), discarded (smidt ud), correction (rettelse) eller restocked (fyldt op). Den kræver rettigheden Læse og nedskrive.
# "Brugt én"
curl -s -X POST "$FORRAAD/workspaces/STED-ID/items/VARE-ID/adjustments" \
-H "Authorization: Bearer $FORRAAD_KEY" -H "Content-Type: application/json" \
-d '{"delta": -1, "reason": "used", "operationId": "brugt-2026-10-16-0001"}'
Svaret har den opdaterede vare og bevægelsen (movement.id).
operationId er påkrævet: 8–128 tegn af A–Z a–z 0–9 . _ : -. Brug et nyt id til hver ny handling.operationId. Er det første kald nået frem, får du samme svar med "replayed": true, og varen nedskrives kun én gang.operationId med et andet indhold giver 409 idempotency_conflict, og intet ændres.409 insufficient_stock.# Fortryd en bevægelse (fx en fejlagtig nedskrivning)
curl -s -X POST "$FORRAAD/workspaces/STED-ID/movements/BEVAEGELSE-ID/undo" \
-H "Authorization: Bearer $FORRAAD_KEY" -H "Content-Type: application/json" \
-d '{"operationId": "fortryd-2026-10-16-0001"}'
Kræver rettigheden Fuld adgang.
# Opret: Idempotency-Key gør det sikkert at prøve igen efter et tabt svar
curl -s -X POST "$FORRAAD/workspaces/STED-ID/items" \
-H "Authorization: Bearer $FORRAAD_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: opret-2026-10-16-0001" \
-d '{"name": "Hakket oksekød", "quantity": 2, "amount": 500, "unit": "g", "storagePlaceId": "OPBEVARING-ID", "frozenDate": "2026-10-16"}'
# Ret: send varens nuværende version; er den ændret imens, svarer API'et 409 version_conflict
curl -s -X PATCH "$FORRAAD/workspaces/STED-ID/items/VARE-ID" \
-H "Authorization: Bearer $FORRAAD_KEY" -H "Content-Type: application/json" \
-d '{"version": 3, "quantity": 1.5, "location": "Øverste skuffe"}'
409 version_conflict, har en anden ændret varen. Hent den igen, og beslut, om ændringen stadig skal laves.PATCH til at sætte det.DELETE …/items/VARE-ID?version=N. Et nyt forsøg efter et tabt svar giver 404, fordi varen allerede er slettet.Idempotency-Key kan et genforsøg af "opret" give en dublet.GET …/items giver højst limit varer (standard 50, højst 100). Er nextCursor ikke null, så hent næste side med ?cursor=…. Byg eller fortolk aldrig en cursor selv.kind eller storagePlaceId.includeInactive=true uden filtre. Så kommer også slettede varer (deletedAt sat), varer med antal 0 og varer i slåede-fra typer. Gem syncCursor fra sidste side, og send den som cursor næste gang for kun at få ændringer siden. Filtrér selv på stedets enabledKinds.version er uændret (fx når en opbevaring er omdøbt).Med Home Assistants indbyggede rest- og rest_command-integrationer kan du vise lageret og nedskrive fra et dashboard eller en automatisering. Læg nøglen i secrets.yaml:
forraad_auth: "Bearer fzg_…"
Og i configuration.yaml (udskift STED-ID):
rest:
- resource: "https://lager.steen.xyz/api/v1/workspaces"
headers:
Authorization: !secret forraad_auth
scan_interval: 900
sensor:
- name: "Varer i Forråd"
unique_id: forraad_varer
value_template: "{{ value_json.workspaces[0].itemCount }}"
rest_command:
forraad_brugt_en:
url: "https://lager.steen.xyz/api/v1/workspaces/STED-ID/items/{{ item_id }}/adjustments"
method: POST
headers:
Authorization: !secret forraad_auth
content_type: "application/json"
payload: '{"delta": -1, "reason": "used", "operationId": "ha-{{ item_id }}-{{ now().timestamp() | int }}"}'
itemCount, alle aktive typer). Brug scan_interval på mindst nogle minutter; se grænserne i afsnit 9.…/items?kind=fridge&limit=20, og vis kun de felter, du har brug for (fx name og quantity). En side har højst 100 varer, og Home Assistant gemmer ikke attributter over 16 KB i sin historik, så læg ikke hele listen i en attribut.rest_command.forraad_brugt_en kaldes med item_id, fx fra en knap: action: rest_command.forraad_brugt_en med data: { item_id: "VARE-ID" }. Nøglen skal have rettigheden Læse og nedskrive.operationId bygges af varen og sekundet, så to tryk i samme sekund kun tæller én gang. Vil du kunne prøve igen efter en fejl, så genbrug samme id.Alle fejl har formen {"error": {"code": "…", "message": "…", "details": {…}}}. message er dansk tekst til mennesker og kan ændre sig; dit program skal reagere på code.
| Status | Typiske koder | Betyder |
|---|---|---|
| 401 | unauthenticated | Nøglen mangler, er forkert, udløbet eller tilbagekaldt. |
| 403 | insufficient_scope | Nøglen har ikke rettigheden til kaldet. |
| 404 | not_found | Findes ikke, eller hører ikke til nøglens sted. |
| 409 | version_conflict, idempotency_conflict, insufficient_stock, not_undoable | Varen er ændret imens, et id er brugt til noget andet, antallet ville blive negativt, eller bevægelsen kan ikke fortrydes (igen). |
| 422 | invalid_request | Ugyldigt indhold; details.field peger på feltet. |
| 429 | rate_limited | For mange kald; vent det antal sekunder, Retry-After siger. |
| 503 | maintenance | Kortvarig vedligeholdelse: skrivning afvises, i sjældne tilfælde også læsning. Prøv igen efter Retry-After sekunder. |
Grænser: 120 læsninger og 60 skrivninger pr. minut pr. nøgle og sted, og 600 billedhentninger pr. minut. Den fulde liste over koder står i kontrakten.
API'et sender kun CORS-headere til få, godkendte adresser (fx Overskud). CORS er en regel i browseren og giver ingen adgang: et kald kræver stadig en gyldig nøgle. Byg derfor ikke en hjemmeside, der kalder API'et direkte med en nøgle; lad din egen server kalde det.
Alt under /api/v1 er stabilt for integrationer:
X-Client-Level) gælder kun Forråds egen app. Nøgler sender den ikke og påvirkes aldrig af den.De vigtigste ændringer for integrationer. Hele loggen står i kontrakten.
enabledKinds kan mangle freezer. Ingen rute, intet felt og ingen fejlkode er fjernet.locations, locationsRevision). Kun tilføjelser for integrationer.