Innehåll

Webbutiks-API – utvecklarguide

BrewOS webbutiks-API kopplar BrewOS-kontoinnehavarens egen webbutik till BrewOS. Kontoinnehavaren kan till exempel vara ett bryggeri eller en importör. Via API:et kan webbutiken:

  • hämta kontots produkter, priser och tillgängliga lagersaldon
  • logga in en innehavare av serverings-, detaljhandels- eller partihandelstillstånd
  • förmedla köparens beställning till BrewOS
  • förmedla en konsuments beställning, som rapporteras på kontoinnehavarens eget detaljhandelstillstånd

Betalningen för en tillståndshavares beställning går inte via webbutiken. Beställningen skapas i BrewOS som en leverans, kontoinnehavaren plockar och levererar den, och köparen får en faktura från BrewOS som tidigare. Betalningen för en konsumentbeställning sköts i webbutiken, och BrewOS fakturerar inte konsumenten.

API:et kan användas på tre sätt:

  • En webbutik för innehavare av alkoholtillstånd (servering, detaljhandel, partihandel): produkter, priser, saldon och beställningar.
  • En webbplats för konsumenter, till exempel en produktpresentation eller tillgänglighetsinformation på kontoinnehavarens webbplats. Att hämta produkter och saldon (GET /products, GET /stock) kräver inte att köparen loggar in, se Webbplats för konsumenter.
  • En webbutik för konsumenter: en konsuments beställning för avhämtning eller hemkörning (POST /consumer-orders), om kontoinnehavaren har tagit konsumentförsäljning i bruk. Se Konsumentbeställningar.

POST /orders kräver en köpare som har loggat in med sitt verksamhetsställes alkoholtillståndsnummer, och BrewOS kontrollerar tillståndet vid varje beställning. En konsumentbeställning görs på en egen sökväg, och den rapporteras på kontoinnehavarens detaljhandelstillstånd.

Den här sidan är avsedd för den som bygger webbutiken. API-nyckeln och köparnas webbutikslösenord skapas av kontoinnehavaren på sitt eget BrewOS-konto.

Snabbguide

  1. Be kontoinnehavaren om en API-nyckel. Kontots ägare skapar den i BrewOS under Inställningar → Webbutiker. Nyckeln har formen bws_… och visas bara en gång.
  2. Testa förbindelsen:

    curl -H "Authorization: Bearer bws_1_…" https://brewos.fi/rest/webshop/v1/webshop
    
  3. Hämta produkterna en gång per dygn (GET /products) och vid behov saldona oftare (GET /stock).

  4. Logga in köparen med tillståndsnummer och lösenord (POST /buyers/authenticate) och spara den returnerade buyer_token i köparens session.
  5. Skicka beställningen (POST /orders) med köpartoken och en unik Idempotency-Key-rubrik.
  6. Följ beställningarna (GET /orders?updated_since=…) om du vill visa köparen beställningens status.
  7. Visa köparen tidigare beställningar (GET /buyer/orders) om webbutiken har en beställningshistorik.

Ändpunkter

Alla sökvägar finns under https://brewos.fi/rest/webshop/v1. Varje anrop kräver API-nyckeln (Authorization: Bearer …). Köparens anrop kräver dessutom rubriken X-Buyer-Token, och att skicka en beställning rubriken Idempotency-Key.

Metod Sökväg Rubriker Syfte
GET /webshop nyckel Anslutningstest: webbutik, konto, inställningar för konsumentförsäljning och serverns tid
GET /products nyckel Publicerade produkter med priser och fria saldon
GET /stock nyckel Produkternas identifierare och fritt saldo
POST /buyers/authenticate nyckel Köparens inloggning med tillståndsnummer och lösenord
GET /buyer nyckel + X-Buyer-Token Den inloggade köparens uppgifter, tillstånd och avtalspriser
GET /buyer/orders nyckel + X-Buyer-Token Köparens tidigare beställningar: värde samt leverans- och faktureringsstatus
POST /orders nyckel + X-Buyer-Token + Idempotency-Key Skicka en tillståndshavares beställning
GET /orders/{order_id} nyckel En av den här webbutikens beställningar med rader
GET /orders?updated_since=… nyckel Webbutikens ändrade beställningar, äldsta först
POST /consumer-orders nyckel + Idempotency-Key Skicka en konsumentbeställning
GET /consumer-orders/{order_id} nyckel En konsumentbeställning med kontaktuppgifter
POST /consumer-orders/{order_id}/payment nyckel Markera en konsumentbeställning som betald
POST /consumer-orders/{order_id}/cancel nyckel Avbryt en konsumentbeställning

Grunder

Adress och format

Alla sökvägar finns under adressen https://brewos.fi/rest/webshop/v1. Förfrågningar och svar är JSON (UTF-8). Skicka rubriken Content-Type: application/json i POST-förfrågningar.

Sak Format
Belopp som heltal i cent, t.ex. 239 = 2,39 €
Moms i procent, t.ex. 25.5
Volym i milliliter (container_size_ml)
Mängder i hela försäljningsenheter (st). En multipack, t.ex. 6 × 330 ml, är en enhet.
Alkoholhalt i procent, t.ex. 5.5
Tidsstämplar ISO 8601 med tidszon, t.ex. 2026-10-12T14:05:12+03:00 (finsk tid)
Datum ÅÅÅÅ-MM-DD

