From a new key to your first order
Go from a brand-new API key to a paid order: check the key, pick hardware and a plan, price the basket, open a checkout session, take payment and read the order back.
Go from a brand-new API key to a paid order, one call at a time. It's the shortest path through the Partner API, and every other API guide builds on the same pieces.
The examples use curl against https://cloud.mergewifi.com, the base URL on the API reference. Responses are trimmed to the fields that matter for each step.
Create a key
API keys are made in the partner portal, under Settings → API keys. Only organization admins can create them, so if you don't see that page, ask an admin on your account.
- In the portal, go to Settings → API keys and click Create key.
- Give it a Name you'll recognize, like "Production integration".
- Choose when it Expires. A year is recommended; 90 days, 30 days and no expiry are there too.
- Tick the Scopes this guide needs:
catalog:read,quotes:write,orders:writeandorders:read. - Click Create key, copy the key somewhere safe, then click I've saved it.
Your key starts with mwf_live_. It's shown once and stored only as a hash, so if you lose it, revoke it and create another. Send it from your own server as a bearer token on every request — never from a browser or mobile app. The API sends no CORS headers, so browser calls won't work anyway. More in Authentication.
| Scope | What it unlocks in this guide |
|---|---|
catalog:read | Listing products and plans |
quotes:write | Pricing a basket |
orders:write | Opening and paying a checkout session, and fetching payment settings |
orders:read | Reading the session and the order back |
Check that it works
GET/v1/me needs no scope at all, so it's the quickest way to see that a key works and what it can do.
export MERGEWIFI_API_KEY="mwf_live_…"
curl https://cloud.mergewifi.com/v1/me \
-H "Authorization: Bearer $MERGEWIFI_API_KEY"{
"object": "api_key_context",
"environment": "live",
"scopes": ["catalog:read", "quotes:write", "orders:read", "orders:write"],
"entitlements": {
"modules": { "order": true, "customers": true, "devices": true, "services": false },
"order": { "customers": true }
}
}scopes is what the key may do. entitlements is what your account may do. A call needs both. Selling through checkout needs the Order module with retail sales on, which is entitlements.order.customers.
- A
401means the key is missing, mistyped, revoked or expired. - A
403withmissing_scopemeans the key needs another scope. - A
403withnot_entitledmeans your account isn't set up for that feature yet.
Choose and price the basket
Your catalog comes in two lists, each returned whole in one response:
- GET/v1/products is the hardware you can sell, at your pricing. Use a product's
idin the basket. - GET/v1/plans is the service plans you can sell with it, each with a
monthly_price. Use a plan'sid.
curl https://cloud.mergewifi.com/v1/products \
-H "Authorization: Bearer $MERGEWIFI_API_KEY"
curl https://cloud.mergewifi.com/v1/plans \
-H "Authorization: Bearer $MERGEWIFI_API_KEY"{
"object": "product",
"id": "1",
"sku": "M-11",
"name": "MergeWiFi Mini",
"equipment_as_a_service": false,
"lease": false
}Prices are decimal strings, like "199.99". A product marked equipment_as_a_service has no upfront charge and must be sold with a plan. One marked lease charges its price every month with the plan. Some products list variants, which are other billing versions of the same hardware, each with its own id.
Now price the basket with POST/v1/quotes. A basket is a list of packages: one device, an optional plan and any add-ons. package_id is your own label for each one. You never send prices — every amount comes from your catalog.
curl https://cloud.mergewifi.com/v1/quotes \
-X POST \
-H "Authorization: Bearer $MERGEWIFI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"packages": [
{
"package_id": "pkg_1",
"device": { "product_id": "1", "quantity": 1, "billing": "purchase", "source": "warehouse" },
"plan": { "plan_id": "12" },
"addons": []
}
],
"shipping_address": {
"line1": "100 Congress Ave",
"city": "Austin",
"state": "TX",
"postal_code": "78701"
}
}'{
"object": "quote",
"totals": {
"subtotal": "379.00",
"tax": "31.27",
"shipping": "12.60",
"due_now": "412.87",
"recurring_monthly": "49.99"
},
"lines": [
{ "name": "MergeWiFi M11 Router", "kind": "item", "amount": "379.00" },
{ "name": "Unlimited 5G", "kind": "activation", "amount": "49.99" }
]
}due_now is what the card would be charged today. Lines with kind: "activation" aren't in it: they start billing when the device is activated, which is how a plan on hardware shipped from the warehouse is billed. recurring_monthly is the monthly total once everything is active.
Open a checkout session
POST/v1/checkout_sessions turns the basket into a sale. It sets up the customer's billing account if they don't have one, builds the order and prices it with tax. Nothing is charged yet.
Send an Idempotency-Key header with a fresh UUID for each sale. If the request times out, send it again with the same key and the same body, and you get the original session back instead of a second sale. See Retries and duplicates.
curl https://cloud.mergewifi.com/v1/checkout_sessions \
-X POST \
-H "Authorization: Bearer $MERGEWIFI_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"first_name": "Dana",
"last_name": "Reyes",
"email": "dana@example.com",
"phone": "5125550142"
},
"shipping_address": {
"line1": "100 Congress Ave",
"city": "Austin",
"state": "TX",
"postal_code": "78701"
},
"packages": [
{
"package_id": "pkg_1",
"device": { "product_id": "1", "quantity": 1, "billing": "purchase", "source": "warehouse" },
"plan": { "plan_id": "12" },
"addons": []
}
]
}'{
"object": "checkout_session",
"id": "3b1e77c2-9a4d-4f81-8c2e-77a90b1d5e64",
"status": "awaiting_payment",
"totals": { "total": "412.87", "due_now": "412.87", "recurring_monthly": "49.99" }
}Keep the id. It's the session and the order: the same id works on the order calls later. totals.due_now is the exact amount you'll charge.
Take payment and read the order
Card details never touch your servers or ours. Your page collects the card with your payment gateway's browser SDK, using the publishable settings from GET/v1/payment_config, and gets back a single-use nonce. Run your own checkout walks through that part.
Then charge the session with POST/v1/checkout_sessions/{id}/actions/pay. expected_total is the session's totals.due_now, echoed back: if the total has moved, the charge is refused rather than billing a figure nobody agreed to.
curl https://cloud.mergewifi.com/v1/checkout_sessions/3b1e77c2-9a4d-4f81-8c2e-77a90b1d5e64/actions/pay \
-X POST \
-H "Authorization: Bearer $MERGEWIFI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"payment": {
"nonce": "eyJjb2RlIjoiNTBfMl8wNjAwMDUyN0…",
"data_descriptor": "COMMON.ACCEPT.INAPP.PAYMENT",
"payment_type_id": 7
},
"expected_total": "412.87"
}'{
"object": "checkout_session",
"id": "3b1e77c2-9a4d-4f81-8c2e-77a90b1d5e64",
"status": "submitted",
"payment_method": { "brand": "Visa", "last4": "4242" },
"submitted_at": "2026-08-26T18:42:10.000Z"
}submitted means the card was charged and the order is on its way to fulfillment. Read it back with GET/v1/orders/{id}, using the same id:
curl https://cloud.mergewifi.com/v1/orders/3b1e77c2-9a4d-4f81-8c2e-77a90b1d5e64 \
-H "Authorization: Bearer $MERGEWIFI_API_KEY"The order lists what was bought in line_items, along with its status, payment_status and totals.
That's a complete sale. From here, Run your own checkout covers declines, retries and cancelling, and Going live covers errors and rate limits before you ship.
Related: Run your own checkout · Track orders and the devices they ship · Going live · API reference

