Skip to content
MGP

Documentation

A working integration is a catalogue call, an order call and a webhook handler. This page is the whole thing; there is no second volume.

REST + JSONHMAC-signed webhooksDated versioningSandbox tenancy

Quickstart

Four calls end to end. Register a merchant, read the catalogue, place an order, handle the webhook. Everything else in this document is detail on those four.

Step 1. register a merchant
curl -X POST https://api.mgp.dev/v1/merchants \
  -H "Authorization: Bearer $MGP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_ref": "your-merchant-4412",
    "name": "Breadliner, Adajan",
    "category": "bakery",
    "locale": "gu-IN",
    "location": { "lat": 21.19, "lng": 72.79, "city": "Surat" }
  }'
Step 2. read the catalogue for that merchant
curl https://api.mgp.dev/v1/catalogue?merchant_ref=your-merchant-4412 \
  -H "Authorization: Bearer $MGP_KEY"

Authentication

Bearer keys, scoped to an environment and a tenancy. Sandbox keys are prefixed mgp_sb_ and live keys mgp_lv_, so a misconfigured environment fails loudly instead of quietly spending money.

Keys carry the API version they were issued against. Pin explicitly with MGP-Version: 2026-06-01 when you want to move deliberately.

Reading the catalogue

The catalogue is per tenancy and, optionally, per merchant. Pass a merchant_ref and MGP filters to what is actually available for that category and geography, with prices already carrying your margin.

GET /v1/catalogue
{
  "items": [
    {
      "id": "influencer-local-drop",
      "service": "influencer-marketing",
      "name": "Local creator drop",
      "summary": "3–6 creators inside a 5 km radius",
      "min_budget": { "amount": 1200000, "currency": "INR" },
      "delivery_days": 7,
      "inputs": [
        { "key": "radius_km", "type": "number", "min": 2, "max": 15 },
        { "key": "offer", "type": "text", "max_length": 120 },
        { "key": "avoid", "type": "text", "required": false }
      ]
    }
  ]
}

Amounts are integer minor units. Never render them by dividing in the client, every item carries its own currency.

Placing an order

An order is created in draft and only becomes real on submit. Both calls are idempotent on Idempotency-Key, so a retried request is never a second campaign.

POST /v1/orders
curl -X POST https://api.mgp.dev/v1/orders \
  -H "Authorization: Bearer $MGP_KEY" \
  -H "Idempotency-Key: 5f0c1a7e-order-4412-sep" \
  -d '{
    "merchant_ref": "your-merchant-4412",
    "item": "influencer-local-drop",
    "budget": { "amount": 1800000, "currency": "INR" },
    "inputs": { "radius_km": 5, "offer": "Buy 2 khari, get 1" }
  }'

Run your own rules before this call: spend ceilings, category blocks, approval chains. MGP will honour tenancy-level ceilings you have configured, but the order of authority is yours first.

Consuming webhooks

Two events: order.updated on every state change, and report.ready once a month per merchant. Both carry the complete object, so there is no follow-up fetch.

verify the signature
const sig = req.headers["mgp-signature"];      // t=1756982400,v1=9f86d0…
const [ts, v1] = parse(sig);
const expected = hmacSha256(secret, ts + "." + rawBody);

if (!timingSafeEqual(expected, v1)) return res.status(400).end();
if (Date.now() / 1000 - ts > 300) return res.status(400).end();  // replay window

handle(JSON.parse(rawBody));
res.status(200).end();

Respond 2xx within ten seconds and do the work asynchronously. Non-2xx responses are retried with exponential backoff for twenty-four hours, and events stay ordered per merchant so state never arrives backwards.

Embedding the storefront

Mint a short-lived session token on your server, then mount the storefront. It inherits your fonts, colours and radii from the theme object, and resizes itself rather than producing a nested scrollbar.

server
const { token } = await mgp.sessions.create({
  merchant_ref: "your-merchant-4412",
  locale: "gu-IN",
  return_url: "https://your-pos.app/marketing"
});
client
<script src="https://js.mgp.dev/v1/storefront.js"></script>
<div id="mgp-storefront"></div>
<script>
  MGP.mount("#mgp-storefront", {
    token: "sess_…",
    theme: {
      font: "Inter, system-ui",
      accent: "#0E5C4A",
      radius: 18
    }
  });
</script>

Errors and retries

Errors are JSON with a stable code, a human message, and, where the problem is fixable, a field. Do not parse messages; they are written for your engineers, not your code.

Common error codes
StatusCodeMeaning
400invalid_inputAn input failed the catalogue item's schema.
402ceiling_exceededThe order would breach a configured spend ceiling.
404merchant_unknownNo merchant with that merchant_ref in this tenancy.
409already_submittedSubmit called twice; the first one won.
422item_unavailableThat catalogue item is not available for this category or geography.
429rate_limitedBack off and retry after the Retry-After header.

Sandbox

The sandbox tenancy ships with fake merchants across every supported category and a lifecycle simulator: you can drive an order from submitted to reported in a minute, including the approval step and a failure path.

advance a sandbox order
curl -X POST https://api.mgp.dev/v1/sandbox/orders/ord_8Nq2f1/advance \
  -H "Authorization: Bearer $MGP_SANDBOX_KEY" \
  -d '{ "to": "awaiting_merchant" }'

Nothing in the sandbox spends money, contacts a creator or sends a WhatsApp message. Test the unhappy paths there (a rejected creative, a breached ceiling, a webhook you failed to acknowledge) before you go live.

Something missing?

These docs are written against the integrations we have actually shipped. If your stack does something ours did not anticipate, tell us. That is usually how a new endpoint gets justified.