Identifiera produkter

Produkterna identifieras med samma koder som kontoinnehavaren rapporterar till Valvira med: EAN-kod (med kontrollsiffra, 13 tecken) eller Valvira-rapporteringskod.

  • I produktlistan har varje produkt ean (den första EAN-koden), eans (alla EAN-koder) och valvira_code. Vilken som helst av dem kan vara null, men en publicerad produkt har alltid minst en kod som identifierar den.
  • På en beställningsrad anges antingen ean eller valvira_code. Båda söks i samma kodregister, så valet av fält spelar ingen roll så länge koden är rätt.
  • Varje produktuppgift har dessutom BrewOS interna produktnummer product_id. Det är permanent och unikt inom kontot, så det kan användas till exempel för att koppla produkterna till kontots övriga BrewOS-rapporter. På beställningsraden anges produkten ändå alltid med en kod.
  • Spara produktens kod i webbutiken och använd den i beställningen. Produktnamnet är inte unikt, eftersom det finns produkter med samma namn i olika förpackningar.

Fel

Fel returneras i formatet RFC 7807 (Content-Type: application/problem+json). Ditt program bör förgrena sig utifrån fältet code. title är en förklaring på finska som kan visas för användaren. Radspecifika fel finns i listan errors:

{
  "type": "about:blank",
  "status": 409,
  "code": "insufficient_stock",
  "title": "Saldo ei riitä kaikille riveille",
  "errors": [
    { "line": 1, "code": "insufficient_stock", "title": "Saldo ei riitä", "product_id": 1,
      "ean": "6430055482348", "valvira_code": null, "requested": 19, "available": 18 }
  ]
}

(Här betyder titlarna "Saldot räcker inte för alla rader" och "Saldot räcker inte".)

Alla felkoder räknas upp under Felkoder.

Autentisering

API-nyckel

Varje förfrågan identifieras med webbutikens API-nyckel:

Authorization: Bearer bws_1_aB3dE…
  • Nyckeln är en hemlighet. Använd den bara på webbutikens server, aldrig i kod som körs i webbläsaren eller i en mobilapp. API:et skickar inga CORS-rubriker.
  • Kontoinnehavaren kan när som helst återkalla nyckeln eller stänga av webbutiken. Då svarar API:et 401 invalid_api_key eller 403 webshop_disabled.
  • Byte av nyckel utan avbrott: en webbutik kan ha två giltiga nycklar samtidigt. Ta den nya nyckeln i bruk och be sedan kontoinnehavaren återkalla den gamla.

Köparens inloggning

Köparen är alltid ett verksamhetsställe som innehar ett alkoholtillstånd. Köparen loggar in i webbutiken med sitt verksamhetsställes alkoholtillståndsnummer och det lösenord som kontoinnehavaren har skapat i BrewOS och gett köparen. Webbutiken förmedlar inloggningsuppgifterna till BrewOS:

curl -X POST https://brewos.fi/rest/webshop/v1/buyers/authenticate \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"license_number": "01063493", "password": "k7mq-x3fa-9pzr-c2hd", "client_ip": "203.0.113.7"}'

Svaret på en lyckad inloggning innehåller buyer_token, som gäller i 12 timmar (expires_at).

  • Skicka token i rubriken X-Buyer-Token i de anrop som gäller köparen (GET /buyer, GET /buyer/orders, POST /orders).
  • Spara inte köparens lösenord. Spara bara token i köparens session. När token går ut eller API:et svarar 401 invalid_buyer_token, be köparen logga in på nytt.
  • Lösenordet har formen xxxx-xxxx-xxxx-xxxx. Versaler och gemener, bindestreck och mellanslag spelar ingen roll.
  • Webbutikslösenordet fungerar i alla webbutiker på samma konto, men token bara i den webbutik med vars nyckel den skapades.
  • client_ip (valfri) är köparens IP-adress. Den registreras vid misslyckade försök.

Felsituationer:

  • Fel tillståndsnummer eller lösenord ger alltid samma svar 401 invalid_credentials. API:et berättar inte vilket som var fel.
  • Efter fem misslyckade försök låses tillståndsnumret i 15 minuter: 429 too_many_attempts.
  • När inloggningsuppgifterna är rätt kontrolleras tillståndet och kundrelationen genast: 403 license_not_in_force, 403 license_type_not_accepted eller 403 buyer_blocked. Visa title för köparen och hänvisa vid behov köparen att kontakta säljaren (kontoinnehavaren).

Produkter och saldon

GET /products

Returnerar de publicerade produkterna med priser och tillgängliga saldon. Hämta listan till exempel en gång per dygn.

