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.
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.
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" }
}'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.
{
"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.
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.
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.
const { token } = await mgp.sessions.create({
merchant_ref: "your-merchant-4412",
locale: "gu-IN",
return_url: "https://your-pos.app/marketing"
});<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.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_input | An input failed the catalogue item's schema. |
| 402 | ceiling_exceeded | The order would breach a configured spend ceiling. |
| 404 | merchant_unknown | No merchant with that merchant_ref in this tenancy. |
| 409 | already_submitted | Submit called twice; the first one won. |
| 422 | item_unavailable | That catalogue item is not available for this category or geography. |
| 429 | rate_limited | Back 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.
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.