Essentials
OpenAPI specs
Stable URLs, regenerated from the API source on every change.
Generate a typed client from these rather than hand-writing request code.
Roles decide what you can call
A team is a provider, a reseller, or hybrid, and the API key carries that role. Calling the wrong family of endpoints returns403, not 404.
- Provider →
/provider/*— publish offers, fulfill orders, manage markets, webhooks, and brands. - Reseller →
/reseller/*— browse the catalog, check address availability, place and track orders, manage links, customers, and webhooks. - Hybrid → both, with the same key.
- Public →
/public/*— no key at all.
Minimal working request
Gotchas
These are the things that most often make a first integration fail. None of them are inferable from the endpoint list. Unknown request fields are rejected. Sending a property an endpoint does not define returns400 with "property <name> should not exist". You cannot take
a response object and PATCH it back — send only the fields you are changing.
List endpoints are not paginated. They return a complete array. There are no
page, limit, offset, or cursor parameters, and adding one is a 400.
Filter server-side with the documented query parameters instead.
Money is a string, not a number. "monthlyPrice": "79.99". Parse with a
decimal library — parseFloat introduces rounding drift into customer-visible
totals.
message is not always a string. On body-validation failures it is an array
of strings. Normalize before displaying.
404 covers authorization on resources. Another team’s offer or order returns
404, not 403. 403 means your team role is wrong for that endpoint family.
There is no idempotency key. POST /reseller/orders is not idempotent — two
identical requests create two orders. If a write times out, reconcile with
GET /reseller/orders before retrying.
No rate limits on the authenticated API today. Only the two public /shop
write endpoints are limited (per IP, per minute). Handle 429 anyway; do not
build a tight polling loop.
Webhook signatures have no separate timestamp header. The timestamp is the
t= component of X-Offergrid-Signature, and the signed string is
`${t}.${rawBody}`. Sign the raw body — re-serializing breaks the
signature. See Verifying signatures.
Both roles have webhooks, on separate paths. POST /provider/webhooks and
POST /reseller/webhooks. Same four event types, same signed envelope — a
provider receives events scoped to its own offers, a reseller to the orders its
team placed. A hybrid team’s single webhook covers both and fires once per
event.
Offers carry a serviceType discriminator. Branch on it ("electricity",
"internet", …) rather than probing for the grouped electricity / internet
objects, which are absent when they do not apply.
Error handling
Full detail, including a retry-safe client: Errors.
Where to read next
Something unclear or wrong? Email support@offergrid.io.