Parameter Standard Högst
page 1
per_page 200 500
curl -H "Authorization: Bearer $API_KEY" "https://brewos.fi/rest/webshop/v1/products?page=1&per_page=200"
{
  "items": [
    {
      "product_id": 26,
      "ean": "6430055480344",
      "eans": ["6430055480344"],
      "valvira_code": null,
      "name": "Avenger",
      "description": "Täyteläinen IPA …",
      "product_group": "Oluet",
      "product_groups": [
        { "id": 12, "name": "Oluet", "dimension": "Tuoteryhmä", "level": "product" }
      ],
      "container_type": "pullo",
      "container_size_ml": 330,
      "pack_units": 1,
      "alcohol_percent": 7.2,
      "list_price": { "excl_vat_cents": 250, "incl_vat_cents": 314, "vat_percent": 25.5 },
      "group_prices": [
        { "customer_group_id": 3, "customer_group_name": "Anniskelu",
          "excl_vat_cents": 230, "incl_vat_cents": 289, "vat_percent": 25.5 }
      ],
      "retail_license_allowed": true,
      "deposit_incl_vat_cents": null,
      "best_before_months": 12,
      "image_url": "https://brewos.fi/tiedosto/…/avenger.png",
      "available_quantity": 1969,
      "updated_at": "2025-10-20T09:48:49+03:00"
    }
  ],
  "page": 1,
  "per_page": 200,
  "total": 94,
  "stock_calculated_at": "2026-10-12T06:00:01+03:00"
}
Fält Förklaring
product_id BrewOS interna produktnummer (permanent).
product_groups Produktens produktgrupper: id, name, dimension (dimension, t.ex. "Typ" eller "Smak") och nivå (level): product_base = dryckens grupp, som gäller alla förpackningar av drycken, product = förpackningens grupp. En produkt kan ha flera grupper.
product_group Namnet på produktens första grupp i dimensionernas ordning, eller null. Behålls för kompatibilitet; använd product_groups.
pack_units Antal flaskor eller burkar i en multipack. För en vanlig produkt 1.
list_price Allmänt pris, eller null om produkten inte har något allmänt pris.
group_prices Priser per kundgrupp. Köparens grupp framgår av inloggningen.
retail_license_allowed Om produkten får säljas med detaljhandelstillstånd, se Tillstånd.
deposit_incl_vat_cents Pant per försäljningsenhet (inkl. moms), eller null. Kontoinnehavaren bestämmer om panten läggs till beställningen.
available_quantity Tillgängligt saldo: i lager minus redan mottagna beställningar som väntar på plockning.
image_url Produktbild (offentlig adress) eller null.

Kontoinnehavaren bestämmer vilka produktgrupper som publiceras i webbutiken. En produkt publiceras om den eller dess dryck har någon av de valda grupperna. Produkter som inte har någon identifierande kod lämnas bort från listan.

GET /stock

En lätt saldolista: endast koder och tillgängligt saldo. Passar för tätare uppdatering. Som standard 500 rader per sida, högst 1000.

{
  "items": [
    { "product_id": 26, "ean": "6430055480344", "eans": ["6430055480344"], "valvira_code": null, "available_quantity": 1969 }
  ],
  "page": 1, "per_page": 500, "total": 94,
  "stock_calculated_at": "2026-10-12T12:00:00+03:00"
}

Saldot kan ändras efter att listan hämtats. Den avgörande kontrollen görs när beställningen skickas, och svaret 409 insufficient_stock anger den aktuella tillgängliga mängden för varje rad.

Webbplats för konsumenter

Produktlistan och saldona kan också användas på en webbplats som är öppen för konsumenter, till exempel för en produktpresentation eller för att visa tillgänglighet. En konsument kan bara beställa om kontoinnehavaren har tagit konsumentförsäljning i bruk, se Konsumentbeställningar.

  • Hämta uppgifterna på servern. API-nyckeln får inte hamna i webbläsaren. Hämta produkterna och saldona till webbplatsens server, spara dem i en cache och visa dem därifrån. API:et tillåter 120 anrop per minut, så anropa det inte vid varje sidladdning.
  • Priserna är priser för tillståndshavare. list_price, group_prices och avtalspriserna är kontots försäljningspriser för innehavare av serverings-, detaljhandels- och partihandelstillstånd, inte konsumentpriser. Webbutiken bestämmer konsumentpriserna.
  • Tillgängligheten gäller kontots eget lager. available_quantity anger kontoinnehavarens tillgängliga saldo, inte tillgängligheten i butiker, restauranger eller hos Alko.
  • Produktbilderna (image_url) är offentliga adresser och kan användas på webbplatsen som sådana.
  • Alkoholmarknadsföring. Innehåll som visas för konsumenter ska följa alkohollagens bestämmelser om marknadsföring. Ansvaret ligger hos den som upprätthåller webbplatsen.

Köpare och priser

Inloggningens svar och GET /buyer

POST /buyers/authenticate returnerar köparens uppgifter. GET /buyer returnerar samma uppgifter på nytt kontrollerade (rubriken X-Buyer-Token). Anropa det till exempel i kassan innan beställningen skickas.

{
  "buyer_token": "3647baa5…",
  "expires_at": "2026-10-13T04:33:02+03:00",
  "buyer": {
    "customer_name": "Walhalli Oy",
    "business_id": "2954128-1",
    "outlet_name": "Valkeakosken Keilahalli",
    "license_number": "01063493",
    "license_type": "anniskelu",
    "license_status": "active",
    "delivery_address": { "street": "Keilahallintie 1", "street2": null, "postal_code": "37600", "town": "Valkeakoski" },
    "customer_group_id": 3,
    "discount_percent": 0
  },
  "contract_prices": [
    { "product_id": 26, "ean": "6430055480344", "valvira_code": null,
      "excl_vat_cents": 230, "incl_vat_cents": 289, "vat_percent": 25.5, "valid_until": null }
  ],
  "warnings": []
}

