Sisällys
Verkkokauppa-API – kehittäjän ohje
BrewOSin verkkokauppa-API yhdistää BrewOS-tilin haltijan oman verkkokaupan BrewOSiin. Tilin haltija voi olla esimerkiksi panimo tai maahantuoja. Rajapinnan kautta verkkokauppa voi:
- hakea tilin tuotteet, hinnat ja vapaat varastosaldot
- kirjata sisään anniskelu-, vähittäismyynti- tai tukkumyyntiluvan haltijan
- välittää ostajan tilauksen BrewOSiin
- välittää kuluttajan tilauksen, joka raportoidaan tilin haltijan omalle vähittäismyyntiluvalle
Luvanhaltijan tilauksen maksu ei kulje verkkokaupan kautta. Tilaus syntyy BrewOSiin toimituksena, tilin haltija kerää ja toimittaa sen, ja ostaja saa laskun BrewOSista kuten ennenkin. Kuluttajatilauksen maksu hoituu verkkokaupassa, eikä BrewOS laskuta kuluttajaa.
Rajapintaa voi käyttää kolmella tavalla:
- Verkkokauppa alkoholiluvan haltijoille (anniskelu, vähittäismyynti, tukkumyynti): tuotteet, hinnat, saldot ja tilaukset.
- Kuluttajille näkyvä verkkosivusto, esimerkiksi tuote-esittely tai
saatavuustieto tilin haltijan kotisivuilla. Tuotteiden ja saldojen haku
(
GET /products,GET /stock) ei vaadi ostajan kirjautumista, ks. Kuluttajille näkyvä sivusto. - Kuluttajaverkkokauppa: kuluttajan tilaus noudettavaksi tai kotiin
kuljetettavaksi (
POST /consumer-orders), jos tilin haltija on ottanut kuluttajamyynnin käyttöön. Ks. Kuluttajatilaukset.
POST /orders vaatii ostajan, joka on kirjautunut toimipaikkansa
alkoholilupanumerolla, ja BrewOS tarkistaa luvan jokaisen tilauksen yhteydessä.
Kuluttajatilaus tehdään omalla polullaan, ja se raportoidaan tilin haltijan
vähittäismyyntiluvalle.
Tämä sivu on tarkoitettu verkkokaupan toteuttajalle. API-avaimen ja ostajien verkkokaupan salasanat luo tilin haltija omalla BrewOS-tilillään.
Pikaopas
- Pyydä tilin haltijalta API-avain. Tilin omistaja luo sen BrewOSissa kohdassa
Asetukset → Verkkokaupat. Avain on muotoa
bws_…ja näytetään vain kerran. Testaa yhteys:
curl -H "Authorization: Bearer bws_1_…" https://brewos.fi/rest/webshop/v1/webshopHae tuotteet kerran vuorokaudessa (
GET /products) ja tarvittaessa saldot useammin (GET /stock).- Kirjaa ostaja sisään lupanumerolla ja salasanalla
(
POST /buyers/authenticate) ja säilytä palautettubuyer_tokenostajan istunnossa. - Lähetä tilaus (
POST /orders) ostajatokenin ja yksilöllisenIdempotency-Key-otsakkeen kanssa. - Seuraa tilauksia (
GET /orders?updated_since=…), jos haluat näyttää ostajalle tilauksen tilan.
Perusteet
Osoite ja muoto
Kaikki polut ovat osoitteen https://brewos.fi/rest/webshop/v1 alla. Pyynnöt ja vastaukset ovat
JSONia (UTF-8). Lähetä POST-pyynnöissä otsake Content-Type: application/json.
| Asia | Muoto |
|---|---|
| Rahasummat | kokonaislukuina sentteinä, esim. 239 = 2,39 € |
| ALV | prosentteina, esim. 25.5 |
| Tilavuus | millilitroina (container_size_ml) |
| Määrät | kokonaisina myyntiyksikköinä (kpl). Monipakkaus, esim. 6 × 330 ml, on yksi yksikkö. |
| Alkoholipitoisuus | prosentteina, esim. 5.5 |
| Aikaleimat | ISO 8601 aikavyöhykkeineen, esim. 2026-10-12T14:05:12+03:00 (Suomen aika) |
| Päivämäärät | VVVV-KK-PP |
Tuotteiden tunnistaminen
Tuotteet tunnistetaan samoilla koodeilla, joilla tilin haltija raportoi Valviralle: EAN-koodilla (tarkisteineen, 13 merkkiä) tai Valvira-raportointikoodilla.
- Tuotelistauksessa jokaisella tuotteella on
ean(ensimmäinen EAN),eans(kaikki EAN-koodit) javalvira_code. Kumpi tahansa voi ollanull, mutta julkaistulla tuotteella on aina vähintään yksi koodi, joka yksilöi sen. - Tilausrivillä annetaan joko
eantaivalvira_code. Molemmat haetaan samasta koodistosta, joten kentän valinnalla ei ole merkitystä, kunhan koodi on oikea. - Jokaisessa tuotetiedossa on lisäksi BrewOSin sisäinen tuotenumero
product_id. Se on pysyvä ja yksilöllinen tilin sisällä, joten sitä voi käyttää esimerkiksi tuotteiden yhdistämiseen tilin muihin BrewOS-raportteihin. Tilausrivillä tuote nimetään kuitenkin aina koodilla. - Tallenna verkkokauppaan tuotteen koodi ja käytä sitä tilauksessa. Tuotenimi ei ole yksilöllinen, sillä samannimisiä tuotteita on eri pakkauksissa.
Virheet
Virheet palautetaan RFC 7807 -muodossa
(Content-Type: application/problem+json). Ohjelmasi kannattaa haarauttaa
kentän code perusteella. title on suomenkielinen selite, jonka voi näyttää
käyttäjälle. Rivikohtaiset virheet ovat listassa 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 }
]
}
Kaikki virhekoodit on lueteltu kohdassa Virhekoodit.
Tunnistautuminen
API-avain
Jokainen pyyntö tunnistetaan verkkokaupan API-avaimella:
Authorization: Bearer bws_1_aB3dE…
- Avain on salaisuus. Käytä sitä vain verkkokaupan palvelimella, älä koskaan selaimessa ajettavassa koodissa tai mobiilisovelluksessa. Rajapinta ei lähetä CORS-otsakkeita.
- Tilin haltija voi perua avaimen tai poistaa verkkokaupan käytöstä milloin tahansa.
Silloin rajapinta vastaa
401 invalid_api_keytai403 webshop_disabled. - Avaimen vaihto ilman katkoa: verkkokaupalla voi olla kaksi voimassa olevaa avainta kerrallaan. Ota uusi avain käyttöön ja pyydä tilin haltijaa sen jälkeen perumaan vanha.
Ostajan kirjautuminen
Ostajana on aina alkoholiluvan haltijan toimipaikka. Ostaja kirjautuu verkkokauppaan toimipaikkansa alkoholilupanumerolla ja salasanalla, jonka tilin haltija on luonut BrewOSissa ja antanut ostajalle. Verkkokauppa välittää tunnukset BrewOSille:
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"}'
Onnistuneen kirjautumisen vastauksessa on buyer_token, joka on voimassa 12 tuntia (expires_at).
- Lähetä token otsakkeessa
X-Buyer-Tokenniissä kutsuissa, jotka koskevat ostajaa (GET /buyer,POST /orders). - Älä tallenna ostajan salasanaa. Säilytä vain token ostajan istunnossa. Kun
token vanhenee tai rajapinta vastaa
401 invalid_buyer_token, pyydä ostajaa kirjautumaan uudelleen. - Salasana on muotoa
xxxx-xxxx-xxxx-xxxx. Kirjainkoolla, väliviivoilla ja välilyönneillä ei ole merkitystä. - Verkkokaupan salasana toimii kaikissa saman tilin verkkokaupoissa, mutta token vain siinä verkkokaupassa, jonka avaimella se luotiin.
client_ip(valinnainen) on ostajan IP-osoite. Se kirjataan epäonnistuneiden yritysten yhteydessä.
Virhetilanteet:
- Väärä lupanumero tai salasana palauttaa aina saman vastauksen
401 invalid_credentials. Rajapinta ei kerro, kumpi oli väärin. - Viiden epäonnistuneen yrityksen jälkeen lupanumero lukittuu 15 minuutiksi:
429 too_many_attempts. - Kun tunnukset ovat oikein, lupa ja asiakkuus tarkistetaan heti:
403 license_not_in_force,403 license_type_not_acceptedtai403 buyer_blocked. Näytä ostajalletitleja ohjaa hänet tarvittaessa ottamaan yhteyttä myyjään (tilin haltijaan).
Tuotteet ja saldot
GET /products
Palauttaa julkaistut tuotteet hintoineen ja vapaine saldoineen. Hae lista esimerkiksi kerran vuorokaudessa.
| Parametri | Oletus | Enintään |
|---|---|---|
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"
}
| Kenttä | Selitys |
|---|---|
product_id |
BrewOSin sisäinen tuotenumero (pysyvä). |
product_groups |
Tuotteen tuoteryhmät: id, name, dimensio (dimension, esim. "Tyyppi" tai "Maku") ja taso (level): product_base = juoman ryhmä, joka koskee juoman kaikkia pakkauksia, product = pakkauksen ryhmä. Tuotteella voi olla useita ryhmiä. |
product_group |
Tuotteen ensimmäisen ryhmän nimi dimensioiden järjestyksessä, tai null. Säilytetty yhteensopivuuden vuoksi; käytä product_groups-kenttää. |
pack_units |
Monipakkauksen pullojen tai tölkkien määrä. Tavallisella tuotteella 1. |
list_price |
Yleinen hinta, tai null, jos tuotteella ei ole yleistä hintaa. |
group_prices |
Asiakasryhmäkohtaiset hinnat. Ostajan ryhmä selviää kirjautumisesta. |
retail_license_allowed |
Saako tuotteen myydä vähittäismyyntiluvalla, ks. Luvat. |
deposit_incl_vat_cents |
Pantti myyntiyksikköä kohden (sis. ALV), tai null. Tilin haltija päättää, lisätäänkö pantti tilaukseen. |
available_quantity |
Vapaa saldo: varastossa olevat miinus jo vastaanotetut, keräilyä odottavat tilaukset. |
image_url |
Tuotekuva (julkinen osoite) tai null. |
Tilin haltija päättää, mitkä tuoteryhmät verkkokauppaan julkaistaan. Tuote julkaistaan, jos sillä tai sen juomalla on jokin valituista ryhmistä. Tuotteet, joilla ei ole yksilöivää koodia, jätetään pois listalta.
GET /stock
Kevyt saldolista: pelkät koodit ja vapaa saldo. Sopii tiheämpään päivitykseen. Oletuksena 500 riviä sivulla, enintään 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"
}
Saldo voi muuttua listan haun jälkeen. Ratkaiseva tarkistus tehdään, kun tilaus lähetetään, ja
409 insufficient_stock-vastaus kertoo tuoreen vapaan määrän jokaiselle riville.
Kuluttajille näkyvä sivusto
Tuotelistausta ja saldoja voi käyttää myös kuluttajille avoimella sivustolla, esimerkiksi tuote-esittelyyn tai saatavuuden näyttämiseen. Kuluttaja voi tilata vain, jos tilin haltija on ottanut kuluttajamyynnin käyttöön, ks. Kuluttajatilaukset.
- Hae tiedot palvelimella. API-avain ei saa päätyä selaimeen. Hae tuotteet ja saldot sivuston palvelimelle, tallenna ne välimuistiin ja näytä sieltä. Rajapinta sallii 120 kutsua minuutissa, joten älä kutsu sitä jokaisella sivulatauksella.
- Hinnat ovat luvanhaltijoiden hintoja.
list_price,group_pricesja sopimushinnat ovat tilin myyntihintoja anniskelu-, vähittäis- ja tukkumyyntiluvan haltijoille, eivät kuluttajahintoja. Kuluttajahinnat määrää verkkokauppa. - Saatavuus on tilin oma varasto.
available_quantitykertoo tilin haltijan vapaan saldon, ei saatavuutta kaupoissa, ravintoloissa tai Alkossa. - Tuotekuvat (
image_url) ovat julkisia osoitteita, ja niitä voi käyttää sivustolla sellaisenaan. - Alkoholimarkkinointi. Kuluttajille näkyvän sisällön pitää noudattaa alkoholilain markkinointisäännöksiä. Vastuu on sivuston ylläpitäjällä.
Ostaja ja hinnat
Kirjautumisen vastaus ja GET /buyer
POST /buyers/authenticate palauttaa ostajan tiedot. GET /buyer palauttaa
samat tiedot uudelleen tarkistettuina (otsake X-Buyer-Token). Kutsu sitä
esimerkiksi kassalla ennen tilauksen lähettämistä.
{
"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": []
}
GET /buyerin vastauksessa ei ole kenttiä buyer_token ja expires_at.
Hinnan määräytyminen
BrewOS hinnoittelee tilauksen. Verkkokauppa voi näyttää ostajalle saman hinnan tällä säännöllä:
hinta = contract_prices[koodi] jos ostajalla on sopimushinta
| group_prices[customer_group_id] muuten, jos ostajan ryhmällä on hinta
| list_price muuten
rivisumma = hinta.excl_vat_cents × määrä
jos discount_percent > 0: rivisumma = round( rivisumma × (1 − discount_percent / 100) )
ALV = round( rivisumma × vat_percent / 100 )
Tilauksen vastauksessa ovat aina BrewOSin laskemat lopulliset hinnat. Jos ne poikkeavat verkkokaupan näyttämistä (esim. tilin haltija muutti hintaa välissä), BrewOSin hinta pätee, ja lasku tehdään sen mukaan.
Tilaukset
POST /orders
Lähettää ostajan tilauksen. Otsakkeet:
| Otsake | |
|---|---|
Authorization: Bearer … |
API-avain |
X-Buyer-Token: … |
kirjautuneen ostajan token |
Idempotency-Key: … |
pakollinen, yksilöllinen tunniste tälle lähetykselle (1–100 merkkiä A–Z a–z 0–9 _ - : .), esim. verkkokaupan tilausnumero tai 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": "Ostotilaus 55",
"requested_delivery_date": "2026-10-16",
"message": "Toimitus takaovelle",
"lines": [
{ "ean": "6430055480344", "quantity": 24 },
{ "valvira_code": "000157181607", "quantity": 6 }
]
}'
| Kenttä | Pakollinen | Selitys |
|---|---|---|
external_order_id |
kyllä | Verkkokaupan tilausnumero, enintään 64 merkkiä. Yksilöllinen verkkokaupan sisällä. |
customer_reference |
Ostajan oma viite (esim. ostotilausnumero), enintään 250 merkkiä. Näkyy laskulla. | |
requested_delivery_date |
Toivottu toimituspäivä VVVV-KK-PP, ei menneisyydessä. Tilin haltija näkee toiveen, mutta se ei sido. |
|
message |
Ostajan viesti tilin haltijalle, enintään 2000 merkkiä. | |
lines |
kyllä | 1–200 riviä. Jokaisella rivillä joko ean tai valvira_code ja quantity (kokonaisluku 1–100 000). Saman tuotteen rivit yhdistetään. |
Tilaus hyväksytään kokonaan tai ei ollenkaan. BrewOS tarkistaa yhdellä kertaa:
- ostajan verkkokaupan salasana ja asiakkuus ovat voimassa, eikä asiakas ole myyntikiellossa
- alkoholilupa on voimassa Valviran luparekisterin mukaan ja oikeaa tyyppiä
- jokainen koodi vastaa yhtä julkaistua tuotetta, ostajan lupa sallii tuotteen ja tuotteella on hinta
- vapaa saldo riittää jokaiselle riville.
Jos kaikki on kunnossa, vastaus on 201 Created, ja tilaus näkyy tilin haltijalle
BrewOSissa keräilyä odottavana toimituksena. Saldo varataan heti.
{
"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": "Ostotilaus 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_idon BrewOSin toimitusnumero. Tilin haltija tunnistaa tilauksen sillä.- Panttisumma on rivillä ja yhteissummissa erikseen (sis. ALV). Laskulle se tulee omana rivinään.
warningsvoi sisältäälicense_register_stale. Silloin lupa tarkistettiin Valviran rekisteristä, jota ei ollut päivitetty yli 72 tuntiin. Tilaus on vastaanotettu, ja tilin haltija tarkistaa luvan ennen toimitusta. Ostajalle ei tarvitse näyttää mitään.
Uudelleenyritys ja idempotenssi
Verkkoyhteys voi katketa ennen kuin vastaus tulee perille. Lähetä tilaus silloin
uudelleen samalla Idempotency-Key-otsakkeella ja samalla rungolla. Jos
BrewOS ehti jo ottaa tilauksen vastaan, se palauttaa saman tilauksen tilakoodilla
200 OK eikä luo toista.
| Tilanne | Vastaus |
|---|---|
| Sama avain, sama runko | 200 ja alkuperäinen tilaus |
| Sama avain, eri runko | 422 idempotency_key_reused |
Uusi avain, mutta external_order_id on jo käytetty |
409 duplicate_order, vastauksessa jo vastaanotetun tilauksen order_id |
GET /orders/{order_id}
Palauttaa yhden tämän verkkokaupan tilauksen samassa muodossa kuin tilauksen
luonti, mutta ilman warnings-kenttää. Muiden verkkokauppojen tilauksiin
vastaus on 404 order_not_found.
status |
Merkitys |
|---|---|
received |
Vastaanotettu, odottaa keräilyä |
ready_for_delivery |
Kerätty, odottaa toimitusta |
delivered |
Toimitettu (delivered_at) |
cancelled |
Tilin haltija perui tilauksen |
invoiced ja invoice_number kertovat, onko tilaus laskutettu. Tilin haltija voi
muuttaa määriä keräilyssä, joten lines ja totals näyttävät aina tilanteen nyt.
GET /orders?updated_since=…
Palauttaa tilaukset, joiden tila tai sisältö on muuttunut annetun hetken jälkeen,
vanhimmasta alkaen. Parametrit: updated_since (ISO 8601, valinnainen) ja
limit (oletus 100, enintään 500).
{ "items": [ { "order_id": 42849, "status": "ready_for_delivery", "…": "…" } ],
"next_updated_since": "2026-10-13T08:15:00+03:00" }
Jatka antamalla next_updated_since seuraavassa kutsussa updated_since-arvona.
Raja sisältyy hakuun, joten edellisen sivun viimeinen tilaus voi tulla uudelleen.
Käsittele tilaukset order_id:n perusteella.
Kuluttajatilaukset
Kun tilin haltija on ottanut verkkokaupan kuluttajamyynnin käyttöön (Asetukset → Verkkokaupat → verkkokauppa → Kuluttajamyynti), verkkokauppa voi lähettää BrewOSiin kuluttajan tilauksen. Kuluttaja ei kirjaudu: kutsu tehdään pelkällä API-avaimella.
- Tilauksesta tulee BrewOSiin toimitus, joka raportoidaan Valviralle tilin haltijan vähittäismyyntiluvalle. Tilin haltija asettaa luvan verkkokaupan asetuksissa.
- Verkkokaupan hinta pätee. Rivillä on kuluttajan maksama yksikköhinta arvonlisäveroineen. BrewOS laskee verottoman hinnan tuotteen nykyisellä ALV-kannalla.
- Maksu hoituu verkkokaupassa. BrewOS ei laskuta kuluttajaa, mutta pitää kirjaa siitä, onko tilaus maksettu.
- Tilaus voi olla maksettu (
paid) tai maksamaton (unpaid), ja kumpikin varaa saldon heti. Maksamaton tilaus perutaan automaattisesti, jos sitä ei makseta tilin haltijan asettamassa ajassa (oletus 60 min) eikä sitä ole vielä kerätty.reservation_expires_atkertoo hetken. - Vähittäismyyntiluvalla saa myydä käymisteitse valmistettuja juomia enintään
8,0 % ja muita enintään 5,5 %. Tuotelistauksen
retail_license_allowedkertoo, saako tuotteen myydä kuluttajalle.
GET /webshop kertoo, onko kuluttajamyynti käytössä:
"consumer_sales": {
"enabled": true,
"license_number": "1234567",
"license_status": "active",
"delivery_methods": [ "pickup" ],
"unpaid_hold_minutes": 60
}
Suositeltu kulku
- Kun kuluttaja siirtyy maksamaan, lähetä tilaus tilassa
unpaid. BrewOS varaa saldon tai kertoo vajauksesta (409 insufficient_stock) ennen kuin kuluttaja maksaa. - Kun maksu onnistuu, merkitse tilaus maksetuksi:
POST /consumer-orders/{order_id}/payment. - Jos maksu keskeytyy, peru tilaus (
POST /consumer-orders/{order_id}/cancel) tai anna varauksen vanhentua.
Valmiiksi maksetun tilauksen voi myös lähettää suoraan tilassa paid. Silloin
saldovajaus selviää vasta maksun jälkeen, ja kuluttajalle pitää palauttaa rahat.
Maksu noudettaessa toimii niin, että tilaus lähetetään tilassa unpaid. Tilin
haltija asettaa silloin varauksen keston nollaksi (ei vanhene) ja merkitsee
tilauksen BrewOSissa maksetuksi luovutuksen yhteydessä.
POST /consumer-orders
Otsakkeet: Authorization: Bearer … ja pakollinen Idempotency-Key, kuten 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": "Noudan perjantaina"
}'
| Kenttä | Pakollinen | Selitys |
|---|---|---|
external_order_id |
kyllä | Verkkokaupan tilausnumero, enintään 64 merkkiä. Yksilöllinen verkkokaupan sisällä, ja sama numeroavaruus kuin POST /orders. |
payment_status |
kyllä | paid tai unpaid |
paid_at |
kun paid |
Maksuaika, ISO 8601 aikavyöhykkeineen |
payment_reference |
Maksunvälittäjän tunniste, enintään 100 merkkiä. Tiedoksi tilin haltijalle. | |
payment_method |
Maksutapa vapaana tekstinä, enintään 40 merkkiä (esim. verkkopankki) |
|
delivery_method |
kyllä | pickup (nouto vähittäismyyntipaikasta) tai home_delivery (kotiinkuljetus). Sallitut tavat ovat GET /webshop-vastauksessa. |
requested_delivery_date |
Toivottu nouto- tai toimituspäivä VVVV-KK-PP |
|
consumer.name |
kyllä | Kuluttajan nimi, enintään 200 merkkiä |
consumer.email, consumer.phone |
Yhteystiedot tilin haltijalle | |
consumer.street, consumer.postal_code, consumer.city |
kotiinkuljetuksessa | Toimitusosoite |
consumer.country |
Vain FI (oletus) |
|
consumer.age_confirmed |
kyllä | Pitää olla true: verkkokauppa on varmistanut, että ostaja on ilmoittanut olevansa täysi-ikäinen. Ikä tarkistetaan silti luovutettaessa. |
lines |
kyllä | 1–200 riviä. Jokaisella rivillä joko ean tai valvira_code, quantity (1–100 000) ja unit_price_incl_vat_cents, eli kuluttajan maksama yksikköhinta arvonlisäveroineen ja alennuksineen ilman panttia (vähintään 1 snt). Saman tuotteen rivit yhdistetään, ja niillä pitää olla sama hinta. |
shipping_incl_vat_cents |
Toimitusmaksu, tiedoksi | |
total_incl_vat_cents |
kyllä | Loppusumma: rivit + pantti + toimitusmaksu. BrewOS laskee summan itse, ja jos se poikkeaa yli sentin riviä kohden, vastaus on 422 total_mismatch. |
Pantti lisätään panttituotteille tilin haltijan asetuksen mukaan.
Pantin määrä tuotetta kohden on tuotelistauksen kentässä deposit_incl_vat_cents.
Laske se mukaan loppusummaan.
BrewOS tarkistaa yhdellä kertaa:
- kuluttajamyynti on käytössä ja toimitustapa sallittu
- tilin haltijan vähittäismyyntilupa on voimassa Valviran luparekisterin mukaan
- jokainen koodi vastaa yhtä julkaistua tuotetta, ja tuotteen saa myydä vähittäismyyntiluvalla
- loppusumma täsmää
- vapaa saldo riittää jokaiselle riville.
Vastaus 201 Created on samaa muotoa kuin B2B-tilauksessa, ja siinä on lisäksi
seuraavat kentät:
{
"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 voi sisältää seuraavat varoitukset. Tilaus on silti vastaanotettu.
license_register_stale: lupa tarkistettiin Valviran rekisteristä, jota ei ollut päivitetty yli 72 tuntiin.price_differs: rivin hinta poikkeaa yli 20 % BrewOSin hinnasta. Tarkista verkkokaupan hinnat.
Idempotenssi toimii kuten POST /orders.
POST /consumer-orders/{order_id}/payment
Merkitsee maksamattoman tilauksen maksetuksi. Runko:
{ "paid_at": "2026-11-03T17:45:12+02:00", "payment_reference": "…", "payment_method": "…" }
(paid_at pakollinen). Toisto palauttaa tilauksen muuttumattomana (200).
Jo peruttu tilaus palauttaa 409 order_cancelled, ja silloin maksu pitää palauttaa kuluttajalle.
POST /consumer-orders/{order_id}/cancel
Peruu tilauksen, jota ei ole vielä kerätty (status = received), ja vapauttaa
saldon. Lähetä runkona {} tai tyhjä runko otsakkeella Content-Length: 0: palvelin
hylkää POST-pyynnön, jossa Content-Length-otsake puuttuu kokonaan
(403, HTML-sivu). Kerätty tai toimitettu tilaus palauttaa
409 order_not_cancellable. Ota silloin yhteyttä tilin haltijaan. Toisto
palauttaa perutun tilauksen (200).
GET /consumer-orders/{order_id}
Palauttaa tilauksen kuluttajan tietoineen. GET /orders ja GET /orders/{order_id}
palauttavat myös kuluttajatilaukset ("type": "consumer"), mutta ilman kuluttajan tietoja.
cancel_reason kertoo perutun tilauksen syyn: payment_expired (varaus vanheni),
webshop (verkkokauppa perui) tai brewos (tilin haltija perui).
Laki ja vastuut
- Nouto vähittäismyyntipaikasta on sallittu.
- Kotiinkuljetus vaatii alkoholilain 3.7.2026 voimaan tulleen muutoksen mukaisen toimitusluvan. Toimitukset voivat alkaa aikaisintaan tammikuussa 2027. Tilin haltija ottaa tavan käyttöön asetuksissa, kun luvan numero on tiedossa.
- Alkoholijuomat saa luovuttaa vastaanottajalle klo 9–21, ei alaikäiselle eikä selvästi päihtyneelle. Kaikkiin osoitteisiin ei saa toimittaa, esimerkiksi kouluihin, päiväkoteihin tai terveydenhuollon yksiköihin. Toimitusosoitteen tarkistus on verkkokaupan ja kuljettajan vastuulla.
- Henkilötiedot. BrewOS säilyttää kuluttajan nimen ja yhteystiedot tilauksen
käsittelyä varten ja poistaa ne 12 kuukauden kuluttua toimituksesta tai
perumisesta. Älä lähetä muita henkilötietoja esimerkiksi
message-kentässä.
Luvat ja vähittäismyynti
Tilata voivat vain toimipaikat, joiden alkoholilupa on voimassa Valviran luparekisterissä ja on jokin seuraavista:
license_type |
Lupa | Tuoterajaus |
|---|---|---|
anniskelu |
Anniskelulupa | ei rajausta |
tukkumyynti |
Tukkumyyntilupa | ei rajausta |
vahittaismyynti |
Vähittäismyyntilupa | käymisteitse valmistetut juomat enintään 8,0 %, muut enintään 5,5 % |
Tuotelistauksen kenttä retail_license_allowed kertoo valmiiksi, saako tuotteen
myydä vähittäismyyntiluvalla. Piilota kielletyt tuotteet tai estä niiden lisääminen
ostoskoriin, kun ostajan license_type on vahittaismyynti. Muuten tilaus
hylätään virheellä product_not_allowed_for_license.
Virhekoodit
| HTTP | code |
Milloin | Mitä tehdä |
|---|---|---|---|
| 400 | invalid_json |
Runko ei ole JSON-objekti | Korjaa pyyntö |
| 400 | invalid_parameter |
Virheellinen kyselyparametri | Korjaa pyyntö |
| 400 | idempotency_key_required |
Idempotency-Key puuttuu tai on virheellinen |
Lisää otsake |
| 401 | invalid_api_key |
API-avain puuttuu, on väärä tai peruttu | Tarkista avain tilin haltijalta |
| 401 | invalid_credentials |
Väärä lupanumero tai salasana | Pyydä ostajaa yrittämään uudelleen |
| 401 | invalid_buyer_token |
Ostajatoken puuttuu, vanheni tai mitätöitiin (esim. uusi salasana) | Kirjaa ostaja uudelleen sisään |
| 403 | webshop_disabled |
Tilin haltija on poistanut verkkokaupan käytöstä | Ota yhteyttä tilin haltijaan |
| 403 | account_unavailable |
BrewOS-tili ei ole käytössä | Ota yhteyttä tilin haltijaan |
| 403 | license_not_in_force |
Lupa ei ole voimassa tai ei löydy rekisteristä | Näytä title, ostaja ottaa yhteyttä myyjään |
| 403 | license_type_not_accepted |
Lupatyyppi ei oikeuta tilaamaan (esim. valmistuslupa) | Näytä title |
| 403 | buyer_blocked |
Asiakkaalle ei voi tällä hetkellä myydä | Näytä title, ostaja ottaa yhteyttä myyjään |
| 403 | consumer_sales_disabled |
Kuluttajamyynti ei ole käytössä tässä verkkokaupassa | Piilota kuluttajan tilausmahdollisuus |
| 403 | seller_license_not_in_force |
Tilin haltijan vähittäismyyntilupa ei ole voimassa | Ota yhteyttä tilin haltijaan |
| 404 | order_not_found |
Tilausta ei ole tällä verkkokaupalla | |
| 404 | not_found |
Polkua ei ole | |
| 405 | method_not_allowed |
Väärä HTTP-metodi (Allow-otsake kertoo oikeat) |
|
| 409 | insufficient_stock |
Saldo ei riitä; errors[].available |
Päivitä ostoskori vapaiden määrien mukaan |
| 409 | duplicate_order |
external_order_id on jo vastaanotettu; order_id |
Tilaus on jo BrewOSissa, älä lähetä uudelleen |
| 409 | order_cancelled |
Maksettavaksi merkitty kuluttajatilaus on jo peruttu | Palauta maksu kuluttajalle |
| 409 | order_not_cancellable |
Kuluttajatilaus on jo kerätty tai toimitettu | Ota yhteyttä tilin haltijaan |
| 422 | validation_failed |
Kenttävirheet; errors[].field / errors[].line |
Korjaa pyyntö |
| 422 | invalid_lines |
Rivivirheet, ks. alla | Poista tai korjaa rivit |
| 422 | idempotency_key_reused |
Sama avain eri rungolla | Käytä uutta avainta uudelle tilaukselle |
| 422 | delivery_method_not_available |
Toimitustapa ei ole käytössä; delivery_methods |
Tarjoa vain sallitut tavat |
| 422 | total_mismatch |
Loppusumma ei täsmää riveihin; expected_total_incl_vat_cents |
Korjaa summan laskenta |
| 429 | rate_limited |
Yli 120 kutsua minuutissa | Odota Retry-After sekuntia |
| 429 | too_many_attempts |
Liian monta väärää kirjautumista | Odota 15 minuuttia |
| 500 | internal_error |
Odottamaton virhe | Yritä myöhemmin samalla Idempotency-Key-otsakkeella |
| 503 | maintenance |
BrewOS on huoltotilassa | Odota Retry-After |
Rivivirheiden (invalid_lines) koodit:
errors[].code |
Merkitys |
|---|---|
unknown_product |
Koodilla ei löydy julkaistua tuotetta |
ambiguous_product |
Koodi vastaa useaa tuotetta. Tilin haltijan pitää korjata koodit. |
product_not_allowed_for_license |
Vähittäismyyntiluvalla ei saa ostaa tätä tuotetta |
price_missing |
Tuotteella ei ole hintaa tälle ostajalle |
conflicting_line_prices |
Kuluttajatilauksen saman tuotteen riveillä on eri hinnat |
Kuluttajatilauksen kenttävirheissä (validation_failed) voi lisäksi olla koodi
age_not_confirmed (consumer.age_confirmed ei ole true) ja not_domestic
(maa muu kuin FI).
Rajoitukset
- Enintään 120 kutsua minuutissa API-avainta kohden.
- Tuotelista enintään 500 ja saldolista enintään 1000 riviä sivulla.
- Tilauksessa enintään 200 riviä ja rivillä enintään 100 000 kpl.
- Ostajatoken on voimassa 12 tuntia.
- Viisi väärää kirjautumista 15 minuutin sisällä lukitsee lupanumeron 15 minuutiksi.
- Rajapinta on tarkoitettu palvelimelta palvelimelle. CORS-otsakkeita ei lähetetä.
OpenAPI-kuvaus
Koneluettava kuvaus koko rajapinnasta (OpenAPI 3.1): /kehittajat/verkkokauppa-api/openapi.yaml. Sen voi avata esimerkiksi Swagger Editorissa tai Postmanissa, tai siitä voi generoida asiakaskirjaston.
Muutoshistoria
| Päivä | Versio | Muutos |
|---|---|---|
| 12.10.2026 | v1 | Ensimmäinen versio: tuotteet, saldot, ostajan kirjautuminen ja tilaukset. |
| 12.10.2026 | v1 | Tuotetietoihin lisätty BrewOSin sisäinen product_id (tuotteet, saldot, sopimushinnat, tilausrivit, saldovirheet). |
| 12.10.2026 | v1 | Ohjeeseen lisätty kuluttajille näkyvän sivuston käyttö. Tilaukset vain alkoholiluvan haltijoilta. |
| 21.10.2026 | v1 | Kuluttajatilaukset (/consumer-orders), GET /webshop-vastauksen consumer_sales ja tilausten type-kenttä. |
Rajapinnan versio on polussa (/v1/). Uusia kenttiä voidaan lisätä vastauksiin
ilman versionvaihtoa, joten ohjelmasi pitää sietää tuntemattomia kenttiä.
Yhteensopimattomat muutokset tehdään uuteen versioon.