Pereiti prie turinio
Mervio Beta

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.

  1. Parduotuvės admin'e įjunkite Nustatymai → Moduliai → API ir webhook'ai.
  2. 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ą.
  3. 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 pagal handle, užsakymą – pagal number, 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 2xx per 10 sekundžių, darbą atlikite po to. Dublikatus atpažinkite pagal antraštę webhook-id (lygi id). Tvarka negarantuojama: lyginkite data.updated_at ir 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}/redeliver arba „Pakartoti nepristatytus“ admin'e).
  • Masiniai pakeitimai: product.updated ir inventory.updated siunčiami daugiausia kartą per minutę prekei ar variantui, su naujausia būsena.
  • Bandymas: POST /v1/webhooks/{id}/test išsiunčia webhook.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

  1. ERP sistemai sukurkite parduotuvės raktą su orders:read, orders:write, catalog:read, catalog:write, webhooks:write (ir customers:read, jei ERP reikia pirkėjo kontaktų).
  2. Užregistruokite webhook'ą įvykiams order.paid, order.cancelled ir order.updated. Pavedimams ir grynaisiais – dar order.created.
  3. Gavę order.paid, sukurkite pardavimo užsakymą ERP pagal data (eilutės turi sku, quantity, unit_price, kiekviena – savo id). Išsaugokite ERP numerį: PATCH /v1/orders/{id} su {"external_id": "SO-2026-1042"}.
  4. Išvykus siuntai – POST /v1/orders/{id}/shipments su {"tracking_number": "LP123456789LT"} (daliniam išsiuntimui – lines). Pirkėjas gauna laišką kaip iš admin'o.
  5. Likučius iš ERP atnaujinkite per POST /v1/inventory/bulk (viena užklausa – 100 ar 1 000 eilučių), kainas – per POST /v1/variants/bulk.
  6. 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.