Svaret på GET /buyer innehåller inte fälten buyer_token och expires_at.

Hur priset bestäms

BrewOS prissätter beställningen. Webbutiken kan visa köparen samma pris med den här regeln:

pris = contract_prices[kod]                          om köparen har ett avtalspris
     | group_prices[customer_group_id]               annars, om köparens grupp har ett pris
     | list_price                                    annars
radsumma = pris.excl_vat_cents × mängd
om discount_percent > 0:  radsumma = round( radsumma × (1 − discount_percent / 100) )
moms = round( radsumma × vat_percent / 100 )

Svaret på beställningen innehåller alltid de slutliga priser som BrewOS har räknat ut. Om de avviker från dem webbutiken visade (t.ex. kontoinnehavaren ändrade priset under tiden), gäller BrewOS pris, och fakturan görs enligt det.

Beställningar

POST /orders

Skickar köparens beställning. Rubriker:

Rubrik
Authorization: Bearer … API-nyckel
X-Buyer-Token: … den inloggade köparens token
Idempotency-Key: … obligatorisk, en unik identifierare för den här sändningen (1–100 tecken A–Z a–z 0–9 _ - : .), t.ex. webbutikens ordernummer eller ett UUID
curl -X POST https://brewos.fi/rest/webshop/v1/orders \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Buyer-Token: $BUYER_TOKEN" \
  -H "Idempotency-Key: WS-100234" \
  -H "Content-Type: application/json" \
  -d '{
    "external_order_id": "WS-100234",
    "customer_reference": "Inköpsorder 55",
    "requested_delivery_date": "2026-10-16",
    "message": "Leverans till bakdörren",
    "lines": [
      { "ean": "6430055480344", "quantity": 24 },
      { "valvira_code": "000157181607", "quantity": 6 }
    ]
  }'
Fält Obligatoriskt Förklaring
external_order_id ja Webbutikens ordernummer, högst 64 tecken. Unikt inom webbutiken.
customer_reference Köparens egen referens (t.ex. inköpsordernummer), högst 250 tecken. Syns på fakturan.
requested_delivery_date Önskad leveransdag ÅÅÅÅ-MM-DD, inte i det förflutna. Kontoinnehavaren ser önskemålet, men det är inte bindande.
message Köparens meddelande till kontoinnehavaren, högst 2000 tecken.
lines ja 1–200 rader. Varje rad har antingen ean eller valvira_code och quantity (heltal 1–100 000). Rader för samma produkt slås ihop.

Beställningen godkänns i sin helhet eller inte alls. BrewOS kontrollerar på en gång att:

  1. köparens webbutikslösenord och kundrelation är giltiga och kunden inte har försäljningsförbud
  2. alkoholtillståndet är i kraft enligt Valviras tillståndsregister och av rätt typ
  3. varje kod motsvarar en publicerad produkt, köparens tillstånd tillåter produkten och produkten har ett pris
  4. det tillgängliga saldot räcker för varje rad.

Om allt är i sin ordning är svaret 201 Created, och beställningen syns för kontoinnehavaren i BrewOS som en leverans som väntar på plockning. Saldot reserveras genast.

{
  "order_id": 42849,
  "external_order_id": "WS-100234",
  "status": "received",
  "created_at": "2026-10-12T16:33:25+03:00",
  "updated_at": "2026-10-12T16:33:25+03:00",
  "delivered_at": null,
  "customer_reference": "Inköpsorder 55",
  "requested_delivery_date": "2026-10-16",
  "invoiced": false,
  "invoice_number": null,
  "license_check": "verified",
  "lines": [
    {
      "product_id": 26, "ean": "6430055480344", "valvira_code": null, "name": "Avenger", "container_size_ml": 330,
      "quantity": 24, "unit_price_excl_vat_cents": 230, "discount_percent": 0,
      "line_excl_vat_cents": 5520, "vat_percent": 25.5, "line_vat_cents": 1408,
      "line_incl_vat_cents": 6928, "deposit_incl_vat_cents": 0
    }
  ],
  "totals": { "excl_vat_cents": 5520, "vat_cents": 1408, "incl_vat_cents": 6928, "deposit_incl_vat_cents": 0 },
  "warnings": []
}
  • order_id är BrewOS leveransnummer. Kontoinnehavaren känner igen beställningen med det.
  • Pantbeloppet anges separat på raden och i totalsummorna (inkl. moms). På fakturan blir det en egen rad.
  • warnings kan innehålla license_register_stale. Då kontrollerades tillståndet i Valviras register, som inte hade uppdaterats på över 72 timmar. Beställningen har tagits emot, och kontoinnehavaren kontrollerar tillståndet före leveransen. Köparen behöver inte se något.
  • BrewOS demokonto har demoköpare vars tillståndsnummer är påhittade (de börjar med DEMO-) och inte finns i Valviras register. Med dem kan du testa inloggning och beställning från början till slut: DEMO-0001 serveringstillstånd, DEMO-0002 detaljhandelstillstånd (endast produkter som får säljas med detaljhandelstillstånd), DEMO-0003 serveringstillstånd som har upphört (inloggningen ger 403 license_not_in_force) och DEMO-0004 serveringstillstånd med specialpriser (contract_prices och discount_percent). Lösenorden får du av kontoinnehavaren. För en demoköpare innehåller warnings i inloggnings- och beställningssvaren demo_license, och beställningens license_check är demo. På andra konton godkänns inte ett DEMO--tillståndsnummer. Demokontots beställningar och deras fakturor annulleras varje natt strax efter midnatt (cancel_reason demo_reset).

