Kūrėjams
REST API ir webhook'ai
Prijunkite ERP, sandėlio ar buhalterijos sistemą prie Mervio parduotuvės: prekės, likučiai pagal saloną, užsakymai, siuntos ir klientai, o įvykiai – per kelias sekundes.
Pradžia
Mervio REST API leidžia jūsų ERP, sandėlio ar buhalterijos sistemai dirbti su parduotuve: prekės, likučiai pagal saloną, užsakymai, siuntos, klientai, nuolaidos, B2B kainynai ir webhook'ai. API yra nuo Grow plano; Pro – didesni limitai ir B2B resursai.
- Parduotuvės admin'e įjunkite Nustatymai → Moduliai → API ir webhook'ai.
- Atidarykite Nustatymai → API ir integracijos → Tokenai → Naujas tokenas. Paskirtis – REST API, pažymėkite tik reikalingas teises ir nukopijuokite tokeną (
mrv_…). Jis parodomas vieną kartą. - Kreipkitės į API:
curl https://api.mervio.lt/v1/shop \
-H "Authorization: Bearer mrv_…"
Atsakyme – parduotuvė, jos kalbos ir valiuta, plano limitai ir tokeno teisės. Visi kiti endpointai – API žinyne (OpenAPI 3.1: https://api.mervio.lt/v1/openapi.json). Žinynas – anglų kalba.
Susitarimai
- JSON į abi puses, laukų pavadinimai
snake_case, datos ISO 8601 UTC (2026-10-28T15:57:43Z). - Pinigai – dešimtainė eilutė su PVM, pvz.
"19.99", parduotuvės valiuta. - Verčiami laukai – objektai pagal kalbą:
{"lt": "Kava", "en": "Coffee"}. - Prekės, variantai, klientai, užsakymai, siuntos ir webhook'ai turi 26 simbolių
id. Prekę galima rasti ir pagalhandle, užsakymą – pagalnumber, klientą – pagal el. paštą. external_id– jūsų sistemos ID (ERP kodas) prekei, variantui, užsakymui ar klientui; sąrašus filtruokite?external_id=….- Atsakymuose bet kada gali atsirasti naujų laukų: nežinomus praleiskite.
Tokenai ir teisės
Tokenas priklauso vienai parduotuvei ir veikia jį sukūrusio komandos nario teisėmis: niekada negali daugiau nei jis ir nustoja veikti, kai narys pašalinamas iš komandos. Integracijai, kuri turi veikti nepriklausomai nuo žmonių, savininkas gali sukurti parduotuvės raktą: jis nepriklauso nuo žmogaus, o audite matomas kaip „API raktas: pavadinimas“.
| Teisė | Leidžia |
|---|---|
catalog:read / catalog:write |
Prekės, variantai, likučiai, salonai, kolekcijos, gamintojai; kainynai (Pro) |
orders:read / orders:write |
Užsakymai, siuntos, grąžinimai, sąskaitos; užsakymų kūrimas |
customers:read / customers:write |
Klientai ir klientų grupės (Pro); pirkėjo el. paštas ir telefonas užsakymuose |
discounts:read / discounts:write |
Kuponai ir automatinės nuolaidos |
webhooks:read / webhooks:write |
Webhook'ų adresai ir pristatymai |
Be customers:read užsakymų atsakymuose ir webhook'uose email ir phone yra null: sandėliui, kuris tik išsiunčia prekes, nereikia matyti, kaip susisiekti su pirkėju.
- Paskirtis: tokenas skirtas MCP (AI asistentams), REST API arba abiem. Tik API tokeno AI asistentas naudoti negali, ir atvirkščiai.
- Galiojimas: 30, 90, 365 d. arba neribotai. Atšauktas tokenas nustoja veikti iškart.
- Keitimas: „Naujas vietoj šio“ sukuria naują tokeną su tomis pačiomis teisėmis, o senasis veikia dar 24 valandas – spėsite persijungti be prastovos.
- IP sąrašas (Pro): tokeną galima apriboti IP adresais ar tinklais (CIDR).
- Tokenus saugokite kaip slaptažodžius. Jie prasideda
mrv_, todėl slaptųjų raktų skeneriai juos randa kode.
Puslapiavimas ir sinchronizacija
Sąrašai puslapiuojami žymekliu (cursor). Nurodykite limit (1–250, numatyta 50), o kitam puslapiui – ankstesnio atsakymo next_cursor:
{ "data": [ … ], "next_cursor": "eyJzIjoiaWQiLCJpZCI6NTB9", "has_more": true }
sort – id (seniausi pirmi, numatyta), -id (naujausi pirmi) arba updated_at.
Kad jūsų sistema neatsiliktų, užklausinėkite su updated_since (sąrašas tada rikiuojamas pagal updated_at):
curl "https://api.mervio.lt/v1/products?updated_since=2026-10-28T12:00:00Z&limit=250" \
-H "Authorization: Bearer mrv_…"
Eikite pagal next_cursor, kol has_more bus false, įsiminkite naujausią matytą updated_at ir kitą kartą pradėkite nuo jo. Webhook'ai praneša, kada sinchronizuoti; žymeklis užtikrina, kad nieko nepraleisite.
Likučiai
Likutis saugomas pagal saloną ar sandėlį (GET /v1/locations). Be location_id turimas omenyje pagrindinis sandėlis. Kiekvienas pakeitimas įrašomas kaip likučio judėjimas.
| Endpointas | Ką daro |
|---|---|
POST /v1/inventory/set |
{ "sku": "KAVA-1", "location_id": 3, "on_hand": 12 } – absoliutus kiekis |
POST /v1/inventory/adjust |
{ "sku": "KAVA-1", "delta": -2, "reason": "manual" } – pridėti ar nurašyti (ne žemiau 0) |
POST /v1/inventory/bulk |
iki 100 eilučių (Pro – 1 000) { sku arba variant_id, location_id, on_hand } |
GET /v1/inventory |
likučiai: on_hand, reserved (neapmokėti užsakymai) ir available |
Masinė užklausa skaičiuojama kaip viena, todėl 50 000 likučių atnaujinsite per kelias minutes:
curl -X POST https://api.mervio.lt/v1/inventory/bulk \
-H "Authorization: Bearer mrv_…" -H "Content-Type: application/json" \
-d '{"items": [{"sku": "KAVA-1", "on_hand": 12}, {"sku": "ARB-2", "location_id": 3, "on_hand": 0}]}'
Atsakyme įrašytos eilutės – updated, kitos – failed su code (not_found, ambiguous_sku, kai tą patį SKU turi keli variantai, location_not_found). Kainas, akcijos kainas, savikainą ir svorį taip pat keičia POST /v1/variants/bulk.
Webhook'ai
Užregistruokite https adresą per POST /v1/webhooks (arba admin'e) ir pasirinkite įvykius. Atsakyme – parašo raktas whsec_…; išsaugokite jį.
curl -X POST https://api.mervio.lt/v1/webhooks \
-H "Authorization: Bearer mrv_…" -H "Content-Type: application/json" \
-d '{"url": "https://erp.example.lt/mervio", "events": ["order.paid", "order.cancelled", "inventory.updated"]}'
Kiekvienas pristatymas – POST su tokiu turiniu:
{
"id": "01k8x3m4n5p6q7r8s9t0v1w2x3",
"type": "order.paid",
"created_at": "2026-10-28T15:57:43Z",
"api_version": "2026-10-28",
"shop_id": "01k8…",
"data": { "id": "01k8…", "number": 1042, "financial_status": "paid", … }
}
data – resursas tokia pačia forma, kokią grąžina GET /v1/… (užsakymas, siunta, prekė, variantas su likučiais ar klientas), todėl papildomos užklausos nereikia.
- Pristatymas: bent kartą. Atsakykite
2xxper 10 sekundžių, darbą atlikite po to. Dublikatus atpažinkite pagal antraštęwebhook-id(lygiid). Tvarka negarantuojama: lyginkitedata.updated_atir senesnes būsenas praleiskite. - Pakartojimai: po 1 min., 5 min., 30 min., 2 val., 6 val., 12 val. ir 24 val. (8 bandymai per maždaug 2 dienas). Peradresavimų nesekame.
- Išjungimas: adresas, kuriam 3 dienas nepavyko pristatyti nė vieno įvykio, išjungiamas, o savininkas gauna laišką (įspėjimas – po 24 val.). Įjunkite jį ir pakartokite praleistus (
POST /v1/webhook-deliveries/{id}/redeliverarba „Pakartoti nepristatytus“ admin'e). - Masiniai pakeitimai:
product.updatedirinventory.updatedsiunčiami daugiausia kartą per minutę prekei ar variantui, su naujausia būsena. - Bandymas:
POST /v1/webhooks/{id}/testišsiunčiawebhook.test.
Parašo patikra
Mervio pasirašo pristatymus pagal atvirą Standard Webhooks specifikaciją. Antraštės: webhook-id, webhook-timestamp (Unix sekundės) ir webhook-signature (v1,<base64>, keičiant raktą – keli, atskirti tarpu). Parašas – HMAC-SHA256 nuo {webhook-id}.{webhook-timestamp}.{neapdorotas turinys} su rakto dalimi po whsec_, iškoduota iš base64. Naudokite neapdorotą užklausos turinį (ne iš naujo užkoduotą JSON) ir atmeskite senesnius nei 5 min. laiko žymenis.
PHP:
function mervioWebhookIsValid(string $secret, array $headers, string $body): bool
{
$id = $headers['webhook-id'] ?? '';
$timestamp = $headers['webhook-timestamp'] ?? '';
if (! ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
return false;
}
$key = base64_decode(substr($secret, strlen('whsec_')));
$expected = 'v1,'.base64_encode(hash_hmac('sha256', "{$id}.{$timestamp}.{$body}", $key, true));
foreach (explode(' ', $headers['webhook-signature'] ?? '') as $signature) {
if (hash_equals($expected, $signature)) {
return true;
}
}
return false;
}
$valid = mervioWebhookIsValid(getenv('MERVIO_WEBHOOK_SECRET'), array_change_key_case(getallheaders()), file_get_contents('php://input'));
Node.js (Express):
import crypto from 'node:crypto';
import express from 'express';
function isValid(secret, headers, body) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
if (
!/^\d+$/.test(timestamp) ||
Math.abs(Date.now() / 1000 - Number(timestamp)) > 300
)
return false;
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const expected =
'v1,' +
crypto
.createHmac('sha256', key)
.update(`${id}.${timestamp}.${body}`)
.digest('base64');
return (headers['webhook-signature'] || '')
.split(' ')
.some(
(signature) =>
signature.length === expected.length &&
crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
),
);
}
const app = express();
app.post('/mervio', express.raw({ type: 'application/json' }), (req, res) => {
if (
!isValid(
process.env.MERVIO_WEBHOOK_SECRET,
req.headers,
req.body.toString('utf8'),
)
)
return res.sendStatus(401);
res.sendStatus(204);
// JSON.parse(req.body) apdorokite atsakę
});
Paruoštų Standard Webhooks bibliotekų yra PHP, Node, Python, Go, Ruby, Java ir kitoms kalboms.
Įvykiai
| Event | Data | When |
|---|---|---|
order.created |
order | An order was placed (also unpaid ones, e.g. bank transfer). |
order.paid |
order | The order's payment was confirmed. |
order.updated |
order | The order changed: status, payment, fulfilment, addresses, lines. |
order.cancelled |
order | The order was cancelled. |
order.refunded |
order + refund |
Money was refunded, in part or in full. |
shipment.created |
shipment | A shipment left, with its tracking number. |
shipment.ready_for_pickup |
shipment | The parcel waits in a parcel locker or store. |
shipment.delivered |
shipment | The parcel was delivered. |
product.created |
product | A product was created. |
product.updated |
product | A product or its variants changed (at most once a minute per product). |
product.archived |
product | A product was archived. |
inventory.updated |
variant | A variant's stock changed at a location (at most once a minute per variant). |
customer.created |
customer | A customer was created. |
customer.updated |
customer | A customer's details changed. |
webhook.test |
{message, endpoint_id} |
Sent with "Send test"; always delivered to that endpoint only. |
Klaidos ir limitai
Klaidos – RFC 9457 formatas (application/problem+json). Sprendimus darykite pagal code, jis nesikeičia:
{
"type": "https://mervio.lt/developers/errors#validation_failed",
"title": "Unprocessable content",
"status": 422,
"detail": "The request has invalid fields.",
"code": "validation_failed",
"errors": {
"variants.0.price": ["The variants.0.price field must be a number."]
},
"request_id": "req_01k8x3…"
}
| Kodas | code |
Reiškia |
|---|---|---|
| 400 | invalid_json |
Turinys nėra JSON objektas |
| 401 | unauthorized |
Nėra tokeno arba jis neteisingas, nebegalioja ar atšauktas |
| 403 | missing_scope |
Tokenui trūksta teisės (required_scope ją nurodo) |
| 403 | plan_required, module_disabled |
Plane nėra API ar B2B, arba modulis išjungtas |
| 403 | wrong_token_kind, ip_not_allowed |
Tik MCP tokenas arba IP ne iš tokeno sąrašo |
| 404 | not_found |
Šioje parduotuvėje tokio resurso nėra |
| 409 | edit_conflict |
If-Match nesutampa: kažkas pakeitė po jūsų skaitymo |
| 409 | invalid_state |
Dabar negalima (pvz. atšaukti išsiųsto užsakymo) |
| 409 | external_id_taken, idempotency_in_progress |
|
| 413 / 415 | payload_too_large, unsupported_media_type |
Daugiau nei 2 MB arba ne JSON |
| 422 | validation_failed, idempotency_key_reused, invalid_cursor |
|
| 429 | rate_limited, daily_quota_exceeded |
Palaukite Retry-After sekundžių |
Prašydami pagalbos nurodykite request_id (taip pat antraštė X-Request-Id): pagal jį randame užklausą, o parduotuvė mato jį veiksmų žurnale.
| Planas | Užklausų tokenui | Užklausų parduotuvei per parą | Masinės eilutės | Webhook'ų adresai | Tokenai |
|---|---|---|---|---|---|
| Grow | 2/s, šuolis iki 40 | 50 000 | 100 | 5 | 10 |
| Pro | 10/s, šuolis iki 200 | 500 000 | 1 000 | 25 | 25 |
Kiekviename atsakyme – RateLimit-Limit, RateLimit-Remaining ir RateLimit-Reset.
Saugūs pakartojimai. Su kiekvienu POST siųskite antraštę Idempotency-Key (unikali eilutė, pvz. UUID). Pakartojus užklausą su tuo pačiu raktu per 24 val., grąžinamas pirmasis atsakymas (Idempotent-Replayed: true), o ne kuriamas antras užsakymas ar siunta.
Lygiagretūs pakeitimai. GET atsakymai turi ETag. Grąžinkite jį su PATCH kaip If-Match, kad neperrašytumėte tarp jūsų skaitymo ir rašymo atlikto pakeitimo (409 edit_conflict).
Receptas: užsakymai į ERP, siuntos atgal
- ERP sistemai sukurkite parduotuvės raktą su
orders:read,orders:write,catalog:read,catalog:write,webhooks:write(ircustomers:read, jei ERP reikia pirkėjo kontaktų). - Užregistruokite webhook'ą įvykiams
order.paid,order.cancelledirorder.updated. Pavedimams ir grynaisiais – darorder.created. - Gavę
order.paid, sukurkite pardavimo užsakymą ERP pagaldata(eilutės turisku,quantity,unit_price, kiekviena – savoid). Išsaugokite ERP numerį:PATCH /v1/orders/{id}su{"external_id": "SO-2026-1042"}. - Išvykus siuntai –
POST /v1/orders/{id}/shipmentssu{"tracking_number": "LP123456789LT"}(daliniam išsiuntimui –lines). Pirkėjas gauna laišką kaip iš admin'o. - Likučius iš ERP atnaujinkite per
POST /v1/inventory/bulk(viena užklausa – 100 ar 1 000 eilučių), kainas – perPOST /v1/variants/bulk. - Kartą per parą užklauskite
GET /v1/orders?updated_since=…, kad pagautumėte, ką galėjote praleisti per webhook'ų sutrikimą.
ERP įvesti užsakymai (telefoniniai ar B2B) į Mervio keliauja per POST /v1/orders: eilutės pagal variant_id, pirkėjas, adresai, shipping ir payment_method, kaip „Naujas užsakymas“ admin'e. Jei ERP jau nurašė likutį – "reserve_stock": false, be laiško pirkėjui – "notify": false, ir visada su Idempotency-Key.
Pakeitimai
- 2026-10-28 – v1. Pirma versija: parduotuvė, prekės, variantai, likučiai pagal saloną, salonai, kolekcijos, gamintojai, užsakymai (ir jų kūrimas), siuntos, grąžinimai, sąskaitos, klientai, nuolaidos, klientų grupės ir kainynai (Pro), webhook'ai.
Versija – adrese (/v1). Nauji laukai ir endpointai pridedami be naujos versijos; integracijas laužiantis pakeitimas gauna /v2, o /v1 veikia dar bent 12 mėn., apie tai pranešame čia ir antraštėmis Deprecation, Sunset.