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
- 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. Testa förbindelsen:
curl -H "Authorization: Bearer bws_1_…" https://brewos.fi/rest/webshop/v1/webshopHämta produkterna en gång per dygn (
GET /products) och vid behov saldona oftare (GET /stock).- Logga in köparen med tillståndsnummer och lösenord
(
POST /buyers/authenticate) och spara den returneradebuyer_tokeni köparens session. - Skicka beställningen (
POST /orders) med köpartoken och en unikIdempotency-Key-rubrik. - Följ beställningarna (
GET /orders?updated_since=…) om du vill visa köparen beställningens status. - 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) ochvalvira_code. Vilken som helst av dem kan varanull, men en publicerad produkt har alltid minst en kod som identifierar den. - På en beställningsrad anges antingen
eanellervalvira_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_keyeller403 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-Tokeni 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_acceptedeller403 buyer_blocked. Visatitlefö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_stockanger 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_pricesoch 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_quantityanger 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:
- köparens webbutikslösenord och kundrelation är giltiga och kunden inte har försäljningsförbud
- alkoholtillståndet är i kraft enligt Valviras tillståndsregister och av rätt typ
- varje kod motsvarar en publicerad produkt, köparens tillstånd tillåter produkten och produkten har ett pris
- 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.
warningskan innehållalicense_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-0001serveringstillstånd,DEMO-0002detaljhandelstillstånd (endast produkter som får säljas med detaljhandelstillstånd),DEMO-0003serveringstillstånd som har upphört (inloggningen ger403 license_not_in_force) ochDEMO-0004serveringstillstånd med specialpriser (contract_pricesochdiscount_percent). Lösenorden får du av kontoinnehavaren. För en demoköpare innehållerwarningsi inloggnings- och beställningssvarendemo_license, och beställningenslicense_checkärdemo. På andra konton godkänns inte ettDEMO--tillståndsnummer. Demokontots beställningar och deras fakturor annulleras varje natt strax efter midnatt (cancel_reasondemo_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.
statusochdelivered_atär desamma som i beställningen (se GET /orders/{order_id}).totalsär beställningens värde nu, beräknat som beställningenstotals.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, ellernullom 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_atanger 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_allowedi 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
- 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. - När betalningen lyckas, markera beställningen som betald:
POST /consumer-orders/{order_id}/payment. - 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:
- konsumentförsäljning är i bruk och leveranssättet är tillåtet
- kontoinnehavarens detaljhandelstillstånd är i kraft enligt Valviras tillståndsregister
- varje kod motsvarar en publicerad produkt, och produkten får säljas med detaljhandelstillstånd
- slutsumman stämmer
- 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.