Nytt försök och idempotens

Nätförbindelsen kan brytas innan svaret kommer fram. Skicka då beställningen på nytt med samma Idempotency-Key-rubrik och samma innehåll. Om BrewOS redan hann ta emot beställningen returnerar det samma beställning med statuskoden 200 OK och skapar ingen ny.

Situation Svar
Samma nyckel, samma innehåll 200 och den ursprungliga beställningen
Samma nyckel, annat innehåll 422 idempotency_key_reused
Ny nyckel, men external_order_id har redan använts 409 duplicate_order, svaret innehåller den redan mottagna beställningens order_id

GET /orders/{order_id}

Returnerar en beställning från den här webbutiken i samma format som när beställningen skapas, men utan fältet warnings. För andra webbutikers beställningar är svaret 404 order_not_found.

status Betydelse
received Mottagen, väntar på plockning
ready_for_delivery Plockad, väntar på leverans
delivered Levererad: överlämnad till transport eller till kunden (delivered_at)
cancelled Kontoinnehavaren annullerade beställningen

delivered betyder inte att beställningen redan har kommit fram: kontoinnehavaren markerar leveransen som levererad när den överlämnas till transport eller till kunden. delivered_at är tidpunkten för överlämnandet.

invoiced och invoice_number anger om beställningen har fakturerats. Kontoinnehavaren kan ändra mängderna vid plockningen, så lines och totals visar alltid läget just nu.

GET /orders?updated_since=…

Returnerar de beställningar vars status eller innehåll har ändrats efter den angivna tidpunkten, med början från den äldsta. Parametrar: updated_since (ISO 8601, valfri) och limit (standard 100, högst 500).

{ "items": [ { "order_id": 42849, "status": "ready_for_delivery", "…": "…" } ],
  "next_updated_since": "2026-10-13T08:15:00+03:00" }

Fortsätt genom att ge next_updated_since som värde för updated_since i nästa anrop. Gränsen ingår i sökningen, så den sista beställningen på föregående sida kan komma på nytt. Hantera beställningarna utifrån order_id.

GET /buyer/orders

Returnerar den inloggade köparens (rubrik X-Buyer-Token) tidigare beställningar, nyaste först. Den är avsedd för webbutikens sida med beställningshistorik och innehåller bara en sammanfattning: id, tidpunkt, värde samt leverans- och faktureringsstatus. Parametrar: page (standard 1) och per_page (standard 50, högst 200).

curl https://brewos.fi/rest/webshop/v1/buyer/orders?per_page=20 \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Buyer-Token: $BUYER_TOKEN"
{
  "items": [
    {
      "order_id": 42849,
      "external_order_id": "WS-100234",
      "channel": "webshop",
      "created_at": "2026-10-12T16:33:25+03:00",
      "customer_reference": "Ostotilaus 55",
      "status": "delivered",
      "delivered_at": "2026-10-14T09:12:00+03:00",
      "totals": { "excl_vat_cents": 5520, "vat_cents": 1408, "incl_vat_cents": 6928, "deposit_incl_vat_cents": 0 },
      "billing": {
        "status": "invoiced",
        "invoice_number": 10812,
        "invoice_date": "2026-10-14",
        "due_date": "2026-10-28",
        "invoice_total_incl_vat_cents": 6928,
        "open_cents": 6928
      }
    }
  ],
  "page": 1,
  "per_page": 20,
  "total": 37
}

Listan innehåller alla kundens beställningar för detta tillståndsnummer: även de som gjorts via kontots andra webbutiker och de som kontoinnehavaren själv har fört in i BrewOS (t.ex. telefonbeställningar, channel: "brewos", external_order_id: null). En beställning hör till ett tillståndsnummer via sitt verksamhetsställe. Beställningar utan verksamhetsställe visas bara om kunden inte har andra tillståndsnummer, så att en kedjas verksamhetsställen inte ser varandras beställningar. Ofärdiga utkast visas inte.

  • status och delivered_at är desamma som i beställningen (se GET /orders/{order_id}).
  • totals är beställningens värde nu, beräknat som beställningens totals.
  • billing.invoice_total_incl_vat_cents är fakturans slutsumma. Den kan även innehålla pantrader och andra rader som lagts till på fakturan.
  • billing.open_cents är det obetalda beloppet, eller null om det inte är känt (fakturan gjordes inte i BrewOS, eller betalningarna har ännu inte hämtats från bokföringen).
billing.status Betydelse
not_invoiced Inte fakturerad ännu
invoiced Fakturerad, inte betald, inte förfallen
partly_paid Delvis betald, inte förfallen
overdue Förfallodagen har passerat och fakturan är obetald
paid Betald
credited Krediterad eller makulerad

Ett utgånget tillstånd eller ett försäljningsförbud döljer inte historiken: köparen ser fortfarande vad som har levererats och vad som är obetalt. Listan innehåller inga beställningsrader. Raderna i webbutikens egna beställningar fås med GET /orders/{order_id}.

