Docs

Going live: errors, retries, pagination and rate limits

Get a Partner API integration ready for production: handle keys safely, read every error the same way, retry without duplicates, page through lists and stay under the rate limits.

Guide5 min readUpdated November 6, 2026

Your integration works. Before real customers depend on it, make sure it handles keys safely, reads errors the same way everywhere, retries without creating duplicates, pages through long lists and backs off when it's asked to.

The Partner API is in early beta. Calls marked available on the API reference are safe to build on, and we won't remove or rename a field on one without telling you first. Response times aren't under an SLA yet.

Keys in production

  • One key per integration. Name each one in Settings → API keys in the partner portal so you can tell them apart. An organization can have up to 20 active keys.
  • Only the scopes it needs. A reporting job that reads orders doesn't need orders:write. Scopes never include each other, so the list on a key is exactly what it can do.
  • Server-side only. Send the key as Authorization: Bearer … from your backend. Never ship it in a browser or mobile app — the API sends no CORS headers. See Authentication.
  • Know who it acts as. A key acts on behalf of the admin who created it, so the sales it makes are recorded under that person.
  • Plan for expiry. Keys last a year by default. To rotate one, create the new key, deploy it, then revoke the old one.

Call GET/v1/me when your service starts. It needs no scope, and it tells you the key works, which scopes it has and what your account is set up for under entitlements — so a misconfigured key fails at deploy, not halfway through a sale.

curl https://cloud.mergewifi.com/v1/me \
  -H "Authorization: Bearer $MERGEWIFI_API_KEY"

Read every error the same way

Every error, from every call, has one shape:

{
  "error": {
    "type": "permission_error",
    "code": "missing_scope",
    "message": "This API key is missing the `orders:write` scope.",
    "request_id": "req_4d00a64235084c1e85dda9c969709906"
  }
}
  • Branch on code. It's stable. message is for people and can change.
  • Fix the field in param. Validation errors name the field, like packages[0].plan.plan_id, one problem at a time.
  • Log request_id. Every response, success or failure, also carries it in the x-request-id header. Quote it when you contact us and we can find the exact request.
StatustypeCodes you'll seeWhat to do
400invalid_request_errorinvalid_requestFix the request. Don't retry it unchanged.
401authentication_errormissing_credentials, invalid_credentials, revoked_credentials, expired_credentialsCheck the key. A revoked or expired key needs replacing.
402payment_errorpayment_failedThe card was declined. Ask for another card.
403permission_errormissing_scope, not_entitled, organization_not_configured, organization_inactiveAdd the scope, or contact us about your account.
404not_found_errorresource_not_foundThe id is wrong, or not on your account.
409conflict_errordevice_unreachable, invalid_state_transition, billing_rejectedThe thing you're changing isn't in a state to allow it. Read it again before deciding.
429rate_limit_errorrate_limit_exceededWait for Retry-After, then retry.
500api_errorinternal_errorRetry later. If it keeps happening, send us the request_id.
503service_unavailable_errorservice_unavailableSomething upstream is briefly unavailable. Wait for Retry-After, then retry.

More in Errors.

Retry without duplicates

Networks drop requests. Here's what's safe to send again:

CallRetry-safe?
Any GETYes.
Opening a checkout sessionYes, with the same Idempotency-Key and the same body. You get the original session back.
Paying a checkout sessionRead the session first. Paying one that's already charged returns it as it is, so a retry can't charge twice.
Adding a port forwarding, dedicated IP or bridged IP ruleYes. An identical rule isn't added twice.
Taking a configuration backupYes. An extra backup does no harm.
Setting or cancelling a restart scheduleYes. A device has one schedule, and cancelling an empty one succeeds.
Device actions: restart, firmware, factory reset, activate, deactivate, testsNo duplicate protection. A retry is sent to the device again, so check the device first.

Opening a checkout session is the only call that takes an Idempotency-Key today, and it's required there. Use a fresh UUID per sale (up to 64 characters) and store it with the cart before you send. Reusing a key for a different basket is refused rather than handing you the wrong sale. See Retries and duplicates.

Page through long lists

Lists come in three styles. Every one uses the same envelope — data, has_more and next_cursor — so one parser handles them all.

  • Cursor pages: orders and devices. Pass limit (1 to 100, default 25). While has_more is true, send next_cursor back as starting_after. Cursors stay correct as new records arrive.
  • Numbered pages: customers. Pass page and limit, and add one to page while has_more is true. next_cursor is always null here.
  • Whole lists: everything else. Products, plans, data plans, network groups, rules and test results come back complete, with has_more set to false.

A limit outside 1–100 is rejected rather than quietly trimmed, so you never get fewer rows than you think. See Pagination.

Stay under the rate limits

Every response to a valid key tells you where you stand:

HeaderMeaning
X-RateLimit-LimitYour ceiling for the current window
X-RateLimit-RemainingRequests left in it
X-RateLimit-ResetWhen it resets, in Unix seconds
X-RateLimit-ResourceWhich limit these numbers describe: burst, key or org

By default a key can make 120 requests a minute, no more than 30 of them in any five seconds, and all your organization's keys share a ceiling of 600 a minute. The headers always show the limit you're closest to. Go over and you get a 429 with Retry-After in seconds — wait that long before trying again.

Requests with a bad key are throttled too, by IP address, so a broken key in a retry loop will soon start getting 429s. Fix the key rather than retrying. If your integration needs a higher ceiling, email support@mergewifi.com. See Rate limits.

Launch checklist

  • Your key has only the scopes it needs, and lives in your server's secrets, not your code.
  • Your service calls GET/v1/me on start-up and fails loudly if a scope is missing.
  • You log request_id on every failure.
  • Every checkout sends a fresh Idempotency-Key, and a timed-out payment reads the session before retrying.
  • Your retries wait for Retry-After and stop on 4xx errors.
  • Your syncs page with next_cursor (or page for customers) and stop when has_more is false.
  • Someone owns the key's expiry date.

Related: From a new key to your first order · Run your own checkout · Track orders and the devices they ship · API reference