Konsumentbeställningar

När kontoinnehavaren har tagit konsumentförsäljning i bruk för webbutiken (Inställningar → Webbutiker → webbutiken → Konsumentförsäljning) kan webbutiken skicka en konsuments beställning till BrewOS. Konsumenten loggar inte in: anropet görs enbart med API-nyckeln.

  • Av beställningen blir det en leverans i BrewOS, som rapporteras till Valvira på kontoinnehavarens detaljhandelstillstånd. Kontoinnehavaren anger tillståndet i webbutikens inställningar.
  • Webbutikens pris gäller. Raden innehåller det styckpris som konsumenten betalat, inklusive moms. BrewOS räknar ut priset utan moms med produktens nuvarande momsklass.
  • Betalningen sköts i webbutiken. BrewOS fakturerar inte konsumenten, men för bok över om beställningen är betald.
  • En beställning kan vara betald (paid) eller obetald (unpaid), och båda reserverar saldot direkt. En obetald beställning annulleras automatiskt om den inte betalas inom den tid som kontoinnehavaren har angett (standard 60 min) och den inte ännu har plockats. reservation_expires_at anger tidpunkten.
  • Med detaljhandelstillstånd får man sälja jäsningsframställda drycker högst 8,0 % och övriga högst 5,5 %. Fältet retail_license_allowed i produktlistan anger om produkten får säljas till en konsument.

GET /webshop anger om konsumentförsäljning är i bruk:

"consumer_sales": {
  "enabled": true,
  "license_number": "1234567",
  "license_status": "active",
  "delivery_methods": [ "pickup" ],
  "unpaid_hold_minutes": 60
}

Rekommenderat förlopp

  1. När konsumenten går vidare till betalning, skicka beställningen med status unpaid. BrewOS reserverar saldot eller meddelar om brist (409 insufficient_stock) innan konsumenten betalar.
  2. När betalningen lyckas, markera beställningen som betald: POST /consumer-orders/{order_id}/payment.
  3. Om betalningen avbryts, annullera beställningen (POST /consumer-orders/{order_id}/cancel) eller låt reservationen gå ut.

En redan betald beställning kan också skickas direkt med status paid. Då framgår en saldobrist först efter betalningen, och konsumenten måste få pengarna tillbaka. Betalning vid avhämtning fungerar så att beställningen skickas med status unpaid. Kontoinnehavaren sätter då reservationens längd till noll (går inte ut) och markerar beställningen som betald i BrewOS vid utlämningen.

POST /consumer-orders

Rubriker: Authorization: Bearer … och en obligatorisk Idempotency-Key, som i POST /orders.

curl -X POST https://brewos.fi/rest/webshop/v1/consumer-orders \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: K-10023" \
  -H "Content-Type: application/json" \
  -d '{
    "external_order_id": "K-10023",
    "payment_status": "unpaid",
    "delivery_method": "pickup",
    "requested_delivery_date": "2026-11-05",
    "consumer": {
      "name": "Matti Meikäläinen",
      "email": "matti@example.fi",
      "phone": "+358401234567",
      "age_confirmed": true
    },
    "lines": [
      { "ean": "6430055480344", "quantity": 6, "unit_price_incl_vat_cents": 450 }
    ],
    "shipping_incl_vat_cents": 0,
    "total_incl_vat_cents": 2700,
    "message": "Jag hämtar på fredag"
  }'
Fält Obligatoriskt Förklaring
external_order_id ja Webbutikens ordernummer, högst 64 tecken. Unikt inom webbutiken, och samma nummerrymd som POST /orders.
payment_status ja paid eller unpaid
paid_at när paid Betalningstidpunkt, ISO 8601 med tidszon
payment_reference Betalningsförmedlarens identifierare, högst 100 tecken. Till kännedom för kontoinnehavaren.
payment_method Betalsätt som fri text, högst 40 tecken (t.ex. nätbank)
delivery_method ja pickup (avhämtning från detaljhandelsstället) eller home_delivery (hemkörning). De tillåtna sätten finns i svaret från GET /webshop.
requested_delivery_date Önskad avhämtnings- eller leveransdag ÅÅÅÅ-MM-DD
consumer.name ja Konsumentens namn, högst 200 tecken
consumer.email, consumer.phone Kontaktuppgifter till kontoinnehavaren
consumer.street, consumer.postal_code, consumer.city vid hemkörning Leveransadress
consumer.country Endast FI (standard)
consumer.age_confirmed ja Måste vara true: webbutiken har försäkrat sig om att köparen har uppgett att hen är myndig. Åldern kontrolleras ändå vid utlämningen.
lines ja 1–200 rader. Varje rad har antingen ean eller valvira_code, quantity (1–100 000) och unit_price_incl_vat_cents, dvs. det styckpris konsumenten betalat inklusive moms och rabatter, utan pant (minst 1 cent). Rader för samma produkt slås ihop och måste ha samma pris.
shipping_incl_vat_cents Leveransavgift, till kännedom
total_incl_vat_cents ja Slutsumma: rader + pant + leveransavgift. BrewOS räknar ut summan själv, och om den avviker med mer än en cent per rad är svaret 422 total_mismatch.

Pant läggs till för pantprodukter enligt kontoinnehavarens inställning. Pantbeloppet per produkt finns i fältet deposit_incl_vat_cents i produktlistan. Räkna med det i slutsumman.

BrewOS kontrollerar på en gång att:

  1. konsumentförsäljning är i bruk och leveranssättet är tillåtet
  2. kontoinnehavarens detaljhandelstillstånd är i kraft enligt Valviras tillståndsregister
  3. varje kod motsvarar en publicerad produkt, och produkten får säljas med detaljhandelstillstånd
  4. slutsumman stämmer
  5. det tillgängliga saldot räcker för varje rad.

Svaret 201 Created har samma format som för en B2B-beställning, med följande ytterligare fält:

{
  "type": "consumer",
  "order_id": 42911,
  "external_order_id": "K-10023",
  "status": "received",
  "payment": { "status": "unpaid", "paid_at": null, "reference": null, "method": null },
  "reservation_expires_at": "2026-11-03T18:42:00+02:00",
  "cancel_reason": null,
  "delivery_method": "pickup",
  "seller_license": { "license_number": "1234567", "check": "verified" },
  "consumer": { "name": "Matti Meikäläinen", "email": "matti@example.fi", "phone": "+358401234567", "…": "…" },
  "lines": [ { "…": "…", "line_excl_vat_cents": 2151, "vat_percent": 25.5, "line_incl_vat_cents": 2700 } ],
  "totals": { "excl_vat_cents": 2151, "vat_cents": 549, "incl_vat_cents": 2700, "deposit_incl_vat_cents": 0,
              "shipping_incl_vat_cents": 0, "order_total_incl_vat_cents": 2700 },
  "warnings": []
}

warnings kan innehålla följande varningar. Beställningen har ändå tagits emot.

  • license_register_stale: tillståndet kontrollerades mot Valviras register, som inte hade uppdaterats på över 72 timmar.
  • price_differs: radens pris avviker med över 20 % från BrewOS pris. Kontrollera webbutikens priser.

Idempotensen fungerar som i POST /orders.

POST /consumer-orders/{order_id}/payment

Markerar en obetald beställning som betald. Innehåll: { "paid_at": "2026-11-03T17:45:12+02:00", "payment_reference": "…", "payment_method": "…" } (paid_at är obligatoriskt). Ett upprepat anrop returnerar beställningen oförändrad (200). En redan annullerad beställning returnerar 409 order_cancelled, och då måste betalningen återbetalas till konsumenten.

POST /consumer-orders/{order_id}/cancel

Annullerar en beställning som ännu inte har plockats (status = received) och frigör saldot. Skicka {} som innehåll, eller ett tomt innehåll med Content-Length: 0: servern avvisar en POST-begäran helt utan Content-Length-huvud (403, en HTML-sida). En plockad eller levererad beställning returnerar 409 order_not_cancellable. Kontakta i så fall kontoinnehavaren. Ett upprepat anrop returnerar den annullerade beställningen (200).

GET /consumer-orders/{order_id}

Returnerar beställningen med konsumentens uppgifter. GET /orders och GET /orders/{order_id} returnerar också konsumentbeställningar ("type": "consumer"), men utan konsumentens uppgifter.

cancel_reason anger orsaken till en annullerad beställning: payment_expired (reservationen gick ut), webshop (webbutiken annullerade), brewos (kontoinnehavaren annullerade) eller demo_reset (demokontots nattliga tömning).

Lag och ansvar

  • Avhämtning från detaljhandelsstället är tillåten.
  • Hemkörning kräver ett leveranstillstånd enligt den ändring av alkohollagen som trädde i kraft den 3 juli 2026. Leveranserna kan börja tidigast i januari 2027. Kontoinnehavaren tar sättet i bruk i inställningarna när tillståndets nummer är känt.
  • Alkoholdrycker får lämnas ut till mottagaren kl. 9–21, inte till en minderårig och inte till en uppenbart berusad person. Man får inte leverera till alla adresser, till exempel skolor, daghem eller hälsovårdsenheter. Kontrollen av leveransadressen är webbutikens och transportörens ansvar.
  • Personuppgifter. BrewOS lagrar konsumentens namn och kontaktuppgifter för att behandla beställningen och raderar dem 12 månader efter leverans eller annullering. Skicka inga andra personuppgifter, till exempel i fältet message.

Tillstånd och detaljhandel

Endast verksamhetsställen vars alkoholtillstånd är i kraft i Valviras tillståndsregister och är något av följande kan beställa:

license_type Tillstånd Produktbegränsning
anniskelu Serveringstillstånd ingen begränsning
tukkumyynti Partihandelstillstånd ingen begränsning
vahittaismyynti Detaljhandelstillstånd jäsningsframställda drycker högst 8,0 %, övriga högst 5,5 %

Fältet retail_license_allowed i produktlistan anger redan om produkten får säljas med detaljhandelstillstånd. Dölj otillåtna produkter eller förhindra att de läggs i varukorgen när köparens license_type är vahittaismyynti. Annars avvisas beställningen med felet product_not_allowed_for_license.

Felkoder

HTTP code När Vad göra
400 invalid_json Innehållet är inte ett JSON-objekt Rätta förfrågan
400 invalid_parameter Felaktig frågeparameter Rätta förfrågan
400 idempotency_key_required Idempotency-Key saknas eller är felaktig Lägg till rubriken
401 invalid_api_key API-nyckeln saknas, är fel eller har återkallats Kontrollera nyckeln med kontoinnehavaren
401 invalid_credentials Fel tillståndsnummer eller lösenord Be köparen försöka igen
401 invalid_buyer_token Köpartoken saknas, har gått ut eller ogiltigförklarats (t.ex. nytt lösenord) Logga in köparen på nytt
403 webshop_disabled Kontoinnehavaren har stängt av webbutiken Kontakta kontoinnehavaren
403 account_unavailable BrewOS-kontot är inte i bruk Kontakta kontoinnehavaren
403 license_not_in_force Tillståndet är inte i kraft eller finns inte i registret Visa title, köparen kontaktar säljaren
403 license_type_not_accepted Tillståndstypen berättigar inte till beställning (t.ex. tillverkningstillstånd) Visa title
403 buyer_blocked Det går för tillfället inte att sälja till kunden Visa title, köparen kontaktar säljaren
403 consumer_sales_disabled Konsumentförsäljning är inte i bruk i den här webbutiken Dölj konsumentens möjlighet att beställa
403 seller_license_not_in_force Kontoinnehavarens detaljhandelstillstånd är inte i kraft Kontakta kontoinnehavaren
404 order_not_found Beställningen finns inte i den här webbutiken
404 not_found Sökvägen finns inte
405 method_not_allowed Fel HTTP-metod (rubriken Allow anger de rätta)
409 insufficient_stock Saldot räcker inte; errors[].available Uppdatera varukorgen enligt de tillgängliga mängderna
409 duplicate_order external_order_id har redan tagits emot; order_id Beställningen finns redan i BrewOS, skicka den inte på nytt
409 order_cancelled Den konsumentbeställning som markerats som betald har redan annullerats Återbetala betalningen till konsumenten
409 order_not_cancellable Konsumentbeställningen har redan plockats eller levererats Kontakta kontoinnehavaren
422 validation_failed Fältfel; errors[].field / errors[].line Rätta förfrågan
422 invalid_lines Radfel, se nedan Ta bort eller rätta raderna
422 idempotency_key_reused Samma nyckel med annat innehåll Använd en ny nyckel för en ny beställning
422 delivery_method_not_available Leveranssättet är inte i bruk; delivery_methods Erbjud bara de tillåtna sätten
422 total_mismatch Slutsumman stämmer inte med raderna; expected_total_incl_vat_cents Rätta summaberäkningen
429 rate_limited Över 120 anrop per minut Vänta Retry-After sekunder
429 too_many_attempts För många felaktiga inloggningar Vänta 15 minuter
500 internal_error Oväntat fel Försök senare med samma Idempotency-Key-rubrik
503 maintenance BrewOS är i underhållsläge Vänta Retry-After

Koder för radfel (invalid_lines):

errors[].code Betydelse
unknown_product Ingen publicerad produkt hittas med koden
ambiguous_product Koden motsvarar flera produkter. Kontoinnehavaren måste rätta koderna.
product_not_allowed_for_license Den här produkten får inte köpas med detaljhandelstillstånd
price_missing Produkten har inget pris för den här köparen
conflicting_line_prices Rader för samma produkt i en konsumentbeställning har olika priser

Fältfel i en konsumentbeställning (validation_failed) kan dessutom ha koden age_not_confirmed (consumer.age_confirmed är inte true) och not_domestic (annat land än FI).

Begränsningar

  • Högst 120 anrop per minut per API-nyckel.
  • Produktlistan högst 500 och saldolistan högst 1000 rader per sida.
  • Högst 200 rader per beställning och högst 100 000 st per rad.
  • Köpartoken gäller i 12 timmar.
  • Fem felaktiga inloggningar inom 15 minuter låser tillståndsnumret i 15 minuter.
  • API:et är avsett för kommunikation från server till server. Inga CORS-rubriker skickas.

OpenAPI-beskrivning

Maskinläsbar beskrivning av hela API:et (OpenAPI 3.1): /kehittajat/verkkokauppa-api/openapi.yaml. Den kan öppnas till exempel i Swagger Editor eller Postman, eller så kan man generera ett klientbibliotek från den.

Ändringshistorik

Datum Version Ändring
12.10.2026 v1 Första versionen: produkter, saldon, köparens inloggning och beställningar.
12.10.2026 v1 BrewOS interna product_id har lagts till i produktuppgifterna (produkter, saldon, avtalspriser, beställningsrader, saldofel).
12.10.2026 v1 Användning på en webbplats för konsumenter har lagts till i guiden. Beställningar endast från innehavare av alkoholtillstånd.
21.10.2026 v1 Konsumentbeställningar (/consumer-orders), fältet consumer_sales i svaret från GET /webshop och fältet type i beställningarna.
29.10.2026 v1 Köparens beställningshistorik (GET /buyer/orders): beställningar, värde samt leverans- och faktureringsstatus.

API:ets version finns i sökvägen (/v1/). Nya fält kan läggas till i svaren utan versionsbyte, så ditt program måste tåla okända fält. Inkompatibla ändringar görs i en ny version.

Sidan uppdaterades senast 31.10.2026 · OpenAPI-beskrivning