# Agent briefing Source: https://offergrid.io/docs/agents Everything an AI agent or code generator needs to call the Offergrid API correctly A single-page briefing for AI agents, code generators, and anyone building an integration in one sitting. Everything here is stated explicitly rather than implied, and every claim reflects the API as it is today. ## Essentials | | | | ----------------- | -------------------------------------------------------------------- | | **Base URL** | `https://api.offergrid.io` | | **Protocol** | HTTPS only | | **Auth** | `x-api-key: YOUR_TEAM_API_KEY` header on every authenticated request | | **Auth scheme** | API key in header. Not OAuth, not a bearer token, not Basic. | | **Content type** | `application/json` | | **Versioning** | Version 1. No version segment in the URL, no version header. | | **Docs index** | [`/docs/llms.txt`](https://offergrid.io/docs/llms.txt) | | **Full docs** | [`/docs/llms-full.txt`](https://offergrid.io/docs/llms-full.txt) | | **Markdown twin** | Append `.md` to any docs URL | ## OpenAPI specs Stable URLs, regenerated from the API source on every change. | Spec | URL | Auth | | -------- | ------------------------------------------------------------------------------------------------ | ----------- | | Provider | [`/docs/openapi/openapi-provider.json`](https://offergrid.io/docs/openapi/openapi-provider.json) | `x-api-key` | | Reseller | [`/docs/openapi/openapi-reseller.json`](https://offergrid.io/docs/openapi/openapi-reseller.json) | `x-api-key` | | Public | [`/docs/openapi/openapi-public.json`](https://offergrid.io/docs/openapi/openapi-public.json) | none | | Full | [`/docs/openapi/openapi.json`](https://offergrid.io/docs/openapi/openapi.json) | `x-api-key` | 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 returns `403`, 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. Full endpoint tables: [Provider](/docs/api-reference/provider) · [Reseller](/docs/api-reference/reseller) · [Public](/docs/api-reference/public) ## Minimal working request ```bash theme={null} curl https://api.offergrid.io/reseller/catalog \ -H "x-api-key: $OFFERGRID_API_KEY" ``` ```typescript theme={null} const response = await fetch('https://api.offergrid.io/reseller/catalog', { headers: { 'x-api-key': process.env.OFFERGRID_API_KEY! }, }); if (!response.ok) throw new Error(`Offergrid ${response.status}`); const offers = await response.json(); ``` ## 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 returns `400` with `"property 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](/docs/providers/webhooks#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 | Code | Retryable | Meaning | | ----- | --------- | ----------------------------------------------------------- | | `400` | No | Request body or query failed validation | | `401` | No | Missing, malformed, or revoked API key | | `403` | No | Valid key, wrong team role for this endpoint | | `404` | No | Not found, or not yours | | `409` | No | Conflicts with existing state (duplicate SKU or brand name) | | `429` | Yes | Rate limited — public endpoints only | | `500` | Yes | Server error; safe to retry idempotent requests | Full detail, including a retry-safe client: [Errors](/docs/api-reference/errors). ## Where to read next | If you want to | Read | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Make your first call | [Quickstart](/docs/quickstart) | | Understand keys and roles | [Authentication](/docs/api-reference/authentication) | | Know the shared rules | [API conventions](/docs/api-reference/conventions) | | Handle failures | [Errors](/docs/api-reference/errors) | | Receive events | [Provider webhooks](/docs/providers/webhooks) · [Reseller webhooks](/docs/resellers/webhooks) | | Know what can change | [Versioning](/docs/api-reference/versioning) | | See every endpoint | [Provider](/docs/api-reference/provider) · [Reseller](/docs/api-reference/reseller) · [Public](/docs/api-reference/public) | Something unclear or wrong? Email [support@offergrid.io](mailto:support@offergrid.io). # API Documentation Source: https://offergrid.io/docs/api-documentation Complete API reference and integration guides for engineering teams Everything engineering teams need to integrate Offergrid. Start with the quickstart, then work from the reference for your role. **Base URL** `https://api.offergrid.io` · **Auth** `x-api-key: YOUR_TEAM_API_KEY` · **Version** 1 **OpenAPI 3.x specs** — [Provider](/docs/openapi/openapi-provider.json) · [Reseller](/docs/openapi/openapi-reseller.json) · [Public](/docs/openapi/openapi-public.json) · [Full](/docs/openapi/openapi.json) **For agents** — [agents.md briefing](/docs/agents) · [llms.txt index](https://offergrid.io/docs/llms.txt) · [llms-full.txt](https://offergrid.io/docs/llms-full.txt) · append `.md` to any docs URL for raw markdown ## Getting Started Your first authenticated call, start to finish Base URL, roles, specs, and where to go next Issuing, rotating, and revoking keys; role behaviour; auth errors One page with everything needed to generate a correct integration ## Shared behaviour The rules every endpoint follows. Reading these first is the difference between a one-hour integration and a one-day one. List responses, identifiers, timestamps, money, and idempotency Every status code, what causes it, and a retry-safe client What is limited today, and what to build for What we promise not to break, and a dated record of what changed ## Provider API Every provider endpoint — offers, orders, markets, webhooks, brands Step-by-step guide to integrating as a service provider Signed, real-time order events — payloads, verification, and delivery behaviour Expose one endpoint for address-level availability and live pricing ## Reseller API Every reseller endpoint — catalog, availability, orders, links, customers Step-by-step guide to integrating as a reseller partner Signed, real-time events for the orders your team placed Find every offer sellable at a specific service address ## Public API Unauthenticated endpoints for shareable links and the consumer storefront ## Common Integration Tasks Create and publish service offers via API Search and filter available offers using the API Order webhooks and order data structure Submit orders programmatically via the API ## Resources Complete provider documentation and guides Complete reseller documentation and guides # API Authentication Source: https://offergrid.io/docs/api-reference/authentication Detailed guide to authenticating with the Offergrid API This is the detailed reference. For a first key and a first request, start with the [Authentication overview](/docs/authentication). ## Authentication Method All Offergrid API endpoints require authentication using a **Team API Key** passed in the request headers. ## API Key Header Include your API key in the `x-api-key` header with every request: ``` x-api-key: YOUR_TEAM_API_KEY ``` ## Example Requests ```bash cURL theme={null} curl -X GET https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_TEAM_API_KEY" ``` ```typescript TypeScript/JavaScript theme={null} const response = await fetch('https://api.offergrid.io/provider/offers', { headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, }, }); const offers = await response.json(); ``` ```python Python theme={null} import requests import os response = requests.get( 'https://api.offergrid.io/provider/offers', headers={ 'x-api-key': os.environ['OFFERGRID_API_KEY'] } ) offers = response.json() ``` ```ruby Ruby theme={null} require 'net/http' require 'json' uri = URI('https://api.offergrid.io/provider/offers') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request['x-api-key'] = ENV['OFFERGRID_API_KEY'] response = http.request(request) offers = JSON.parse(response.body) ``` ```go Go theme={null} package main import ( "fmt" "io/ioutil" "net/http" "os" ) func main() { client := &http.Client{} req, _ := http.NewRequest("GET", "https://api.offergrid.io/provider/offers", nil) req.Header.Add("x-api-key", os.Getenv("OFFERGRID_API_KEY")) resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```php PHP theme={null} ``` ## Team Roles and Permissions Your API key's permissions depend on your team's role: ### Provider Teams Access to every `/provider/*` endpoint: offers, orders, markets, webhooks, brands, and customers. See the [Provider API Reference](/docs/api-reference/provider) for the full list. ### Reseller Teams Access to every `/reseller/*` endpoint: catalog, address availability, orders, shareable links, and customers. See the [Reseller API Reference](/docs/api-reference/reseller) for the full list. ### Hybrid Teams Some teams have both provider AND reseller roles. Hybrid teams can access all endpoints with the same API key. ## Authentication Errors ### 401 Unauthorized The key is missing, malformed, or not recognized. The `message` tells you which: | `message` | Cause | | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | `API key required` | No `x-api-key` header on the request. | | `Invalid API key format. The API key value should be the actual key, not a header name.` | The header value starts with `x-` — usually the header name was sent as the value. | | `Invalid team API key` | The key does not match any team, or has been revoked. | ```json theme={null} { "statusCode": 401, "message": "Invalid team API key", "error": "Unauthorized" } ``` **Solution**: check the header name is exactly `x-api-key`, that the value is the key itself, and that the key has not been revoked. ### 403 Forbidden The key is valid, but your team's **role** does not cover this endpoint family. ```json theme={null} { "statusCode": 403, "message": "Provider access required. Your team does not have provider privileges.", "error": "Forbidden" } ``` The reseller equivalent reads `Reseller access required. Your team does not have reseller privileges.` **Solution**: call the endpoints your role allows, or contact [support@offergrid.io](mailto:support@offergrid.io) to have your team set up as hybrid. A `403` is about the *endpoint family*, not a specific record. Requesting another team's offer or order returns `404`, not `403` — see [Errors](/docs/api-reference/errors). ## Security Best Practices ### Store Keys Securely **✅ Good**: ```typescript theme={null} const apiKey = process.env.OFFERGRID_API_KEY; ``` **❌ Bad**: ```typescript theme={null} const apiKey = 'pk_live_abc123...'; // Never hardcode! ``` For production, use: * **AWS**: AWS Secrets Manager * **Azure**: Azure Key Vault * **GCP**: Secret Manager * **HashiCorp**: Vault * **1Password**: 1Password CLI Generate new API keys periodically and revoke old ones: * Every 90 days for active keys * Immediately if compromised * When team members leave ### Use HTTPS Only `https://api.offergrid.io` is the only base URL. Plain HTTP is not served, and there is no alternate hostname — anything else is not Offergrid. ### Don't Expose Keys Client-Side Never include API keys in: * Frontend JavaScript code * Mobile app binaries * Public repositories * Client-side API calls Make API calls from your backend server only. ### Separate Keys by Environment Use different API keys for: * **Development**: Testing and development work * **Staging**: Pre-production testing * **Production**: Live customer transactions This limits the impact of compromised keys. ## Rate Limiting The authenticated Provider and Reseller APIs are **not rate limited today** — there is no quota, no `X-RateLimit-*` header, and no `429` on any `/provider/*` or `/reseller/*` endpoint. The unauthenticated `/public/shop/*` write endpoints are limited per IP. That is a property of the current stage, not a guarantee. Handle `429` in your client anyway, and do not build a tight polling loop. See [Rate limits](/docs/api-reference/rate-limits) for exactly what is enforced and what we will do before introducing limits here. ## Managing API Keys ### Generating Keys 1. Sign in to [offergrid.io](https://offergrid.io) 2. Navigate to **Settings** → **API Keys** 3. Click **Generate New Key** 4. **Copy immediately** - you won't see it again! 5. Store securely in environment variables or secrets manager ### Revoking Keys If a key is compromised: 1. Go to **Settings** → **API Keys** 2. Find the compromised key 3. Click **Revoke** 4. Generate a new key 5. Update your applications Revoked keys return `401 Unauthorized` immediately. ### Key Naming Give keys descriptive names: * `Production API Key` * `Staging Environment` * `Development - John's Laptop` * `CI/CD Pipeline` This helps identify which key to revoke if needed. ## Troubleshooting ### "Invalid API key" error **Check**: 1. Header name is exactly `x-api-key` (lowercase, with hyphen) 2. API key was copied correctly (no extra spaces) 3. API key hasn't been revoked 4. Request is going to correct base URL ### "Forbidden" error **Check**: 1. Team has the correct role (provider or reseller) 2. Endpoint matches team role 3. Account is active and verified ### Keys not working in production **Check**: 1. Using production API key (not development key) 2. Environment variables set correctly 3. Key has proper permissions 4. Not hitting rate limits ## Next Steps Every provider endpoint Every reseller endpoint Every status code and a retry-safe client What is limited, and what is not # Changelog Source: https://offergrid.io/docs/api-reference/changelog Dated record of changes to the Offergrid API Every change to the public API — new endpoints, new fields, corrected documentation, and any deprecation — is recorded here, newest first. See [Versioning](/docs/api-reference/versioning) for what counts as a breaking change and how deprecations work. This changelog starts on 2026-08-28. Changes made before that date are not listed individually; the [OpenAPI specs](/docs/openapi/openapi-provider.json) are the authoritative record of the current surface. **Added — reseller webhooks** Resellers can now register webhook endpoints and receive order events for the orders their team placed, at [`POST /reseller/webhooks`](/docs/reseller-api-reference/reseller-webhooks/register-a-webhook) and the usual list/get/update/delete/deliveries operations alongside it. The contract is identical to the provider side — same four subscribable event types (`order.created`, `order.item.created`, `order.item.status_changed`, `order.cancelled`), same `X-Offergrid-Signature` scheme, same `{ id, type, version, data }` envelope, same at-least-once delivery and delivery log. What differs is scoping: a provider receives events for items built on its own offers, a reseller for orders its own team placed. A **hybrid** team's single webhook now covers both roles and receives each event exactly once, rather than twice. This is additive. Existing provider webhooks are unaffected: no provider endpoint, payload, or subscription changes, and no provider begins receiving an event type it did not subscribe to. See [Reseller webhooks](/docs/resellers/webhooks) for the full guide. **Fixed — webhook signature verification docs were wrong** The provider webhooks guide described an `x-offergrid-timestamp` request header that the API has never sent. The timestamp is carried in the `t=` component of `X-Offergrid-Signature`. Any verifier written against the old documentation would have rejected every delivery. The [verification examples](/docs/providers/webhooks#verifying-signatures) are now tested against the signing implementation. The same page also corrected the webhook envelope (the event type field is `type`, not `event`; there is a `version` field and no top-level `timestamp`), the subscribable event list, and the delivery timeout (10 seconds, not 5). **Fixed — reseller webhooks were documented but did not exist** The reseller webhooks page described a registration flow and four event types that had no implementation at the time. The page was corrected to describe polling instead — and reseller webhooks then shipped the same day (see the entry above), so the [page](/docs/resellers/webhooks) now documents the real endpoints. **Fixed — rate limits were documented but not enforced** The authentication guide published a burst limit, a sustained limit, and `X-RateLimit-*` response headers, none of which the API implements. See [Rate limits](/docs/api-reference/rate-limits) for what is genuinely enforced: the two public `/shop` write endpoints, per IP. **Changed — OpenAPI specs now document error responses** Every operation in the published specs now declares `401`, `403`, and `500` alongside its documented error cases, and every `4xx`/`5xx` response carries a schema rather than a bare description. Two new component schemas, `ErrorResponse` and `ValidationErrorResponse`, model the shapes described on the [Errors](/docs/api-reference/errors) page. Five operations that documented only a `404` now declare their success response as well. **Removed — `http://localhost:3000` from the published specs** The specs listed a local development server that a generated client or agent could select. Production is now the only server entry; local development is described in prose on the [Introduction](/docs/api-reference/introduction) page. **Added — new documentation pages** [Errors](/docs/api-reference/errors), [Rate limits](/docs/api-reference/rate-limits), [API conventions](/docs/api-reference/conventions), [Versioning](/docs/api-reference/versioning), this changelog, and per-role reference indexes for the [Provider](/docs/api-reference/provider), [Reseller](/docs/api-reference/reseller), and [Public](/docs/api-reference/public) APIs. # API conventions Source: https://offergrid.io/docs/api-reference/conventions List responses, identifiers, timestamps, money, and filtering — the rules every endpoint follows Conventions that hold across the whole API. Individual endpoints document what is specific to them; everything here is assumed. ## Requests | | | | ------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **Base URL** | `https://api.offergrid.io` | | **Protocol** | HTTPS only | | **Auth header** | `x-api-key: YOUR_TEAM_API_KEY` on every authenticated request — see [Authentication](/docs/api-reference/authentication) | | **Request bodies** | JSON. Send `Content-Type: application/json`. | | **Partial updates** | `PATCH` with only the fields you want changed. Omitted fields are left alone. | **Unknown fields are rejected, not ignored.** Sending a property the endpoint does not define returns `400` with `"property should not exist"`. This turns a typo into an immediate error instead of a silently dropped value — but it also means you cannot round-trip a response object straight back into a `PATCH`. Send only the fields you intend to change. ## List responses **List endpoints are not paginated.** They return a complete JSON array of every matching record your team can see. There are no `page`, `limit`, `offset`, or `cursor` parameters, and no pagination envelope. ```json theme={null} [ { "id": "1a2b3c4d-...", "name": "High-Speed Internet 1000 Mbps" }, { "id": "5e6f7a8b-...", "name": "Fiber 500" } ] ``` This is fine at current catalog and order volumes and keeps clients simple. It does mean a list response grows with your data, so: * **Filter server-side where you can.** `GET /reseller/catalog` accepts `category`, `minPrice`, `maxPrice`, `zipCode`, and `search`; narrowing there is far cheaper than fetching everything and filtering locally. * **Do not assume a bounded response size** in your client — no fixed buffers, no hard-coded array-length expectations. * **Prefer the detail endpoint** when you already know the id. `GET /reseller/orders/{id}` beats scanning `GET /reseller/orders`. Pagination will be added additively — an opt-in query parameter with the unpaginated array as the default response — so it does not break existing clients. It will be announced in the [changelog](/docs/api-reference/changelog) before it ships. If unbounded lists are already a problem for you, tell us at [support@offergrid.io](mailto:support@offergrid.io). ## Filtering Filters are query parameters, and they combine with AND. An unknown query parameter is rejected with `400`, the same as an unknown body field. ```bash theme={null} curl -G https://api.offergrid.io/reseller/catalog \ -H "x-api-key: YOUR_TEAM_API_KEY" \ --data-urlencode "category=internet" \ --data-urlencode "zipCode=94102" \ --data-urlencode "maxPrice=100" \ --data-urlencode "search=fiber" ``` ## Identifiers Every resource id is a **UUID v4** string. ``` 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d ``` Treat them as opaque: do not parse them, infer type or ordering from them, or assume any length other than 36 characters. Ids are stable for the life of the resource. The one exception is a shareable link's `slug`, which appears in public URLs and is a short human-shareable token rather than a UUID. ## Timestamps All timestamps are **ISO 8601 in UTC**, with milliseconds and a trailing `Z`. ```json theme={null} { "createdAt": "2026-08-28T12:00:00.000Z" } ``` Send them in the same format. Date-only fields — a move-in date, an installation date — are calendar dates with no time component and no timezone; do not convert them through a local timezone or they can shift by a day. ## Money Monetary values are returned as **strings**, not numbers. ```json theme={null} { "monthlyPrice": "79.99", "promoMonthlyPrice": "49.99", "totalMonthly": "129.98" } ``` Parse money with a decimal library, not `parseFloat`. These are exact decimal values; binary floating point cannot represent them exactly, and rounding drift on prices and totals shows up in customer-visible numbers. Strings are used precisely so no precision is lost in transport. All amounts are in **USD**, monthly, and before taxes and fees unless the offer states otherwise. Prices on an order are snapshotted at order time — an offer whose price changes later does not retroactively change existing orders. ## Nulls and optional fields A field that does not apply is `null` rather than absent, so response shapes stay stable. Two exceptions are genuinely conditional and absent when they do not apply: * `electricity` — present only on electricity offers * `internet` — present only on internet offers Every offer carries a top-level `serviceType` discriminator (`"electricity"`, `"internet"`, …), so branch on that rather than probing for the grouped object. Responses are **additive over time**. New fields can appear in any response without notice, so parse permissively and ignore what you do not recognize — a strict parser that rejects unknown response fields will break on a routine release. See [Versioning](/docs/api-reference/versioning). ## Idempotency There is no `Idempotency-Key` header today. `POST /reseller/orders` is not idempotent: two identical requests create two orders. If a write times out without a response, **reconcile before retrying** — `GET /reseller/orders` will tell you whether the first attempt landed. See [Handling errors](/docs/api-reference/errors#handling-errors). Reads, `PATCH`, and `DELETE` are naturally idempotent and safe to retry. ## Next steps Every status code and a retry-safe client What is limited, and what is not What we promise not to break Keys, headers, roles, and rotation # Errors Source: https://offergrid.io/docs/api-reference/errors Every error the Offergrid API returns, what causes it, and how to recover Offergrid uses conventional HTTP status codes. A `2xx` means the request succeeded, a `4xx` means something about the request needs fixing, and a `5xx` means the failure was ours. ## Error format Almost every error returns the same envelope: ```json theme={null} { "statusCode": 404, "message": "Offer not found", "error": "Not Found" } ``` | Field | Type | Description | | ------------ | ------------------- | ---------------------------------------------------------------------------------------------------- | | `statusCode` | integer | The HTTP status, repeated in the body. | | `message` | string \| string\[] | What went wrong. An **array** when request-body validation failed — one entry per failed constraint. | | `error` | string | Short, stable name for the status code. | `message` is not always a string. Request-body validation failures return an array, and two endpoints return a richer shape (see [Validation errors](#validation-errors) below). Normalize before displaying: `Array.isArray(body.message) ? body.message.join('; ') : body.message`. ## Status codes | Code | Meaning | What to do | | ----- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | `400` | The request body failed validation. | Fix the request. Do not retry unchanged. | | `401` | Missing, malformed, or revoked API key. | Check the `x-api-key` header. Do not retry unchanged. | | `403` | Valid key, wrong role for this endpoint. | Use an endpoint your team's role allows, or have your team's role updated. | | `404` | The resource does not exist, or is not yours. | Check the id. Note that another team's resource returns 404, not 403. | | `409` | The request conflicts with existing state — usually a duplicate SKU or brand name. | Change the conflicting value, or update the existing record instead. | | `429` | Rate limited. Only reachable on the [public endpoints](/docs/api-reference/rate-limits). | Back off and retry. | | `500` | Something failed on our side. | Retry with exponential backoff. Safe for idempotent requests. | **404 is used for authorization failures on resources.** Every provider and reseller endpoint is scoped to your team, so requesting an offer or order belonging to another team returns `404 Not Found` rather than `403 Forbidden`. This is deliberate — a 403 would confirm the resource exists. `403` means something different: your API key is valid but your *team role* does not grant access to that whole class of endpoint — a reseller team calling `/provider/*`, or vice versa. ## Validation errors ### Request-body validation Every endpoint that accepts a body validates it before any work happens. Unknown properties are rejected rather than ignored, so a typo in a field name is a `400`, not a silently dropped value. ```json theme={null} { "statusCode": 400, "message": [ "name should not be empty", "category must be one of the following values: internet, electricity", "property monthlyPrce should not exist" ], "error": "Bad Request" } ``` Each entry names the offending field first, so they can be mapped back to form fields by prefix. ### Publish validation [`POST /provider/offers/{id}/publish`](/docs/provider-api-reference/provider-offers/publish-an-offer) checks an offer against the publish-readiness rules, which are richer than field-level validation. Its `400` carries a different, structured shape: ```json theme={null} { "error": "Validation failed", "message": "Please fix 2 validation issues before publishing.", "validationErrors": [ { "section": "pricing", "field": "monthlyPrice", "message": "Monthly price is required" }, { "section": "internetDetails", "field": "downloadSpeedMbps", "message": "Download speed is required for internet offers" } ], "sections": {} } ``` | Field | Description | | ---------------------------- | ------------------------------------------------------------------- | | `validationErrors` | Flat list — one entry per unmet publish rule. | | `validationErrors[].section` | The editor section the field belongs to. | | `validationErrors[].field` | Dot-delimited path to the field. | | `sections` | The same errors grouped by section, for rendering inline in a form. | This response has no `statusCode` field — the status is on the HTTP response only. Branch on the HTTP status, not on the presence of `statusCode` in the body. The CSV bulk endpoints (`/provider/offers/bulk-upload`, `/bulk-update`, and their `/validate` variants) report per-row problems in their `200`/`201` body rather than as an error — a partially-valid file is a successful request with a results breakdown, not a failure. ## Handling errors Retry only what is retryable. A `400`, `401`, `403`, `404`, or `409` will return the same result no matter how many times you send it; retrying wastes your budget and ours. ```typescript theme={null} const RETRYABLE = new Set([429, 500, 502, 503, 504]); async function callOffergrid(path: string, init: RequestInit = {}, maxAttempts = 4) { for (let attempt = 1; ; attempt++) { const response = await fetch(`https://api.offergrid.io${path}`, { ...init, headers: { ...init.headers, 'x-api-key': process.env.OFFERGRID_API_KEY! }, }); if (response.ok) return response.json(); if (!RETRYABLE.has(response.status) || attempt === maxAttempts) { const body = await response.json().catch(() => ({})); const detail = Array.isArray(body.message) ? body.message.join('; ') : body.message; throw new Error(`Offergrid ${response.status}: ${detail ?? response.statusText}`); } // Honor Retry-After when present, otherwise exponential backoff with jitter. const retryAfter = Number(response.headers.get('retry-after')); const delayMs = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 250 + Math.random() * 250; await new Promise((resolve) => setTimeout(resolve, delayMs)); } } ``` **Non-idempotent writes need care.** `POST /reseller/orders` creates an order; a blind retry after a timeout can create a second one. If a write times out without a response, reconcile with [`GET /reseller/orders`](/docs/reseller-api-reference/reseller-orders/list-your-orders) before retrying rather than sending it again. ## Next steps What is limited, and what is not List responses, identifiers, timestamps, and money Keys, headers, roles, and rotation How the API changes, and what we promise not to break # Introduction Source: https://offergrid.io/docs/api-reference/introduction Base URL, authentication, roles, and where to go next in the Offergrid API The Offergrid API is a B2B marketplace API connecting service providers with reseller partners, streamlining the distribution and sale of essential services like internet and electricity. ## Quick facts | | | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Base URL** | `https://api.offergrid.io` | | **Protocol** | HTTPS only | | **Auth** | `x-api-key: YOUR_TEAM_API_KEY` header on every authenticated request | | **Format** | JSON request and response bodies | | **Version** | 1 — no version segment in the URL, no version header | | **Specs** | [Provider](/docs/openapi/openapi-provider.json) · [Reseller](/docs/openapi/openapi-reseller.json) · [Public](/docs/openapi/openapi-public.json) · [Full](/docs/openapi/openapi.json) | ## Your first request ```bash theme={null} curl https://api.offergrid.io/reseller/catalog \ -H "x-api-key: YOUR_TEAM_API_KEY" ``` A `200` with a JSON array means you are connected and authenticated. A `401` means the key or header is wrong; a `403` means the key is fine but your team's role does not cover that endpoint. See [Errors](/docs/api-reference/errors). ## What you can do Which half of the API you use depends on your team's role. The role is carried by the API key — there is nothing else to configure. Publish and manage service offers, define market coverage, receive and fulfill orders, and register webhooks. Browse the catalog, check what is available at a service address, place orders for customers, and track fulfillment. Unauthenticated endpoints powering shareable reseller links and the consumer storefront. No API key. A single page with everything needed to generate a correct integration. **Hybrid teams** hold both roles and reach every endpoint with the same key. ## Before you build Four pages that will save you a debugging session each: Issuing, rotating, and revoking keys; role behaviour; auth errors List responses, identifiers, timestamps, money, and idempotency Every status code, what causes it, and a retry-safe client What we promise not to break, and how deprecations work ## Local development `https://api.offergrid.io` is the only Offergrid-hosted environment — there is no separate sandbox host, and no test-mode key. If you want an environment to experiment against, run the API locally from the monorepo; it listens on `http://localhost:3000` by default and speaks the same contract as production. Because there is no sandbox, requests against `api.offergrid.io` are real: orders you create are real orders, and they notify real providers. Use a dedicated team for integration testing, and delete test data when you are done. ## Getting an API key Sign in at [offergrid.io](https://offergrid.io), then **Settings → API Keys → Generate New Key**. The key is shown once — store it in a secrets manager or environment variable immediately. Keep your API key server-side. Never put it in client-side code, a mobile binary, or a repository. Treat it like a password. # Provider API Reference Source: https://offergrid.io/docs/api-reference/provider Every provider endpoint — offers, orders, markets, webhooks, brands, and customers The Provider API is how a service provider publishes offers and fulfills the orders resellers place against them. Every endpoint below links to its full reference page with parameters, schemas, and a request builder. **Base URL** `https://api.offergrid.io` **Authentication** `x-api-key: YOUR_TEAM_API_KEY` on every request — see [Authentication](/docs/api-reference/authentication). **OpenAPI spec** [`openapi-provider.json`](/docs/openapi/openapi-provider.json) ## Provider Brands | Endpoint | Method | Path | What it does | | ----------------------------------------------------------------------------------------------------------- | -------- | ----------------------- | ------------------------------------------------------------------------------ | | [Create a brand](/docs/provider-api-reference/provider-brands/create-a-brand) | `POST` | `/provider/brands` | Brands are the visual identity offers display under instead of your team name. | | [List all brands for your team](/docs/provider-api-reference/provider-brands/list-all-brands-for-your-team) | `GET` | `/provider/brands` | | | [Get a brand by id](/docs/provider-api-reference/provider-brands/get-a-brand-by-id) | `GET` | `/provider/brands/{id}` | | | [Update a brand](/docs/provider-api-reference/provider-brands/update-a-brand) | `PATCH` | `/provider/brands/{id}` | Renaming or re-logoing a brand changes it for every offer displaying it. | | [Delete an unused brand](/docs/provider-api-reference/provider-brands/delete-an-unused-brand) | `DELETE` | `/provider/brands/{id}` | Only brands no offer references can be deleted. | ## Provider Customers | Endpoint | Method | Path | What it does | | ------------------------------------------------------------------------------------------ | ------ | ---------------------------------- | ------------------------------------------------------------------------------------- | | [List customers](/docs/provider-api-reference/provider-customers/list-customers) | `GET` | `/provider/customers` | Customers are people who have placed an order for one of your offers. | | [Get customer detail](/docs/provider-api-reference/provider-customers/get-customer-detail) | `GET` | `/provider/customers/{customerId}` | Detail view of a customer including the order items they have placed for your offers. | ## Provider Markets | Endpoint | Method | Path | What it does | | -------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------- | ----------------------- | | [Create a market](/docs/provider-api-reference/provider-markets/create-a-market) | `POST` | `/provider/markets` | | | [List all markets for your team](/docs/provider-api-reference/provider-markets/list-all-markets-for-your-team) | `GET` | `/provider/markets` | | | [Get a market by id](/docs/provider-api-reference/provider-markets/get-a-market-by-id) | `GET` | `/provider/markets/{id}` | | | [Update a market](/docs/provider-api-reference/provider-markets/update-a-market) | `PATCH` | `/provider/markets/{id}` | | | [Delete a market](/docs/provider-api-reference/provider-markets/delete-a-market) | `DELETE` | `/provider/markets/{id}` | | | [Add a geographic area to a market](/docs/provider-api-reference/provider-markets/add-a-geographic-area-to-a-market) | `POST` | `/provider/markets/{id}/areas` | Areas compose a market. | | [Remove an area from a market](/docs/provider-api-reference/provider-markets/remove-an-area-from-a-market) | `DELETE` | `/provider/markets/{id}/areas/{areaId}` | | ## Provider Offers Provider: Manage your service offerings | Endpoint | Method | Path | What it does | | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | [Create a new offer](/docs/provider-api-reference/provider-offers/create-a-new-offer) | `POST` | `/provider/offers` | Create a new service offering as a provider. | | [List all your offers](/docs/provider-api-reference/provider-offers/list-all-your-offers) | `GET` | `/provider/offers` | Retrieve all offers created by your provider team. | | [Get a specific offer](/docs/provider-api-reference/provider-offers/get-a-specific-offer) | `GET` | `/provider/offers/{id}` | Retrieve details of a specific offer you created. | | [Update an offer](/docs/provider-api-reference/provider-offers/update-an-offer) | `PATCH` | `/provider/offers/{id}` | Update an existing offer. | | [Delete an offer](/docs/provider-api-reference/provider-offers/delete-an-offer) | `DELETE` | `/provider/offers/{id}` | Permanently delete an offer. | | [Publish an offer](/docs/provider-api-reference/provider-offers/publish-an-offer) | `POST` | `/provider/offers/{id}/publish` | Validate an offer against the publish-readiness rules and, if it passes, set its status to active. | | [Duplicate an offer](/docs/provider-api-reference/provider-offers/duplicate-an-offer) | `POST` | `/provider/offers/{id}/duplicate` | Create a new draft offer pre-filled from an existing one. | | [Download CSV template for bulk offer upload](/docs/provider-api-reference/provider-offers/download-csv-template-for-bulk-offer-upload) | `GET` | `/provider/offers/bulk-upload/template` | Download a CSV template file with headers and an example row. | | [Bulk upload offers from CSV](/docs/provider-api-reference/provider-offers/bulk-upload-offers-from-csv) | `POST` | `/provider/offers/bulk-upload` | Upload a CSV file to create multiple offers at once. | | [Validate CSV file without creating offers](/docs/provider-api-reference/provider-offers/validate-csv-file-without-creating-offers) | `POST` | `/provider/offers/bulk-upload/validate` | Upload a CSV file to validate its structure and data without actually creating offers. | | [Bulk update offers from CSV](/docs/provider-api-reference/provider-offers/bulk-update-offers-from-csv) | `POST` | `/provider/offers/bulk-update` | Upload a CSV file to update multiple existing offers at once, using the same template as bulk upload (GET /bulk-upload/template). | | [Validate a bulk-update CSV without updating offers](/docs/provider-api-reference/provider-offers/validate-a-bulk-update-csv-without-updating-offers) | `POST` | `/provider/offers/bulk-update/validate` | Upload a CSV file to validate it against the bulk-update rules without writing anything. | ## Provider Orders Provider: Fulfill orders from resellers | Endpoint | Method | Path | What it does | | ------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------- | ------------------------------------------------------------------------------------------ | | [List order items to fulfill](/docs/provider-api-reference/provider-orders/list-order-items-to-fulfill) | `GET` | `/provider/orders` | Retrieve all order items for your offers that need fulfillment. | | [Get order item details](/docs/provider-api-reference/provider-orders/get-order-item-details) | `GET` | `/provider/orders/{itemId}` | Retrieve detailed information about a specific order item for fulfillment. | | [Update order item status](/docs/provider-api-reference/provider-orders/update-order-item-status) | `PATCH` | `/provider/orders/{itemId}/status` | Update the fulfillment status of an order item (e.g., accept, reject, schedule, complete). | ## Provider Webhooks Provider: Receive order events for your offers | Endpoint | Method | Path | What it does | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------ | ---------------------------------------------------------------------- | | [Register a webhook](/docs/provider-api-reference/provider-webhooks/register-a-webhook) | `POST` | `/provider/webhooks` | Register an HTTPS endpoint to receive signed order-event deliveries. | | [List your registered webhooks](/docs/provider-api-reference/provider-webhooks/list-your-registered-webhooks) | `GET` | `/provider/webhooks` | Secrets are masked — the full value is only ever returned at creation. | | [Get a webhook by id](/docs/provider-api-reference/provider-webhooks/get-a-webhook-by-id) | `GET` | `/provider/webhooks/{id}` | | | [Update a webhook (url, subscribed events, or active state)](/docs/provider-api-reference/provider-webhooks/update-a-webhook-url-subscribed-events-or-active-state) | `PATCH` | `/provider/webhooks/{id}` | | | [Delete a webhook](/docs/provider-api-reference/provider-webhooks/delete-a-webhook) | `DELETE` | `/provider/webhooks/{id}` | | | [List recent delivery attempts for a webhook](/docs/provider-api-reference/provider-webhooks/list-recent-delivery-attempts-for-a-webhook) | `GET` | `/provider/webhooks/{id}/deliveries` | | ## Conventions Every endpoint on this page follows the shared [API conventions](/docs/api-reference/conventions) for list responses, identifiers, timestamps, and money, and the shared [error format](/docs/api-reference/errors). # Public API Reference Source: https://offergrid.io/docs/api-reference/public Unauthenticated endpoints powering shareable reseller links and the /shop storefront The Public API backs the two surfaces an end customer can reach without an account: a reseller's shareable link, and the consumer storefront at offergrid.io/shop. No API key is required, and every endpoint re-checks coverage server-side. **Base URL** `https://api.offergrid.io` **Authentication** None. These endpoints are deliberately unauthenticated. **OpenAPI spec** [`openapi-public.json`](/docs/openapi/openapi-public.json) ## Public Public: Unauthenticated endpoints for shareable links and the /shop storefront | Endpoint | Method | Path | What it does | | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Get link details](/docs/public-api-reference/public/get-link-details) | `GET` | `/public/links/{slug}` | Retrieve public information about a shareable link, including the service address and property name. | | [Get available offers for a link](/docs/public-api-reference/public/get-available-offers-for-a-link) | `GET` | `/public/links/{slug}/offers` | Retrieve all service offers available for the address associated with this link. | | [Submit an order via a shareable link](/docs/public-api-reference/public/submit-an-order-via-a-shareable-link) | `POST` | `/public/links/{slug}/orders` | Create a new order for the selected service offers. | | [Browse consumer-enabled offers](/docs/public-api-reference/public/browse-consumer-enabled-offers) | `GET` | `/public/shop/offers` | Returns active, consumer-enabled offers, optionally filtered by ZIP/city/state (coverage-checked through the offer's markets) and category. | | [Get a consumer offer by its public slug](/docs/public-api-reference/public/get-a-consumer-offer-by-its-public-slug) | `GET` | `/public/shop/offers/{publicSlug}` | Full pricing/compliance detail for one consumer-enabled offer. | | [Check address-level serviceability for on-screen offers](/docs/public-api-reference/public/check-address-level-serviceability-for-on-screen-offers) | `POST` | `/public/shop/serviceability` | Given a full street address, returns per-offer serviceability + exact price for offers whose markets reference an external serviceability source. | | [Place a checkout-mode shop order](/docs/public-api-reference/public/place-a-checkout-mode-shop-order) | `POST` | `/public/shop/orders` | Creates an order through the canonical transactional path (idempotent, snapshotted, outbox-emitting), attributed to the house reseller team. | | [Record an outbound shop click](/docs/public-api-reference/public/record-an-outbound-shop-click) | `POST` | `/public/shop/clicks` | Logs a click for a consumer offer — a lead\_gen click returns a redirectUrl (the provider URL with Offergrid attribution merged on); a checkout-mode click is … | ## Conventions Every endpoint on this page follows the shared [API conventions](/docs/api-reference/conventions) for list responses, identifiers, timestamps, and money, and the shared [error format](/docs/api-reference/errors). # Rate limits Source: https://offergrid.io/docs/api-reference/rate-limits What is rate limited on the Offergrid API today, and what to build for ## The short version **The authenticated Provider and Reseller APIs are not rate limited today.** There is no request quota, no `X-RateLimit-*` header, and no `429` on any `/provider/*` or `/reseller/*` endpoint. The unauthenticated `/public/shop/*` write endpoints **are** limited, per IP. We would rather tell you exactly what is enforced than publish limits we do not apply. This page will change when that changes — see [Versioning](/docs/api-reference/versioning) for how we announce it. ## What is limited | Endpoint | Limit | Scope | | ------------------------------------------------------------------------------------------------ | -------------------- | ------------- | | [`POST /public/shop/clicks`](/docs/public-api-reference/public/record-an-outbound-shop-click) | 30 requests / minute | Per client IP | | [`POST /public/shop/orders`](/docs/public-api-reference/public/place-a-checkout-mode-shop-order) | 10 requests / minute | Per client IP | Both use a sliding one-minute window. Exceeding the window returns: ```json theme={null} { "statusCode": 429, "message": "Too many requests" } ``` There is no `Retry-After` header on these responses. Retry after the window has moved — one minute is always sufficient. These endpoints back the consumer storefront at `offergrid.io/shop`, where a real shopper places one order. The limits exist to cap scripted submission volume, and are set well above anything legitimate browsing produces. ## What is not limited Every endpoint requiring an `x-api-key` header — the whole Provider and Reseller API — is currently unmetered. Your integration will not receive a `429` from them. That is a deliberate choice for the current stage, not an oversight, and not a guarantee. **Do not build an integration that depends on it.** ## Build for limits anyway Two habits cost nothing now and mean you need no changes when limits arrive: **Handle `429` in your client.** Treat it as retryable with backoff, honoring `Retry-After` when present. The [error-handling helper](/docs/api-reference/errors#handling-errors) on the Errors page already does both. **Do not poll faster than your data changes.** Order fulfillment moves on human timescales — a provider accepting an order, scheduling an installation. Polling [`GET /reseller/orders`](/docs/reseller-api-reference/reseller-orders/list-your-orders) every 5–15 minutes is responsive enough for every workflow we have seen; once a day is plenty for orders in a terminal state. Tight polling loops are the usual reason an integration is the first to notice a new limit. If you have a bulk or backfill job that would generate unusual sustained volume, tell us at [support@offergrid.io](mailto:support@offergrid.io) first. We would rather plan for it with you than discover it in a graph. ## When limits ship When we introduce limits on the authenticated API we will, at minimum: * Announce them in the [changelog](/docs/api-reference/changelog) before they take effect. * Return standard `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers on every response, so you can see your headroom before you hit a wall. * Include `Retry-After` on every `429`. * Set the initial ceiling well above observed integration traffic. ## Next steps Every status code and a retry-safe client List responses, identifiers, timestamps, and money # Reseller API Reference Source: https://offergrid.io/docs/api-reference/reseller Every reseller endpoint — catalog, availability, orders, links, and customers The Reseller API is how a reseller partner browses the catalog, checks what is available at a service address, and places and tracks orders on behalf of customers. Every endpoint below links to its full reference page with parameters, schemas, and a request builder. **Base URL** `https://api.offergrid.io` **Authentication** `x-api-key: YOUR_TEAM_API_KEY` on every request — see [Authentication](/docs/api-reference/authentication). **OpenAPI spec** [`openapi-reseller.json`](/docs/openapi/openapi-reseller.json) ## Reseller Availability Reseller: Find offers available at a service address | Endpoint | Method | Path | What it does | | ------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------------ | ------------------------------------------------------------------------------------------ | | [Find available offers for an address](/docs/reseller-api-reference/reseller-availability/find-available-offers-for-an-address) | `POST` | `/reseller/availability` | Given a service address, returns every offer your reseller team can sell at that location. | ## Reseller Catalog Reseller: Browse available service offerings | Endpoint | Method | Path | What it does | | ------------------------------------------------------------------------------------------------ | ------ | ------------------------ | -------------------------------------------------------------------- | | [Browse available offers](/docs/reseller-api-reference/reseller-catalog/browse-available-offers) | `GET` | `/reseller/catalog` | Browse all service offers available to your reseller team. | | [Get offer details](/docs/reseller-api-reference/reseller-catalog/get-offer-details) | `GET` | `/reseller/catalog/{id}` | Retrieve detailed information about a specific offer in the catalog. | ## Reseller Customers Reseller: Manage customers and leads | Endpoint | Method | Path | What it does | | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | [List customers and leads](/docs/reseller-api-reference/reseller-customers/list-customers-and-leads) | `GET` | `/reseller/customers` | Returns this reseller team's customers (people who have placed an order) and leads (people added manually, by API import, or by event). | | [Add a customer or lead manually](/docs/reseller-api-reference/reseller-customers/add-a-customer-or-lead-manually) | `POST` | `/reseller/customers` | Create a new lead or customer record. | | [Get customer detail (with this reseller's orders)](/docs/reseller-api-reference/reseller-customers/get-customer-detail-with-this-resellers-orders) | `GET` | `/reseller/customers/{customerId}` | | | [Update customer notes / tags / status / kind](/docs/reseller-api-reference/reseller-customers/update-customer-notes-tags-status-kind) | `PATCH` | `/reseller/customers/{customerId}` | Update reseller-private fields. | | [Remove customer from this reseller's list](/docs/reseller-api-reference/reseller-customers/remove-customer-from-this-resellers-list) | `DELETE` | `/reseller/customers/{customerId}` | Removes the link between this reseller and the customer. | ## Reseller Links Reseller: Manage shareable customer links | Endpoint | Method | Path | What it does | | ------------------------------------------------------------------------------------------------ | -------- | ---------------------- | ------------------------------------------------------------------------------------------------- | | [Create a shareable link](/docs/reseller-api-reference/reseller-links/create-a-shareable-link) | `POST` | `/reseller/links` | Generate a shareable link for tenants to order services at a specific address. | | [List all shareable links](/docs/reseller-api-reference/reseller-links/list-all-shareable-links) | `GET` | `/reseller/links` | Retrieve all shareable links created by your reseller team. | | [Get link details](/docs/reseller-api-reference/reseller-links/get-link-details) | `GET` | `/reseller/links/{id}` | Retrieve detailed information about a specific link, including recent orders placed via the link. | | [Update a link](/docs/reseller-api-reference/reseller-links/update-a-link) | `PATCH` | `/reseller/links/{id}` | Update link properties such as property name, move-in date, or status. | | [Delete a link](/docs/reseller-api-reference/reseller-links/delete-a-link) | `DELETE` | `/reseller/links/{id}` | Permanently delete a shareable link. | ## Reseller Orders Reseller: Place and manage orders | Endpoint | Method | Path | What it does | | ----------------------------------------------------------------------------------- | ------- | ------------------------------ | ------------------------------------------------------------------------------------------------------- | | [Place a new order](/docs/reseller-api-reference/reseller-orders/place-a-new-order) | `POST` | `/reseller/orders` | Create a new order for one or more service offers. | | [List your orders](/docs/reseller-api-reference/reseller-orders/list-your-orders) | `GET` | `/reseller/orders` | Retrieve all orders placed by your reseller team. | | [Get order details](/docs/reseller-api-reference/reseller-orders/get-order-details) | `GET` | `/reseller/orders/{id}` | Retrieve detailed information about a specific order, including all items and their fulfillment status. | | [Cancel an order](/docs/reseller-api-reference/reseller-orders/cancel-an-order) | `PATCH` | `/reseller/orders/{id}/cancel` | Cancel a pending or submitted order. | ## Reseller Webhooks Reseller: Receive order events for the orders you placed | Endpoint | Method | Path | What it does | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------ | ------------------------------------------------------------------------------------------------ | | [Register a webhook](/docs/reseller-api-reference/reseller-webhooks/register-a-webhook) | `POST` | `/reseller/webhooks` | Register an HTTPS endpoint to receive signed order-event deliveries for orders your team placed. | | [List your registered webhooks](/docs/reseller-api-reference/reseller-webhooks/list-your-registered-webhooks) | `GET` | `/reseller/webhooks` | Secrets are masked — the full value is only ever returned at creation. | | [Get a webhook by id](/docs/reseller-api-reference/reseller-webhooks/get-a-webhook-by-id) | `GET` | `/reseller/webhooks/{id}` | | | [Update a webhook (url, subscribed events, or active state)](/docs/reseller-api-reference/reseller-webhooks/update-a-webhook-url-subscribed-events-or-active-state) | `PATCH` | `/reseller/webhooks/{id}` | | | [Delete a webhook](/docs/reseller-api-reference/reseller-webhooks/delete-a-webhook) | `DELETE` | `/reseller/webhooks/{id}` | | | [List recent delivery attempts for a webhook](/docs/reseller-api-reference/reseller-webhooks/list-recent-delivery-attempts-for-a-webhook) | `GET` | `/reseller/webhooks/{id}/deliveries` | The 50 most recent attempts, newest first. | ## Conventions Every endpoint on this page follows the shared [API conventions](/docs/api-reference/conventions) for list responses, identifiers, timestamps, and money, and the shared [error format](/docs/api-reference/errors). # Versioning Source: https://offergrid.io/docs/api-reference/versioning How the Offergrid API changes, what we promise not to break, and how deprecations work ## Where we are The Offergrid API is at **version 1**. There is no version segment in the URL and no version header — `https://api.offergrid.io/provider/offers` is the current and only address for that endpoint. We evolve version 1 additively rather than shipping v2, v3, v4 URLs. That keeps integrations working without a migration project every time the product grows. ## What will not change without notice Within version 1, we treat these as a contract: * **Endpoint paths and methods.** An existing path will not be removed, renamed, or change its HTTP method. * **Existing response fields.** A field that exists will not be removed, renamed, or change type. * **Existing request fields.** A currently-optional field will not become required, and accepted values will not be narrowed. * **Status codes for existing conditions.** A request that returns `409` today will not start returning `400`. * **Enum values you already receive.** An existing order status or offer category will not be renamed out from under you. * **The webhook envelope.** `{ id, type, version, data }` is stable. An incompatible change to an existing event type's payload bumps `version`. ## What can change at any time These are routine, and your integration must tolerate them: * **New fields in responses.** Parse permissively and ignore fields you do not recognize. A parser that rejects unknown fields will break on a normal release. * **New optional request fields.** * **New endpoints, new query parameters, and new webhook event types.** A webhook only receives the types it subscribes to, so a new type will not arrive unannounced — but a `switch` on `type` should still have a default branch. * **New enum values.** Handle an unrecognized status by falling through rather than throwing. * **Ordering of array results,** unless an endpoint documents a sort. * **Error message wording.** Branch on the HTTP status code, never on the `message` string. The single most common cause of a broken integration is a client that validates responses strictly. Be liberal in what you accept. ## Deprecation If we ever need to make a breaking change, this is the process: 1. **Announce** in the [changelog](/docs/api-reference/changelog) with the reason, the replacement, and a removal date. 2. **Ship the replacement first**, so both old and new work at the same time. 3. **Notify integrators directly** — we know which teams call which endpoints, and we will email you rather than expect you to be watching a page. 4. **Leave at least 90 days** between the announcement and removal. Nothing is deprecated today. ## Preview features Some capabilities ship to specific partners before they are general. If something is not documented here, treat it as unsupported and subject to change regardless of whether it appears to work — including undocumented fields you may see in a response. ## Staying informed * The [changelog](/docs/api-reference/changelog) records every API-affecting change. * The OpenAPI specs are regenerated from the API on every change and published at stable URLs — [provider](/docs/openapi/openapi-provider.json), [reseller](/docs/openapi/openapi-reseller.json), [public](/docs/openapi/openapi-public.json). Diffing them in CI is the most reliable way to detect a change that affects you. * For anything that would materially affect your integration, email [support@offergrid.io](mailto:support@offergrid.io) and we will make sure you are on the notification list. ## Next steps Dated record of API changes List responses, identifiers, timestamps, and money # Authentication Source: https://offergrid.io/docs/authentication Learn how to authenticate with the Offergrid API This is the overview — enough to get a key and make your first authenticated request. For role behaviour, exact error messages, key rotation, and troubleshooting, see the [API authentication reference](/docs/api-reference/authentication). ## Overview Offergrid uses **Team API Keys** for authentication. Your API key identifies your organization and determines whether you have provider, reseller, or hybrid access to the platform. ## Getting Your API Key ### Step 1: Sign In to Offergrid Visit [offergrid.io](https://offergrid.io) and sign in to your account. ### Step 2: Navigate to Settings Go to your team settings or API settings page in the dashboard. ### Step 3: Generate an API Key Click **Generate New API Key** and securely save the key. You won't be able to see it again after leaving the page. Keep your API key secure and never expose it in client-side code, public repositories, or version control systems. Treat it like a password. ## Using Your API Key Include your API key in the `x-api-key` header with every API request: ```bash theme={null} curl https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_TEAM_API_KEY" ``` ### Example Requests ```typescript TypeScript theme={null} const response = await fetch('https://api.offergrid.io/provider/offers', { method: 'GET', headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, }, }); const offers = await response.json(); ``` ```python Python theme={null} import requests import os response = requests.get( 'https://api.offergrid.io/provider/offers', headers={ 'x-api-key': os.environ['OFFERGRID_API_KEY'] } ) offers = response.json() ``` ```javascript JavaScript theme={null} const apiKey = process.env.OFFERGRID_API_KEY; fetch('https://api.offergrid.io/provider/offers', { headers: { 'x-api-key': apiKey, }, }) .then((res) => res.json()) .then((offers) => console.log(offers)); ``` ## API Key Permissions Your API key's permissions are based on your team's role: ### Provider Access Every `/provider/*` endpoint — offers, orders, markets, webhooks, brands, and customers. See the [Provider API Reference](/docs/api-reference/provider). ### Reseller Access Every `/reseller/*` endpoint — catalog, address availability, orders, shareable links, and customers. See the [Reseller API Reference](/docs/api-reference/reseller). ### Hybrid Access Some teams have both provider and reseller roles. Hybrid teams can access all endpoints with the same API key. ## Best Practices Use environment variables or secure key management systems (like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault) to store API keys. Never hardcode keys in your application code. Generate new API keys periodically and revoke old ones to minimize security risks. Use separate API keys for development, staging, and production environments. Track API key usage in your Offergrid dashboard to detect any unusual activity. If you suspect an API key has been exposed, revoke it immediately and generate a new one. ## API Base URLs ``` https://api.offergrid.io ``` This is the only Offergrid-hosted environment. There is no separate sandbox host and no test-mode key, so requests you make are real — see [Local development](/docs/api-reference/introduction#local-development) for how to test safely. ## Error Responses If authentication fails, you'll receive a `401 Unauthorized` response: ```json theme={null} { "statusCode": 401, "message": "Invalid team API key", "error": "Unauthorized" } ``` Common authentication errors: * **Missing API key**: The `x-api-key` header was not provided * **Invalid API key**: The provided key doesn't exist or has been revoked * **Wrong value sent**: The header name was sent as the value instead of the key A `403` is different — the key is valid, but your team's role does not cover that endpoint family. Every status code and its exact message is listed in [Errors](/docs/api-reference/errors). ## Need Help? If you're having trouble with authentication: * Check that you're using the correct header name (`x-api-key`) * Verify that your API key hasn't been revoked * Ensure your team has the appropriate provider or reseller role * Contact support at [support@offergrid.io](mailto:support@offergrid.io) # How It Works Source: https://offergrid.io/docs/how-it-works Understand how Offergrid connects service providers with reseller partners ## Platform Overview Offergrid is a B2B marketplace that streamlines the distribution and sale of essential services. We connect service providers (internet and electricity companies) with reseller partners who help customers find and purchase these services. ## For Service Providers ### Create and Manage Offers Providers publish their service offerings with pricing, availability, and detailed specifications. Control who can see your offers and manage multiple service categories from a single platform. ### Receive Orders When resellers place orders on behalf of customers, you receive them instantly with all necessary customer information for fulfillment. ### Fulfill and Track Update order status through the fulfillment workflow—from acceptance to scheduling, installation, and activation. Keep resellers informed every step of the way. ## For Resellers ### Browse the Catalog Search and filter available service offerings by category, price, location, and features. Compare multiple providers to find the best fit for your customers. ### Place Orders Submit orders with customer details and service addresses. Orders are automatically routed to the appropriate providers for fulfillment. ### Track Progress Monitor order status in real-time as providers accept, schedule, and complete installations. Keep your customers informed throughout the process. ## Key Benefits Providers reach more customers through established reseller networks without building individual partnerships. Resellers access multiple service providers through one platform, reducing complexity and administrative overhead. Order routing, status updates, and notifications happen automatically, reducing manual coordination. Both providers and resellers get real-time insights into orders, fulfillment status, and partner performance. ## Use Cases ### Property Management Property managers can quickly source and order internet, electricity, and other services for new tenants or property improvements. ### Real Estate Real estate agents offer value-added services to homebuyers by coordinating essential service setup before move-in. ### Corporate Facilities Facilities managers find and order services for new office locations, expansions, or vendor changes across multiple properties. ### Service Aggregators Companies that bundle or resell services can integrate multiple providers without managing individual API integrations. ## Next Steps Learn how to publish offers and fulfill orders Start browsing offers and placing orders # Welcome to Offergrid Source: https://offergrid.io/docs/index The B2B marketplace connecting service providers with reseller partners ## What is Offergrid? Offergrid is a B2B marketplace platform that connects service providers with reseller partners. One platform where you can browse service catalogs, place orders, and track fulfillment—streamlining the distribution of essential services like internet and electricity. Expand your distribution reach and integration network through qualified reseller partners. Connect via API, dashboard, or white-glove service—choose what works for you. **[View Provider Docs →](/docs/providers/index)** Access services from multiple providers through one platform. Place orders, track fulfillment, and earn commissions. **[View Reseller Docs →](/docs/resellers/index)** ## Platform Overview ### For Service Providers **Publish Service Offerings** * Create detailed service offers with pricing and availability * Control which resellers can see and order your services * Manage multiple service categories from one platform **Receive Orders Instantly** * Get structured order data with all customer information * Accept or reject based on serviceability * Track fulfillment through automated workflows **Manage Reseller Partnerships** * Build preferred partner lists * Create exclusive offers for specific resellers * Monitor reseller performance metrics ### For Resellers **Browse the Catalog** * Search services by category, price, and location * Compare offerings from multiple providers * Save favorite offers for quick access **Place Orders Easily** * Submit orders with customer details * Orders automatically routed to providers * Track status from acceptance through activation ## Service Categories Our currently supported service categories include: Broadband, fiber, cable, DSL, wireless Deregulated retail electricity plans Additional essential services ## How It Works Service providers create detailed offerings with pricing, availability, and specifications Reseller partners search and compare services for their customers Resellers can submit orders through the Offergrid API or use existing submission channels ## Key Benefits Reach more customers through established reseller networks without building individual partnerships or custom integrations. Access services from many providers through a single API or dashboard. Reduce complexity and administrative overhead. Order routing, status updates, and notifications happen automatically. Less manual coordination, more time for growth. Track orders, fulfillment status, and partner performance in real-time. Make data-driven decisions. ## Core Use Case ### Property Management Property managers use Offergrid to source and order internet, electricity, and other essential services for new tenants or property improvements across their portfolios—all from one platform. ## Get Started Get up and running in minutes with our quick start guide Understand the platform workflow and key concepts Explore the complete API documentation Learn how to authenticate with the API ## Choose Your Path **Service providers** offer essential services (internet, electricity) and want to expand distribution. Learn about provider benefits and features Get started as a provider Publish your first service offering Integrate with your systems **Resellers** help customers find and purchase services through property management, real estate, facilities management, or service aggregation. Learn about reseller benefits and features Get started as a reseller Find services for your customers Integrate with your systems ## Need Help? Have questions? Our team is here to help at [support@offergrid.io](mailto:support@offergrid.io) # Create a brand Source: https://offergrid.io/docs/provider-api-reference/provider-brands/create-a-brand /openapi/openapi-provider.json post /provider/brands Brands are the visual identity offers display under instead of your team name. Each team manages its own brands; names are matched case-insensitively within the team. # Delete an unused brand Source: https://offergrid.io/docs/provider-api-reference/provider-brands/delete-an-unused-brand /openapi/openapi-provider.json delete /provider/brands/{id} Only brands no offer references can be deleted. # Get a brand by id Source: https://offergrid.io/docs/provider-api-reference/provider-brands/get-a-brand-by-id /openapi/openapi-provider.json get /provider/brands/{id} # List all brands for your team Source: https://offergrid.io/docs/provider-api-reference/provider-brands/list-all-brands-for-your-team /openapi/openapi-provider.json get /provider/brands # Update a brand Source: https://offergrid.io/docs/provider-api-reference/provider-brands/update-a-brand /openapi/openapi-provider.json patch /provider/brands/{id} Renaming or re-logoing a brand changes it for every offer displaying it. Sending an empty imageUrl clears the logo. # Get customer detail Source: https://offergrid.io/docs/provider-api-reference/provider-customers/get-customer-detail /openapi/openapi-provider.json get /provider/customers/{customerId} Detail view of a customer including the order items they have placed for your offers. # List customers Source: https://offergrid.io/docs/provider-api-reference/provider-customers/list-customers /openapi/openapi-provider.json get /provider/customers Customers are people who have placed an order for one of your offers. This list does not include reseller-side leads. # Add a geographic area to a market Source: https://offergrid.io/docs/provider-api-reference/provider-markets/add-a-geographic-area-to-a-market /openapi/openapi-provider.json post /provider/markets/{id}/areas Areas compose a market. Supply the `type` field and the matching fields: postalCodes (postal), admin* fields (admin), geometry (polygon), h3Cells (h3), or sourceRef (serviceability — must name an active serviceability integration source owned by your team). Use operation=exclude to carve a hole out of the market. # Create a market Source: https://offergrid.io/docs/provider-api-reference/provider-markets/create-a-market /openapi/openapi-provider.json post /provider/markets # Delete a market Source: https://offergrid.io/docs/provider-api-reference/provider-markets/delete-a-market /openapi/openapi-provider.json delete /provider/markets/{id} # Get a market by id Source: https://offergrid.io/docs/provider-api-reference/provider-markets/get-a-market-by-id /openapi/openapi-provider.json get /provider/markets/{id} # List all markets for your team Source: https://offergrid.io/docs/provider-api-reference/provider-markets/list-all-markets-for-your-team /openapi/openapi-provider.json get /provider/markets # Remove an area from a market Source: https://offergrid.io/docs/provider-api-reference/provider-markets/remove-an-area-from-a-market /openapi/openapi-provider.json delete /provider/markets/{id}/areas/{areaId} # Update a market Source: https://offergrid.io/docs/provider-api-reference/provider-markets/update-a-market /openapi/openapi-provider.json patch /provider/markets/{id} # Bulk update offers from CSV Source: https://offergrid.io/docs/provider-api-reference/provider-offers/bulk-update-offers-from-csv /openapi/openapi-provider.json post /provider/offers/bulk-update Upload a CSV file to update multiple existing offers at once, using the same template as bulk upload (GET /bulk-upload/template). Each row is matched to an existing offer by its "sku" column (scoped to your team), so "sku" is required on every row. Blank cells leave the existing value unchanged — only non-blank columns are applied — which means this endpoint cannot clear a field back to empty. Rows whose SKU does not match one of your offers are reported as errors and never created. # Bulk upload offers from CSV Source: https://offergrid.io/docs/provider-api-reference/provider-offers/bulk-upload-offers-from-csv /openapi/openapi-provider.json post /provider/offers/bulk-upload Upload a CSV file to create multiple offers at once. Download the template first using GET /bulk-upload/template. The CSV supports linking offers to markets using the "marketNames" column (comma-separated market names). Markets must already exist before uploading - create them first via the markets API. # Create a new offer Source: https://offergrid.io/docs/provider-api-reference/provider-offers/create-a-new-offer /openapi/openapi-provider.json post /provider/offers Create a new service offering as a provider. The offer will be associated with your team. # Delete an offer Source: https://offergrid.io/docs/provider-api-reference/provider-offers/delete-an-offer /openapi/openapi-provider.json delete /provider/offers/{id} Permanently delete an offer. You can only delete your own offers. # Download CSV template for bulk offer upload Source: https://offergrid.io/docs/provider-api-reference/provider-offers/download-csv-template-for-bulk-offer-upload /openapi/openapi-provider.json get /provider/offers/bulk-upload/template Download a CSV template file with headers and an example row. Use this template to bulk upload offers. # Duplicate an offer Source: https://offergrid.io/docs/provider-api-reference/provider-offers/duplicate-an-offer /openapi/openapi-provider.json post /provider/offers/{id}/duplicate Create a new draft offer pre-filled from an existing one. The copy gets a "(Copy)" name, resets to draft status, omits the unique SKU, and strips known sample pricing entries. # Get a specific offer Source: https://offergrid.io/docs/provider-api-reference/provider-offers/get-a-specific-offer /openapi/openapi-provider.json get /provider/offers/{id} Retrieve details of a specific offer you created. # List all your offers Source: https://offergrid.io/docs/provider-api-reference/provider-offers/list-all-your-offers /openapi/openapi-provider.json get /provider/offers Retrieve all offers created by your provider team. # Publish an offer Source: https://offergrid.io/docs/provider-api-reference/provider-offers/publish-an-offer /openapi/openapi-provider.json post /provider/offers/{id}/publish Validate an offer against the publish-readiness rules and, if it passes, set its status to active. Returns 400 with structured validationErrors when the offer is not ready to publish. # Update an offer Source: https://offergrid.io/docs/provider-api-reference/provider-offers/update-an-offer /openapi/openapi-provider.json patch /provider/offers/{id} Update an existing offer. You can only update your own offers. # Validate a bulk-update CSV without updating offers Source: https://offergrid.io/docs/provider-api-reference/provider-offers/validate-a-bulk-update-csv-without-updating-offers /openapi/openapi-provider.json post /provider/offers/bulk-update/validate Upload a CSV file to validate it against the bulk-update rules without writing anything. Checks that every row has a "sku" matching one of your offers and that any non-blank enum, numeric, and market values are valid. # Validate CSV file without creating offers Source: https://offergrid.io/docs/provider-api-reference/provider-offers/validate-csv-file-without-creating-offers /openapi/openapi-provider.json post /provider/offers/bulk-upload/validate Upload a CSV file to validate its structure and data without actually creating offers. This is useful for checking your CSV before performing the actual bulk upload. # Get order item details Source: https://offergrid.io/docs/provider-api-reference/provider-orders/get-order-item-details /openapi/openapi-provider.json get /provider/orders/{itemId} Retrieve detailed information about a specific order item for fulfillment. # List order items to fulfill Source: https://offergrid.io/docs/provider-api-reference/provider-orders/list-order-items-to-fulfill /openapi/openapi-provider.json get /provider/orders Retrieve all order items for your offers that need fulfillment. Each item represents a single offer ordered by a reseller. # Update order item status Source: https://offergrid.io/docs/provider-api-reference/provider-orders/update-order-item-status /openapi/openapi-provider.json patch /provider/orders/{itemId}/status Update the fulfillment status of an order item (e.g., accept, reject, schedule, complete). Use this to manage the order workflow from acceptance to completion. # Delete a webhook Source: https://offergrid.io/docs/provider-api-reference/provider-webhooks/delete-a-webhook /openapi/openapi-provider.json delete /provider/webhooks/{id} # Get a webhook by id Source: https://offergrid.io/docs/provider-api-reference/provider-webhooks/get-a-webhook-by-id /openapi/openapi-provider.json get /provider/webhooks/{id} # List recent delivery attempts for a webhook Source: https://offergrid.io/docs/provider-api-reference/provider-webhooks/list-recent-delivery-attempts-for-a-webhook /openapi/openapi-provider.json get /provider/webhooks/{id}/deliveries # List your registered webhooks Source: https://offergrid.io/docs/provider-api-reference/provider-webhooks/list-your-registered-webhooks /openapi/openapi-provider.json get /provider/webhooks Secrets are masked — the full value is only ever returned at creation. # Register a webhook Source: https://offergrid.io/docs/provider-api-reference/provider-webhooks/register-a-webhook /openapi/openapi-provider.json post /provider/webhooks Register an HTTPS endpoint to receive signed order-event deliveries. The response includes the signing secret — it is shown only this once. # Update a webhook (url, subscribed events, or active state) Source: https://offergrid.io/docs/provider-api-reference/provider-webhooks/update-a-webhook-url-subscribed-events-or-active-state /openapi/openapi-provider.json patch /provider/webhooks/{id} # Provider Documentation Source: https://offergrid.io/docs/provider-documentation Complete guide for service providers using Offergrid ## Provider Documentation Everything you need to know about using Offergrid as a service provider. Expand your distribution network, manage offers, fulfill orders, and grow your business. ## Getting Started Learn about provider benefits, features, and how Offergrid works for service providers Get up and running in minutes with step-by-step instructions ## Managing Offers Learn how to publish your first service offering Understand service categories and how to structure your offers Set up pricing models that work for your business Control who can see and order your services ## Order Fulfillment Understand how orders are delivered and what information you receive Learn the complete order fulfillment process Tips and best practices for successful order fulfillment ## Partner Management Build and manage relationships with reseller partners Create exclusive offers for your top-performing partners ## Integration Integrate Offergrid with your existing systems Set up real-time notifications for orders and updates Complete API documentation and reference # API Integration Guide Source: https://offergrid.io/docs/providers/api-integration Integrate Offergrid with your existing systems ## Overview The Offergrid Provider API lets you automate offer management and order fulfillment by integrating directly with your existing systems. ## Getting Started ### 1. Get Your API Key Generate a Team API Key from your dashboard: 1. Sign in to [offergrid.io](https://offergrid.io) 2. Navigate to **Settings** → **API Keys** 3. Click **Generate New Key** 4. Save the key securely See [Authentication](/docs/authentication) for detailed instructions. ### 2. Choose Your Integration Approach **Option A: Direct API Calls** * Use HTTP requests from your application * Full control and flexibility * Best for custom integrations **Option B: Webhooks** * Receive real-time notifications * Event-driven architecture * Best for order automation **Option C: Scheduled Sync** * Poll API periodically * Simple to implement * Best for batch processing ## Common Integration Patterns ### Pattern 1: Automated Offer Sync Sync your internal product catalog to Offergrid: ```typescript theme={null} // Example: Daily sync of offers from your CRM async function syncOffers() { const internalOffers = await fetchFromCRM(); for (const offer of internalOffers) { const offergridOffer = transformToOffergridFormat(offer); // Check if offer exists const existing = await findOfferBySKU(offer.sku); if (existing) { // Update existing offer await updateOffer(existing.id, offergridOffer); } else { // Create new offer await createOffer(offergridOffer); } } } // Transform your internal format to Offergrid format function transformToOffergridFormat(internalOffer) { return { name: internalOffer.productName, category: mapCategory(internalOffer.type), sku: internalOffer.sku, monthlyPrice: internalOffer.price, status: internalOffer.isActive ? 'active' : 'inactive', description: internalOffer.description, serviceSpecificData: { // Map category-specific fields }, }; } ``` Run this sync: * **Daily**: For frequently changing catalogs * **Hourly**: For dynamic pricing * **On-demand**: When products update in your system ### Pattern 2: Real-Time Order Processing Use webhooks to process orders immediately: ```typescript theme={null} // Example: Webhook endpoint to receive new orders app.post('/webhooks/offergrid/orders', async (req, res) => { const { event, orderId, itemId } = req.body; if (event === 'order.created') { // Fetch full order details const order = await fetchOrderFromOffergrid(itemId); // Check service availability const available = await checkServiceability(order.serviceAddress); if (available) { // Auto-accept and create work order in your system await acceptOrder(itemId); await createWorkOrder(order); // Schedule customer contact await scheduleCustomerCall(order); } else { // Reject with reason await rejectOrder(itemId, 'Service not available at this address'); } } res.status(200).send('OK'); }); ``` ### Pattern 3: Status Sync from Fulfillment System Update Offergrid when your internal status changes: ```typescript theme={null} // Example: Update Offergrid when your CRM updates order status async function onWorkOrderStatusChange(workOrder) { const statusMap = { scheduled: 'scheduled', in_progress: 'in_progress', completed: 'completed', failed: 'failed', }; await updateOffergridOrderStatus(workOrder.offergridItemId, { status: statusMap[workOrder.status], providerNotes: workOrder.notes, scheduledFor: workOrder.appointmentDate, metadata: { workOrderId: workOrder.id, technicianId: workOrder.technicianId, }, }); } // Hook into your CRM's status change events crmSystem.on('workorder.status_changed', onWorkOrderStatusChange); ``` ## Core API Operations ### Managing Offers ```bash theme={null} POST /provider/offers ``` ```json theme={null} { "name": "High-Speed Internet 1000 Mbps", "category": "internet", "status": "active", "monthlyPrice": 59.99 } ``` Returns the created offer with assigned ID. ```bash theme={null} GET /provider/offers ``` Returns array of all offers for your team. ```bash theme={null} PATCH /provider/offers/{id} ``` ```json theme={null} { "monthlyPrice": 49.99, "status": "active" } ``` Updates only the fields provided. ```bash theme={null} DELETE /provider/offers/{id} ``` Permanently removes the offer. ### Managing Orders ```bash theme={null} GET /provider/orders?status=pending ``` Filter by status to get orders needing attention. ```bash theme={null} GET /provider/orders/{itemId} ``` Returns full order details including customer info and service address. ```bash theme={null} PATCH /provider/orders/{itemId}/status ``` ```json theme={null} { "status": "accepted", "providerNotes": "Order accepted. Customer will be contacted within 24 hours.", "metadata": {} } ``` Updates order status and adds notes for reseller. ## Error Handling Implement robust error handling: ```typescript theme={null} async function safeApiCall(apiFunction, retries = 3) { for (let i = 0; i < retries; i++) { try { return await apiFunction(); } catch (error) { if (error.status === 401) { // Authentication error - don't retry throw new Error('Invalid API key'); } if (error.status === 429) { // Rate limited - wait and retry await sleep(2 ** i * 1000); continue; } if (error.status >= 500) { // Server error - retry await sleep(2 ** i * 1000); continue; } // Client error (4xx) - don't retry throw error; } } throw new Error('Max retries exceeded'); } ``` ## Rate Limiting Be mindful of rate limits: * **Burst**: 100 requests per minute * **Sustained**: 10,000 requests per hour **Best practices**: * Implement exponential backoff * Cache responses when appropriate * Batch operations where possible * Use webhooks instead of polling ## Security Best Practices Use environment variables or secure key management: ```typescript theme={null} // ✅ Good const apiKey = process.env.OFFERGRID_API_KEY; // ❌ Bad const apiKey = 'pk_live_abc123...'; // Never hardcode! ``` Always use `https://` endpoints, never `http://`. Verify webhook requests actually come from Offergrid: ```typescript theme={null} function verifyWebhook(req) { const signature = req.headers['x-offergrid-signature']; const payload = JSON.stringify(req.body); const expected = createHmac('sha256', webhookSecret) .update(payload) .digest('hex'); return signature === expected; } ``` Don't let API calls hang indefinitely: ```typescript theme={null} const response = await fetch(url, { headers: { 'x-api-key': apiKey }, signal: AbortSignal.timeout(10000), // 10 second timeout }); ``` ## Testing Your Integration ### 1. Use Draft Offers Test with `status: "draft"` offers that won't be visible to resellers: ```json theme={null} { "name": "Test Offer - Do Not Order", "status": "draft", "monthlyPrice": 0.01 } ``` ### 2. Create Test Orders Work with support to create test orders for validation. ### 3. Monitor Logs Watch your integration logs for errors: ```typescript theme={null} const logger = createLogger({ level: 'info', format: format.json(), }); logger.info('Order accepted', { orderId: order.id, offerId: order.offerId, status: 'accepted', }); ``` ## Example Integration Full example of a provider integration: ```typescript theme={null} import { OffergridClient } from '@offergrid/node-sdk'; const client = new OffergridClient({ apiKey: process.env.OFFERGRID_API_KEY, }); // Sync offers daily cron.schedule('0 2 * * *', async () => { await syncOffersToOffergrid(); }); // Process new orders via webhook app.post('/webhooks/offergrid', async (req, res) => { const { event, itemId } = req.body; if (event === 'order.created') { await processNewOrder(itemId); } res.sendStatus(200); }); // Update Offergrid when internal status changes async function onInternalStatusChange(workOrder) { await client.orders.updateStatus(workOrder.offergridItemId, { status: workOrder.status, providerNotes: workOrder.notes, }); } ``` ## Next Steps Complete API documentation Set up real-time notifications API key management Learn about order processing # Provider Business Case Source: https://offergrid.io/docs/providers/business-case How Offergrid reduces fulfillment costs by enabling direct API orders from PMS partners ## The Opportunity Property management software (PMS) providers are becoming a major sales channel for service providers. But this channel introduces a new cost layer: PMS providers generate the leads, then pass them to brokers who submit orders and collect commissions. **What if 80% of these orders went directly to you?** Orders that don't require human intervention—no exceptions, no special handling—can be submitted via API directly from the PMS originator, bypassing the broker entirely. ## The Math (Hypothetical) | Cost Component | Current Model | With Offergrid | | ------------------- | ------------- | -------------- | | Offergrid Platform | \$0 | \$25 | | Broker Commission | \$250 | \$0 | | PMS Commission | \$250 | \$250 | | PMC Commission | \$250 | \$250 | | **Total per Order** | **\$750** | **\$525** | **Estimated Savings: \$225 per API order** At scale, a major provider processing thousands of PMS-originated orders monthly could reduce fulfillment costs by \$500K+ per month. ## How Offergrid Works 1. **You publish offers** — pricing, availability, marketing assets, and compliance requirements stored in one place 2. **PMS partners access your catalog** — approved resellers get real-time offer data via API or dashboard (millisecond access) 3. **Orders come direct** — API-ready orders submit directly to you; complex orders still route through brokers 4. **You control access** — decide which partners see which offers, set submission rules per market or reseller ## What Makes This Different * **Not a broker**: Offergrid doesn't sell services or take order commissions like Red Ventures * **Not a master broker**: Unlike DSI or Perfect Vision, Offergrid manages data relationships, not business relationships * **One integration**: Connect once, then add partners through your dashboard—no additional dev work per partner ## Integration Options for Providing Offers | Option | Offers | | --------------------- | ----------------------------------------------------------------------------------------------------- | | Self-Serve, UI | Upload offers manually via dashboard or in-bulk using CSV uploads | | Self-Serve, API | Use our provider API to synchronize your offers from your source-of-truth | | White Glove / Managed | Partner with our team for us to create a secure, bespoke, transformation/sync pipeline on your behalf | ## Next Steps Explore the full provider documentation Set up a meeting with your digital/engineering team Begin with a single market or partner to validate the model # Creating Offers Source: https://offergrid.io/docs/providers/creating-offers Learn how to create and publish service offerings ## Overview Service offers are the foundation of your presence on Offergrid. Each offer represents a specific service package you're making available to reseller partners. ## Creating an Offer ### Via Dashboard Click **Offers** in the sidebar menu Click the **Create New Offer** button * **Name**: Customer-facing name (e.g., "High-Speed Internet 1000 Mbps") * **Internal Name**: Your internal tracking name (optional) * **Category**: Select the service type * **SKU**: Your product SKU for inventory tracking * **Pricing Type**: Fixed, Variable, Tiered, or Custom * **Monthly Price**: Base recurring price * **Setup Fees**: One-time fees (optional) * **Additional Costs**: Equipment, installation, etc. Include service-specific information based on category (speeds, channels, coverage, etc.) * **Description**: Detailed service description * **Marketing Headline**: Catchy tagline * **Key Features**: Bullet points of main benefits * **Images**: Service photos or graphics * **Service Area**: ZIP codes or regions where available * **Status**: Draft (hidden) or Active (visible to resellers) **Save as Draft** to review later, or **Publish** to make available immediately ### Via API Create an offer programmatically using the Provider API: ```bash cURL theme={null} curl -X POST https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "High-Speed Internet 1000 Mbps", "category": "internet", "status": "active", "monthlyPrice": 59.99, "description": "Blazing fast fiber internet with unlimited data", "keyFeatures": [ "1000 Mbps download", "1000 Mbps upload", "Unlimited data", "No contract required" ], "internet": { "speed": { "minBandwidthMbps": 1000, "maxBandwidthMbps": 1000, "connectionType": "fiber" } } }' ``` ```typescript TypeScript theme={null} const response = await fetch('https://api.offergrid.io/provider/offers', { method: 'POST', headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'High-Speed Internet 1000 Mbps', category: 'internet', status: 'active', monthlyPrice: 59.99, description: 'Blazing fast fiber internet with unlimited data', keyFeatures: [ '1000 Mbps download', '1000 Mbps upload', 'Unlimited data', 'No contract required', ], internet: { speed: { minBandwidthMbps: 1000, maxBandwidthMbps: 1000, connectionType: 'fiber', }, }, }), }); const offer = await response.json(); ``` ## Required Fields At minimum, you must provide: * `name` - Public-facing offer name ## Recommended Fields For better reseller engagement, include: * `description` - Detailed service description * `category` - Service type (`internet`, `electricity`, or `other`) * `monthlyPrice` - Pricing information * `keyFeatures` - Bullet points of main benefits * `electricity` / `internet` - Category-specific structured contract (see [Service Categories](/docs/providers/offer-categories)) ## Offer Status Control offer visibility with the `status` field: * **`draft`**: Hidden from resellers, visible only to you * **`active`**: Visible to authorized resellers, available for ordering * **`inactive`**: Temporarily hidden but can be reactivated * **`archived`**: Permanently archived, not available Start with `draft` status while you finalize details, then switch to `active` when ready. ## Consumer shop distribution Beyond reseller (B2B) access, you can publish an offer to the public `/shop` storefront. This is opt-in and independent of reseller access — nothing appears in the shop unless you set `consumerEnabled: true`. * `consumerEnabled` - Set to `true` to list the offer on the public shop. * `consumerMode` - How the offer converts: * `checkout` - the shopper completes the order inside Offergrid. * `lead_gen` - the shop's call-to-action logs a click and redirects the shopper to your own signup page (`leadGenUrl`) with attribution parameters; Offergrid creates no order. * `leadGenUrl` - The partner signup URL template for `lead_gen` offers. Must be an `https://` URL and may embed `{{variable}}` placeholders in query-parameter values, rendered from collected customer data at handoff. Supported template variables: `first_name`, `last_name`, `full_name`, `email`, `phone`, `street`, `unit`, `city`, `state`, `zip`, `esiid`, `start_date`, `action` (`move`/`switch`), `tdsp_duns`, `click_id`, `offer_external_id`. ```json theme={null} { "consumerEnabled": true, "consumerMode": "lead_gen", "leadGenUrl": "https://partner.example.com/signup?ref=offergrid&fname={{first_name}}&zip={{zip}}" } ``` The `leadGenUrl` template is fully validated (https, allowlisted variables, and placeholders only in query-parameter values) when the offer is published. A `lead_gen` offer cannot be published without a valid `leadGenUrl`. ## Best Practices Make it easy for resellers to understand what you're offering at a glance. Include key details like speeds, sizes, or service levels in the name. The more details you provide, the easier it is for resellers to match your services to customer needs. Include technical specs, coverage details, and any limitations. Help resellers sell your services by providing marketing headlines, key benefits, and high-quality images. Include all fees and costs upfront. Clearly indicate recurring vs. one-time charges. Keep service area and availability information current to avoid order failures. Create offers in `draft` status first, review all details, and test the order flow before making them `active`. ## Next Steps Learn about category-specific requirements Understand different pricing models Control who can see your offers View complete API documentation # Electricity Offers Source: https://offergrid.io/docs/providers/electricity-offers Publish deregulated retail electricity plans with the structured .electricity contract ## Overview Electricity is a first-class service type on Offergrid. Rather than a free-form JSON blob, electricity offers carry a single, structured **`electricity`** object grouped into four sections — **rate**, **term**, **plan**, and **disclosures** — that models a deregulated retail electricity plan the way a Texas [Electricity Facts Label (EFL)](https://www.puc.texas.gov/consumer/electricity/Documents/facts.pdf) does. This page is the narrative guide. The field-by-field reference is published in the [API Reference](/docs/api-reference/introduction) as the `ElectricityContractWrite`, `ElectricityContract`, and `ElectricityCharge` schema components. ## Identifying an electricity offer Every offer response carries a top-level **`serviceType`** discriminator that mirrors `category` — check either field: ```jsonc theme={null} { "id": "…", "serviceType": "electricity", "category": "electricity", "name": "Amigo Fixed 12", "status": "active", "electricity": { /* the contract, see below */ } } ``` Electricity offers additionally include the grouped **`electricity`** object. Other service types (`internet`, `other`) get their own top-level key. ## The `electricity` contract ```jsonc theme={null} "electricity": { "rate": { "type": "fixed", // fixed | variable | indexed "charges": [ /* bill breakdown — the source of truth, see below */ ], "avgPriceAt1000Kwh": 16.2, // ¢/kWh, the all-in EFL comparison number "estimatedMonthlyAt1000Kwh": 162.00 // READ-ONLY — derived from charges }, "term": { "length": "months_12", // no_contract | month_to_month | months_12 | months_24 | months_36 "earlyTerminationFee": 150, // dollars "earlyTerminationFeeNotes": "Prorated by months remaining" }, "plan": { "renewablePercentage": 100, // 0–100 "freeNightsWeekends": false, "noDeposit": true }, "disclosures": { // Texas PUCT regulatory disclosures "electricityFactsLabel": { "url": "https://example.com/efl.pdf", "versionId": "EFL-2024-001", "avgPrice500kwh": 17.1, "avgPrice1000kwh": 16.2, "avgPrice2000kwh": 15.4, "renewablePercent": 100 }, "puctCertNumber": "10081", "puctCertifiedName": "Amigo Energy", "termsOfServiceUrl": "https://example.com/tos.pdf", "yourRightsUrl": "https://example.com/yrac.pdf" } } ``` All four sections and all fields are optional on write, **except** the [publish requirement](#publishing-requirements). Send only what you have; unspecified fields are left untouched on update. * **`type`** — `fixed`, `variable`, or `indexed`. * **`charges`** — the ordered provider (REP) + utility (TDU) charge lines that define pricing. This is the source of truth — see [The charge breakdown](#the-charge-breakdown-rate-charges). * **`avgPriceAt1000Kwh`** — the EFL "average price at 1000 kWh" comparison number, in ¢/kWh (all-in). * **`estimatedMonthlyAt1000Kwh`** — read-only dollar headline, derived from `charges` (ignored on write). * **`length`** — `no_contract`, `month_to_month`, `months_12`, `months_24`, or `months_36`. * **`earlyTerminationFee`** — cancellation fee in dollars. * **`earlyTerminationFeeNotes`** — free-text detail, e.g. "Prorated by months remaining". * **`renewablePercentage`** — 0–100. * **`freeNightsWeekends`** — boolean. * **`noDeposit`** — boolean. * **`electricityFactsLabel`** — the EFL: `url`, `versionId`, and the three benchmark prices (`avgPrice500kwh`, `avgPrice1000kwh`, `avgPrice2000kwh`) plus `renewablePercent`. * **`puctCertNumber`** / **`puctCertifiedName`** — your PUCT REP certification. * **`termsOfServiceUrl`** / **`yourRightsUrl`** — the Terms of Service and Your Rights as a Customer documents. ## Creating an electricity offer ```bash cURL theme={null} curl -X POST https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Amigo Fixed 12", "category": "electricity", "electricity": { "rate": { "type": "fixed", "charges": [ { "type": "perKwh", "owner": "provider", "label": "Energy Charge", "centsPerKwh": 12.5 }, { "type": "fixed", "owner": "utility", "label": "TDU Meter Charge", "amountDollars": 4.39 }, { "type": "perKwh", "owner": "utility", "label": "TDU Delivery", "centsPerKwh": 4.2 } ] }, "term": { "length": "months_12", "earlyTerminationFee": 150 }, "plan": { "renewablePercentage": 100, "noDeposit": true }, "disclosures": { "puctCertNumber": "10081", "electricityFactsLabel": { "url": "https://example.com/efl.pdf" } } } }' ``` ```typescript TypeScript theme={null} const response = await fetch('https://api.offergrid.io/provider/offers', { method: 'POST', headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Amigo Fixed 12', category: 'electricity', electricity: { rate: { type: 'fixed', charges: [ { type: 'perKwh', owner: 'provider', label: 'Energy Charge', centsPerKwh: 12.5 }, { type: 'fixed', owner: 'utility', label: 'TDU Meter Charge', amountDollars: 4.39 }, { type: 'perKwh', owner: 'utility', label: 'TDU Delivery', centsPerKwh: 4.2 }, ], }, term: { length: 'months_12', earlyTerminationFee: 150 }, plan: { renewablePercentage: 100, noDeposit: true }, disclosures: { puctCertNumber: '10081', electricityFactsLabel: { url: 'https://example.com/efl.pdf' }, }, }, }), }); const offer = await response.json(); ``` The response is the created offer with `serviceType: "electricity"`, the projected `electricity` object (now including the derived `rate.estimatedMonthlyAt1000Kwh`), and the standard offer fields. ## Updating an electricity offer Send only the sections you're changing. The `electricity` object is **merged** onto the stored offer, so unrelated data is preserved: ```jsonc PATCH /provider/offers/{id} theme={null} { "electricity": { "term": { "earlyTerminationFee": 200 }, "plan": { "renewablePercentage": 100 } } } ``` This changes only the early termination fee and renewable percentage; the existing rate, charges, and disclosures are untouched, and the derived headline is recomputed. The `electricity` object is the **only** way to set an electricity offer's pricing, term, plan, and disclosures — there is no flat/free-form alternative field to fall back to. ## The charge breakdown (`rate.charges`) A deregulated retail electricity bill has two legally distinct parts (Texas PUCT Rule 25.475 / the EFL): charges set by the **provider** (REP) and pass-through delivery charges set by the local **utility** (TDU, e.g. Oncor). `rate.charges` is an ordered list of charge lines that captures both, plus usage-tiered charges and threshold bill credits. Each charge line: | Field | Applies to | Meaning | | ----------------------------- | ----------------- | ---------------------------------------------------------------------- | | `type` | all | `perKwh` \| `fixed` \| `credit` | | `owner` | all | `provider` (REP) \| `utility` (TDU) | | `label` | all | Display name, e.g. `"Energy Charge"`, `"TDU Delivery"` | | `centsPerKwh` | `perKwh` | Rate in ¢/kWh | | `amountDollars` | `fixed`, `credit` | Dollar amount (credits entered **positive**, subtracted from the bill) | | `minUsageKwh` / `maxUsageKwh` | optional | Usage band (see below) | **Usage bands:** * `perKwh` bills only the kWh inside the band — `"12¢ for the first 500 kWh"` is `{ "minUsageKwh": 0, "maxUsageKwh": 500 }`; `"15¢ above 500 kWh"` is `{ "minUsageKwh": 500 }`. * `fixed` / `credit` applies only when **total** usage falls inside the band — e.g. `"$125 credit if usage ≥ 1000 kWh"` is a credit with `{ "minUsageKwh": 1000 }`. ### Bill estimate math Offergrid folds `charges` into an itemized bill at any usage level. Line amounts are rounded to cents individually; subtotals and totals are sums of the rounded lines, so a breakdown always reconciles. Worked example at **1000 kWh** for the create payload above: | Line | Owner | Calculation | Amount | | ---------------- | -------- | ---------------- | ------------ | | Energy Charge | provider | 12.5¢ × 1000 kWh | \$125.00 | | TDU Meter Charge | utility | flat | \$4.39 | | TDU Delivery | utility | 4.2¢ × 1000 kWh | \$42.00 | | **Total** | | | **\$171.39** | * Provider subtotal = \$125.00 * Utility subtotal = \$46.39 * Total = **\$171.39** * Effective all-in rate = 171.39 × 100 ÷ 1000 = **17.139 ¢/kWh** (the EFL metric) `rate.estimatedMonthlyAt1000Kwh` in the response is this total at 1000 kWh. ## Publishing requirements `POST /provider/offers/{id}/publish` validates the offer. For electricity, the one contract-specific rule is: `rate.charges` must include **at least one provider `perKwh` energy charge**. If it doesn't, publish returns `400` with structured `validationErrors` pointing at `electricityDetails.charges`. General offer requirements — name, SKU, description, at least one market, etc. — also apply. ## Derived and read-only fields * **`rate.estimatedMonthlyAt1000Kwh`** is computed from `rate.charges`. It is output-only — setting it on a write has no effect. * Offergrid mirrors this figure into the generic `monthlyPrice` column so electricity offers sort and filter alongside fixed-price offers. ## Storage Electricity offer data lives in a dedicated typed relation — there is no free-form JSON blob involved. `charges` is the only accepted rate representation in the `electricity` contract; you never have to branch on a flat legacy rate format. Read everything electricity-related from `.electricity`. ## Quick reference | You want to… | Use | | --------------------------- | ------------------------------------------------------------------ | | Detect an electricity offer | top-level `serviceType === "electricity"` | | Read pricing | `electricity.rate.charges` (+ derived `estimatedMonthlyAt1000Kwh`) | | Read term / ETF | `electricity.term` | | Read green %, perks | `electricity.plan` | | Read EFL / PUCT | `electricity.disclosures` | | Set pricing | send `electricity.rate.charges` on create/update | | Publish | ensure ≥1 provider `perKwh` charge, then `POST …/publish` | ## Next steps The general offer create/publish flow Fields for internet, electricity, and the other category Automate offer sync and order processing The `ElectricityContract` schema, field by field # Fulfillment Best Practices Source: https://offergrid.io/docs/providers/fulfillment-best-practices Tips and strategies for smooth order fulfillment ## Overview Successful fulfillment leads to happy customers, satisfied resellers, and more business. Follow these best practices to streamline your operations and maximize success rates. ## Response Time ### Accept/Reject Within 24 Hours **Target**: Respond to new orders within 2-4 hours Fast response times: * Show resellers you're reliable * Keep customers engaged and excited * Reduce order cancellations * Improve your provider rating Set up automated notifications to alert your team immediately when orders arrive. ### Communicate Delays Promptly If you can't respond quickly: * Update order status with a note * Set expectations for when you'll have an answer * Don't leave orders in `pending` for days ```json theme={null} { "status": "submitted_to_provider", "providerNotes": "Order received. Address verification in progress, will update within 24 hours." } ``` ## Address Verification ### Always Verify Service Availability Before accepting an order: Ensure street, city, state, ZIP are complete and valid Confirm your service is available at that specific address Look for HOA restrictions, building policies, or other blockers For apartments/condos, ensure unit number is correct ### Common Address Issues Watch out for: * **Incorrect ZIP codes**: Verify ZIP matches city/state * **Missing unit numbers**: Apartment orders without unit info * **Ambiguous addresses**: "123 Main Street" exists in multiple cities * **New construction**: Addresses not yet in your system * **Rural routes**: Non-standard address formats When in doubt, contact the reseller to clarify address details before rejecting. They can often provide additional context. ## Scheduling & Communication ### Contact Customers Promptly After accepting an order: * Contact customer within 24-48 hours * Offer multiple scheduling options * Confirm contact information * Set clear expectations ### Provide Scheduling Details When setting `status: "scheduled"`, include: ```json theme={null} { "status": "scheduled", "scheduledFor": "2025-01-15T13:00:00Z", "providerNotes": "Installation scheduled for Tuesday, Jan 15, 1-5 PM. Technician Mike Johnson will call 30 minutes before arrival.", "metadata": { "appointmentWindow": "1-5 PM", "technicianName": "Mike Johnson", "technicianPhone": "+1-555-999-8888", "confirmationNumber": "CONF-12345" } } ``` ### Send Reminders * 48 hours before appointment * 24 hours before appointment * 30 minutes before technician arrival ## Installation Quality ### Arrive On Time * Honor appointment windows strictly * Call if running late * Update order status if delays occur ### Complete Work Properly * Test all services before leaving * Verify customer satisfaction * Leave premises clean and organized * Provide account details and documentation ### Update Status Accurately ```json theme={null} { "status": "completed", "providerNotes": "Installation completed successfully. All services tested and operational. Customer account #12345 active.", "metadata": { "accountNumber": "12345", "completedDate": "2025-01-15", "technicianId": "TECH-789" } } ``` ## Rejection Management ### Be Specific About Reasons Don't just reject—explain why: ```json Bad Example theme={null} { "status": "rejected", "providerNotes": "Cannot fulfill order." } ``` ```json Good Example theme={null} { "status": "rejected", "providerNotes": "Service not available at this address. The building does not have fiber infrastructure. Cable internet up to 500 Mbps is available as an alternative. Contact us for details." } ``` ### Suggest Alternatives When rejecting, help resellers find solutions: * Recommend alternative services you offer * Suggest what's possible (if fiber isn't available, mention cable) * Provide contact info for special cases ### Common Rejection Reasons Track and address frequent issues: * **"Service area"**: Expand coverage or clarify boundaries * **"Technical limitations"**: Document building requirements * **"Duplicate order"**: Improve deduplication process * **"Credit check"**: Clarify credit requirements upfront ## Performance Metrics Monitor these key indicators: ### Acceptance Rate **Target**: > 85% Low acceptance rates indicate: * Offers listed in wrong service areas * Unclear offer descriptions * Technical limitations not documented ### Time to Schedule **Target**: \< 5 days from acceptance Customers expect quick scheduling. Long delays lead to cancellations. ### Completion Rate **Target**: > 95% High failure rates suggest: * Poor address verification * Scheduling issues * Technical problems ### Customer Satisfaction **Target**: 4.5+ stars Track feedback from resellers about customer experience. ## Automation Strategies ### Auto-Accept Where Possible For standardized services with clear availability: ```typescript theme={null} async function autoAcceptIfAvailable(order) { const isServiceable = await checkServiceAvailability(order.serviceAddress); if (isServiceable && order.offer.category === 'internet') { await updateOrderStatus(order.itemId, { status: 'accepted', providerNotes: 'Order auto-accepted. Customer will be contacted within 24 hours.', }); await scheduleCustomerContact(order); } } ``` ### Integrate with CRM/Scheduling Connect Offergrid to your existing systems: * Import orders automatically * Sync appointment scheduling * Update order status from your fulfillment system * Generate work orders automatically ### Set Up Webhooks Receive instant notifications: * New orders arrive * Reseller cancels order * Status updates needed See [Webhooks](/docs/providers/webhooks) for details. ## Communication Best Practices Use `providerNotes` to communicate clearly. Resellers relay updates to customers. If something goes wrong, update status immediately and explain what happened. Include phone numbers or email for customer questions about their specific order. Structure `metadata` the same way every time so resellers can parse it programmatically. If reseller includes notes (preferred contact times, specific instructions), acknowledge them in your response. ## Handling Edge Cases ### Duplicate Orders If a customer already has service: ```json theme={null} { "status": "rejected", "providerNotes": "Customer already has active service. Account #12345. Contact customer care to modify existing service." } ``` ### Address Outside Service Area Reject with specific boundary information: ```json theme={null} { "status": "rejected", "providerNotes": "Address is 0.3 miles outside our service area. We service ZIP codes 94101-94110. Contact us if service area expands." } ``` ### Technical Installation Issues If installation fails after acceptance: ```json theme={null} { "status": "failed", "providerNotes": "Installation failed. Building requires landlord approval for external wiring. Customer was informed and approved to proceed. Rescheduled for Jan 20 after approvals." } ``` ## Next Steps Understanding status progression How orders are received Automate your fulfillment workflow Real-time order notifications # Provider Overview Source: https://offergrid.io/docs/providers/index Start distributing your services through the Offergrid marketplace ## Welcome, Service Providers Offergrid helps you expand your distribution reach by connecting you with qualified reseller partners. Our AI-native, agent-friendly APIs are easy to use and flexible enough to work with any integration need—whether you offer internet or electricity services. Streamline partner management and order fulfillment through our platform. ## Why Use Offergrid? Access a network of reseller partners without building individual integrations or partnerships. Receive structured order data with all necessary customer information for quick fulfillment. Choose who can see and order your services—make offers public, limit to preferred partners, or select specific resellers. Manage offers and orders through our API or dashboard. Integrate with your existing systems. ## Getting Started Sign up at [offergrid.io](https://offergrid.io) and register your organization as a service provider. Generate a Team API Key from your dashboard settings to access the API. Publish a service offering with pricing, availability, and fulfillment details. Choose which resellers can see and order your services. Resellers will be able to browse your offerings and place orders for their customers. ## How It Works ### 1. Publish Service Offers Create detailed service offerings including: * Service details (name, category, pricing, and specifications) * Availability by location or market * Marketing language and assets to support offers * Disclosure and authorization documents required to submit orders ### 2. Manage Reseller Access Control offer visibility: * **Public**: All verified resellers can see and order * **Preferred Resllers**: Only your approved reseller list * **Selected Resellers**: Specific resellers you choose ### 3. Receive and Fulfill Orders When resellers place orders: * Get instant notification with customer details * Accept or reject based on submission rules you set for resellers * Update status through the fulfillment workflow * Keep resellers informed with automated notifications ## What You Can Do Create, update, and manage your service offerings Process and fulfill orders from resellers Control which resellers can access your offers Integrate Offergrid with your existing systems ## Need Help? Follow our step-by-step checklist Explore the Provider API endpoints Have questions? Contact us at [support@offergrid.io](mailto:support@offergrid.io) # Internet Offers Source: https://offergrid.io/docs/providers/internet-offers Publish internet plans with the structured .internet contract ## Overview Internet is a first-class service type on Offergrid. Rather than a free-form JSON blob, internet offers carry a single, structured **`internet`** object grouped into four sections — **speed**, **data**, **term**, and **disclosures** — including the FCC [Broadband Consumer Label](https://www.fcc.gov/broadbandlabels) fields. This page is the narrative guide. The field-by-field reference is published in the [API Reference](/docs/api-reference/introduction) as the `InternetContractWrite` and `InternetContract` schema components. ## Identifying an internet offer Every offer response carries a top-level **`serviceType`** discriminator that mirrors `category` — check either field: ```jsonc theme={null} { "id": "…", "serviceType": "internet", "category": "internet", "name": "Gigabit Fiber", "status": "active", "internet": { /* the contract, see below */ } } ``` Internet offers additionally include the grouped **`internet`** object. Other service types (`electricity`, `other`) get their own top-level key. ## The `internet` contract ```jsonc theme={null} "internet": { "speed": { "minBandwidthMbps": 100, // advertised plan range "maxBandwidthMbps": 1000, "connectionType": "fiber" // fiber | cable | dsl | satellite | fixed_wireless | 5g_home }, "data": { "capGb": 1024 // omit for an unlimited plan }, "term": { "length": "months_12", // no_contract | month_to_month | months_12 | months_24 | months_36 "earlyTerminationFee": 150, // dollars "earlyTerminationFeeNotes": "Prorated by months remaining" }, "disclosures": { // FCC Broadband Consumer Label "broadbandLabel": { "url": "https://example.com/broadband-label.pdf", "typicalDownload": 940, // Mbps "typicalUpload": 880, // Mbps "typicalLatency": 15, // ms "dataCapGb": 1024 }, "networkManagementUrl": "https://example.com/network-management" } } ``` All four sections and all fields are optional on write, **except** the [publish requirement](#publishing-requirements). Send only what you have; unspecified fields are left untouched on update. * **`minBandwidthMbps`** / **`maxBandwidthMbps`** — the marketed plan range, in Mbps. * **`connectionType`** — `fiber`, `cable`, `dsl`, `satellite`, `fixed_wireless`, or `5g_home`. The FCC "typical" speeds (what customers actually experience) live under `disclosures.broadbandLabel`. * **`capGb`** — monthly data cap in GB. **Omit for an unlimited plan.** * **`length`** — `no_contract`, `month_to_month`, `months_12`, `months_24`, or `months_36`. * **`earlyTerminationFee`** — cancellation fee in dollars. * **`earlyTerminationFeeNotes`** — free-text detail, e.g. "Prorated by months remaining". * **`broadbandLabel`** — `url`, plus the label's typical performance figures (`typicalDownload`, `typicalUpload` in Mbps, `typicalLatency` in ms) and `dataCapGb`. * **`networkManagementUrl`** — link to your network management practices disclosure. ## Creating an internet offer ```bash cURL theme={null} curl -X POST https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Gigabit Fiber", "category": "internet", "monthlyPrice": 79.99, "internet": { "speed": { "minBandwidthMbps": 100, "maxBandwidthMbps": 1000, "connectionType": "fiber" }, "data": { "capGb": 1024 }, "term": { "length": "months_12", "earlyTerminationFee": 150 }, "disclosures": { "broadbandLabel": { "url": "https://example.com/broadband-label.pdf", "typicalDownload": 940, "typicalUpload": 880, "typicalLatency": 15 }, "networkManagementUrl": "https://example.com/network-management" } } }' ``` ```typescript TypeScript theme={null} const response = await fetch('https://api.offergrid.io/provider/offers', { method: 'POST', headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Gigabit Fiber', category: 'internet', monthlyPrice: 79.99, internet: { speed: { minBandwidthMbps: 100, maxBandwidthMbps: 1000, connectionType: 'fiber' }, data: { capGb: 1024 }, term: { length: 'months_12', earlyTerminationFee: 150 }, disclosures: { broadbandLabel: { url: 'https://example.com/broadband-label.pdf', typicalDownload: 940, typicalUpload: 880, typicalLatency: 15, }, networkManagementUrl: 'https://example.com/network-management', }, }, }), }); const offer = await response.json(); ``` The response is the created offer with `serviceType: "internet"`, the projected `internet` object, and the standard offer fields. Unlike electricity, internet pricing is set directly via `monthlyPrice` — the contract does not derive it. ## Updating an internet offer Send only the sections you're changing. The `internet` object is **merged** onto the stored offer, so unrelated data is preserved: ```jsonc PATCH /provider/offers/{id} theme={null} { "internet": { "term": { "earlyTerminationFee": 0, "length": "no_contract" } } } ``` This changes only the term; the existing speed, data, and disclosures are untouched. The `internet` object is the **only** way to set an internet offer's speed, data, term, and disclosures — there is no flat/free-form alternative field to fall back to. ## Publishing requirements `POST /provider/offers/{id}/publish` validates the offer. For internet, the contract-specific rule is: `speed` must include `minBandwidthMbps`, `maxBandwidthMbps`, and a valid `connectionType`. If any are missing, publish returns `400` with structured `validationErrors` pointing at the `internetDetails` bandwidth/connection fields. General offer requirements — name, SKU, description, at least one market, etc. — also apply. ## Storage Internet offer data lives in a dedicated typed relation — there is no free-form JSON blob involved. Read and write everything internet-related through `.internet`. ## Quick reference | You want to… | Use | | ------------------------ | ---------------------------------------------------------------------------- | | Detect an internet offer | top-level `serviceType === "internet"` | | Read speed / connection | `internet.speed` | | Read data allowance | `internet.data.capGb` (omitted = unlimited) | | Read term / ETF | `internet.term` | | Read FCC label | `internet.disclosures.broadbandLabel` | | Set the offer | send `internet.speed` / `data` / `term` / `disclosures` on create/update | | Publish | ensure `speed` has min/max bandwidth + connectionType, then `POST …/publish` | ## Next steps The general offer create/publish flow The parallel structured contract for electricity Automate offer sync and order processing The `InternetContract` schema, field by field # Service Categories Source: https://offergrid.io/docs/providers/offer-categories Understanding service categories and category-specific requirements ## Supported Categories Offergrid supports three service categories: ## Internet Services **Category**: `internet` Internet offers are set through the structured [`.internet` contract](/docs/providers/internet-offers) — it models speed, data allowance, term, and the FCC Broadband Consumer Label directly, and is the only way to set an internet offer's service-specific fields. **Example**: ```json theme={null} { "name": "Gigabit Fiber Internet", "category": "internet", "monthlyPrice": 79.99, "internet": { "speed": { "minBandwidthMbps": 1000, "maxBandwidthMbps": 1000, "connectionType": "fiber" }, "term": { "length": "no_contract" } } } ``` ## Electricity Services **Category**: `electricity` Electricity offers are set through the structured [`.electricity` contract](/docs/providers/electricity-offers) — it models rate, term, plan, and Texas PUCT disclosures directly, with an itemized provider/utility charge breakdown. This is the only way to set an electricity offer's service-specific fields. **Example**: ```json theme={null} { "name": "100% Renewable Fixed Rate", "category": "electricity", "electricity": { "rate": { "type": "fixed", "charges": [ { "type": "perKwh", "owner": "provider", "label": "Energy Charge", "centsPerKwh": 12.0 } ] }, "term": { "length": "months_12" }, "plan": { "renewablePercentage": 100 } } } ``` ## Other Services **Category**: `other` For services that don't fit the standard categories, use the `other` category and provide detailed information in the `description` field. ## Category-Specific Best Practices Lead with speed tiers and technology type. Clearly state data caps or unlimited usage. Be transparent about rate structure and contract terms. Highlight renewable energy percentages. ## Next Steps Learn how to create offers Understand pricing models # Offer Visibility & Access Control Source: https://offergrid.io/docs/providers/offer-visibility Control which resellers can see and order your services ## Overview Offergrid gives you complete control over who can see and order your service offerings. Choose from public visibility, preferred partners, or selected resellers. ## Visibility Options ### Public Access **Who can see**: All verified resellers on the platform **Best for**: * Maximizing distribution reach * Commoditized services with standard pricing * Building brand awareness * Growing your partner network **How to enable**: Set the visibility to public when creating or updating an offer: ```json theme={null} { "name": "High-Speed Internet", "status": "active", "visibility": "public" } ``` Public visibility is the default setting for new offers. Simply publish an offer with `status: "active"` to make it available to all resellers. ### Preferred Partners **Who can see**: Only resellers you've added to your preferred partner list **Best for**: * Premium services with special pricing * Partners who meet quality standards * Maintaining service quality control * Relationship-based distribution **How to enable**: 1. Build your preferred partner list in the dashboard or via API 2. Set offer visibility to `preferred`: ```json theme={null} { "name": "Premium Service Package", "status": "active", "visibility": "preferred" } ``` All resellers on your preferred list will be able to see and order this offer. ### Selected Resellers **Who can see**: Specific resellers you choose for this offer **Best for**: * Exclusive partnerships * Market testing with select partners * Custom or negotiated offerings * Regional exclusives **How to enable**: Specify which reseller teams can access the offer: ```json theme={null} { "name": "Exclusive Regional Offer", "status": "active", "visibility": "selected", "allowedResellers": [ "reseller-team-id-1", "reseller-team-id-2" ] } ``` Only the resellers with matching team IDs will see this offer in their catalog. ## Managing Partner Lists ### Adding Preferred Partners Via dashboard: 1. Go to **Partners** → **Preferred Resellers** 2. Click **Add Partner** 3. Search for reseller by name or ID 4. Click **Add to Preferred List** Via API: ```json theme={null} POST /provider/partners/preferred { "resellerTeamId": "team-xyz-123" } ``` ### Removing Partners Remove partners from your preferred list: ```json theme={null} DELETE /provider/partners/preferred/{resellerTeamId} ``` Removed partners will immediately lose access to your preferred-only offers. ## Offer Status vs. Visibility Understand the difference: | Status | Visibility | Result | | ---------- | ----------- | --------------------------------------- | | `draft` | Any | **Hidden** from all resellers | | `active` | `public` | **Visible** to all verified resellers | | `active` | `preferred` | **Visible** to preferred partners only | | `active` | `selected` | **Visible** to specified resellers only | | `inactive` | Any | **Hidden** from all resellers | | `archived` | Any | **Permanently hidden** | An offer must have `status: "active"` AND appropriate visibility settings to be seen by resellers. Draft and inactive offers are never visible regardless of visibility settings. ## Use Cases ### Scenario 1: Launch a New Service **Goal**: Test with trusted partners before broad release **Strategy**: 1. Create offer with `visibility: "selected"` 2. Add 3-5 high-performing resellers 3. Monitor performance and feedback 4. Expand to `visibility: "preferred"` after validation 5. Eventually go `visibility: "public"` ### Scenario 2: Regional Exclusives **Goal**: Grant exclusive rights to one reseller per region **Strategy**: 1. Create separate offers for each region 2. Use `visibility: "selected"` for each 3. Assign one reseller per region offer 4. Track performance and adjust as needed ### Scenario 3: Tiered Partner Program **Goal**: Reward top performers with exclusive offers **Strategy**: 1. Standard offers: `visibility: "public"` 2. Enhanced offers: `visibility: "preferred"` 3. Premium offers: `visibility: "selected"` for top tier only 4. Promote resellers between tiers based on performance ### Scenario 4: Negotiated Pricing **Goal**: Offer custom pricing to specific partners **Strategy**: 1. Create custom offer for partner 2. Set `visibility: "selected"` 3. Add only that partner's team ID 4. Use clear `internalName` for tracking (e.g., "Internet-Partner-XYZ-Special") ## Visibility Best Practices Launch new offers to a small group first. Validate quality and pricing before opening to broader audiences. When removing access or changing visibility, notify affected partners in advance to maintain good relationships. Build preferred partner lists based on performance metrics: order volume, fulfillment success, customer satisfaction. Monitor which visibility settings drive the most orders. Adjust strategy based on data. Make clear what resellers need to do to gain access to preferred or selected offers. Audit your partner lists quarterly. Remove inactive partners, promote high performers. ## Next Steps Managing reseller partnerships Setting up preferred partner programs Learn about creating offers View complete API documentation # Order Lifecycle & Status Updates Source: https://offergrid.io/docs/providers/order-workflow Understanding order status progression and when to update ## Order Status Lifecycle Orders progress through several states from initial submission to completion: ``` pending → submitted_to_provider → accepted → scheduled → in_progress → completed → active ``` Or, if something goes wrong: ``` pending → rejected accepted → cancelled in_progress → failed ``` ## Status Definitions ### Initial States **`pending`** * Order just created by reseller * Awaiting provider review * **Your action**: Review and accept or reject **`submitted_to_provider`** * Order sent to your fulfillment system * Awaiting confirmation * **Your action**: Confirm receipt and accept or reject ### Accepted States **`accepted`** * You've confirmed you can fulfill the order * Customer info verified * **Your action**: Schedule installation or activation **`scheduled`** * Installation or activation date set * Customer has been contacted * **Your action**: Complete the installation on schedule **`in_progress`** * Installation or setup actively happening * Technician on-site or service being activated * **Your action**: Complete installation and activate service ### Completion States **`completed`** * Installation finished successfully * Service activated and tested * **Your action**: None (terminal state) **`active`** * Service is live and active * Ongoing service subscription * **Your action**: Monitor for issues, provide support ### Failure States **`rejected`** * Unable to fulfill the order * Clear reason provided * **Your action**: None (terminal state) **`cancelled`** * Order cancelled after acceptance * By provider or reseller request * **Your action**: None (terminal state) **`failed`** * Installation or activation failed * Technical or logistical issues * **Your action**: Document reason, coordinate with reseller on resolution ## Updating Order Status ### Via API Update order item status with a PATCH request: ```bash theme={null} curl -X PATCH "https://api.offergrid.io/provider/orders/ITEM_ID/status" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "scheduled", "providerNotes": "Installation scheduled for Tuesday, Jan 15 between 1-5 PM", "scheduledFor": "2025-01-15T13:00:00Z", "metadata": { "technicianName": "Mike Johnson", "technicianPhone": "+1-555-999-8888" } }' ``` ### Required vs. Optional Fields **Always required**: * `status` - The new status value **Recommended**: * `providerNotes` - Human-readable update for reseller * `scheduledFor` - When status is `scheduled` * `metadata` - Additional context (tracking numbers, appointment details) ## Common Workflows ### Workflow 1: Standard Installation Status: `pending` Action: Review order details and verify service availability Status: `accepted` ```json theme={null} { "status": "accepted", "providerNotes": "Order accepted. Customer will be contacted within 24 hours to schedule installation." } ``` Status: `scheduled` ```json theme={null} { "status": "scheduled", "providerNotes": "Installation scheduled for Jan 15, 1-5 PM", "scheduledFor": "2025-01-15T13:00:00Z", "metadata": { "appointmentWindow": "1-5 PM", "technicianName": "Mike Johnson" } } ``` Status: `in_progress` ```json theme={null} { "status": "in_progress", "providerNotes": "Technician on-site, installation in progress" } ``` Status: `completed` or `active` ```json theme={null} { "status": "active", "providerNotes": "Service activated successfully. Customer account #12345", "metadata": { "accountNumber": "12345", "activationDate": "2025-01-15" } } ``` ### Workflow 2: Instant Activation (No Installation) For services that don't require physical installation: ``` pending → accepted → active ``` Example (electricity switches, other no-installation services): ```json theme={null} { "status": "active", "providerNotes": "Policy activated immediately. Policy #POL-98765", "metadata": { "policyNumber": "POL-98765", "effectiveDate": "2025-01-02" } } ``` ### Workflow 3: Rejection If you cannot fulfill: ```json theme={null} { "status": "rejected", "providerNotes": "Service not available at this address. Building does not have fiber infrastructure. Cable internet available as alternative." } ``` ### Workflow 4: Cancellation After Acceptance If an accepted order must be cancelled: ```json theme={null} { "status": "cancelled", "providerNotes": "Customer requested cancellation before installation. No fees charged." } ``` ## Status Update Best Practices Update within hours of status changes, not days. Real-time updates build trust with resellers. Use `providerNotes` to explain what happened and what happens next. Resellers relay this info to customers. When setting status to `scheduled`, always include the date/time and appointment window. Add account numbers, work order IDs, or tracking numbers in `metadata` for future reference. If rejecting or failing an order, clearly explain why and suggest alternatives when possible. Structure `metadata` consistently so resellers can parse and use it programmatically. ## Automated Status Updates Integrate Offergrid status updates into your existing systems: ```typescript theme={null} // Example: Update status when your CRM changes async function onInstallationScheduled(workOrder) { await fetch(`https://api.offergrid.io/provider/orders/${workOrder.offergridItemId}/status`, { method: 'PATCH', headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ status: 'scheduled', providerNotes: `Installation scheduled for ${workOrder.appointmentDate}`, scheduledFor: workOrder.appointmentDate, metadata: { workOrderId: workOrder.id, technicianId: workOrder.technicianId, }, }), }); } ``` ## Monitoring Order Progress Track order metrics in your dashboard: * **Acceptance rate**: % of orders accepted vs. rejected * **Time to accept**: How quickly you respond to new orders * **Completion rate**: % of accepted orders successfully completed * **Average fulfillment time**: Days from acceptance to activation ## Next Steps Understanding incoming orders Tips for smooth fulfillment View complete API documentation Automate status updates # Preferred Reseller Programs Source: https://offergrid.io/docs/providers/preferred-resellers Setting up and managing preferred partner lists ## Overview Preferred reseller programs let you reward top-performing partners with exclusive benefits while maintaining quality control over who can access your services. ## Why Create a Preferred Program? ### Benefits for Providers * **Quality control**: Work with partners who meet your standards * **Better margins**: Reduce costs by working with efficient partners * **Predictable volume**: Build recurring business with known partners * **Competitive advantage**: Attract top resellers with exclusive offerings ### Benefits for Resellers * **Exclusive access**: Services not available to general public * **Better pricing**: Improved margins on premium offers * **Priority support**: Faster response times, dedicated contacts * **Early access**: New services before broad launch ## Setting Up Your Program ### Step 1: Define Criteria Decide what makes a reseller "preferred": **Quantitative metrics**: * Minimum monthly order volume (e.g., 10+ orders/month) * Acceptance rate threshold (e.g., 90%+) * Completion rate (e.g., 95%+) * Customer satisfaction score (e.g., 4.5+ stars) * Tenure on platform (e.g., 3+ months) **Qualitative factors**: * Industry expertise * Geographic coverage * Target customer segments * Brand alignment * Communication quality Start with objective metrics first. Add subjective criteria only for strategic partnerships. ### Step 2: Identify Candidates Review existing resellers against your criteria: ```sql theme={null} -- Example query for identifying candidates SELECT reseller_id, COUNT(*) as total_orders, AVG(acceptance_rate) as avg_acceptance, AVG(completion_rate) as avg_completion, AVG(customer_rating) as avg_rating FROM orders WHERE created_at >= DATE_SUB(NOW(), INTERVAL 90 DAY) GROUP BY reseller_id HAVING total_orders >= 10 AND avg_acceptance >= 0.90 AND avg_completion >= 0.95 AND avg_rating >= 4.5 ORDER BY total_orders DESC; ``` ### Step 3: Add to Preferred List Via API: ```bash theme={null} curl -X POST "https://api.offergrid.io/provider/partners/preferred" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "resellerTeamId": "team-abc-123", "tier": "preferred", "effectiveDate": "2025-01-01", "notes": "High-volume partner with excellent completion rates" }' ``` Via Dashboard: 1. Navigate to **Partners** → **Preferred Resellers** 2. Click **Add Partner** 3. Search for reseller by name or ID 4. Add notes about why they qualify 5. Click **Add to List** ### Step 4: Create Preferred Offers Create offers visible only to preferred partners: ```json theme={null} { "name": "Premium Business Internet - Preferred Partner Pricing", "category": "internet", "status": "active", "visibility": "preferred", "monthlyPrice": 74.99, "internalName": "Bus-Internet-Preferred", "description": "Special pricing for preferred partners only" } ``` ## Program Tiers Many providers use multi-tier programs: ### Single Tier: Preferred **Simple approach**: You're either preferred or you're not. **Benefits**: * Easy to understand * Simple to manage * Clear entry criteria **Example**: * **Standard**: Public offers at regular pricing * **Preferred**: Exclusive offers with 10% better pricing ### Multi-Tier: Bronze/Silver/Gold **Graduated approach**: Multiple levels with increasing benefits. **Benefits**: * Motivates progression * Rewards top performers more * Flexibility in partnerships **Example tiers**: ``` Bronze (All resellers): - Public offers - Standard pricing - Standard support Silver (10+ orders/month, 90%+ completion): - Silver-exclusive offers - 5% better pricing - Priority support Gold (50+ orders/month, 95%+ completion): - Gold-exclusive offers - 10% better pricing - Dedicated account manager - Co-marketing opportunities ``` ## Managing the Program ### Regular Reviews **Monthly**: * Check for partners who now qualify * Identify partners falling below standards * Review program effectiveness **Quarterly**: * Audit all preferred partners * Adjust criteria if needed * Send performance reports to partners ### Adding Partners Notify partners when adding them: ``` Subject: Welcome to [Your Company] Preferred Partner Program Congratulations! Based on your outstanding performance, you've been selected for our Preferred Partner Program. As a preferred partner, you now have access to: - Exclusive service offers - 10% better pricing on all services - Priority support response - Early access to new services Your partnership metrics: - Orders last quarter: 45 - Acceptance rate: 96% - Completion rate: 98% - Customer rating: 4.8/5 Thank you for your excellent work. We look forward to continued growth together! ``` ### Removing Partners If a partner falls below standards: 1. **Warning**: Notify them they're at risk 2. **Grace period**: Give 30-60 days to improve 3. **Removal**: Remove from list if no improvement 4. **Communication**: Explain why and how to regain status ```bash theme={null} # Remove from preferred list curl -X DELETE "https://api.offergrid.io/provider/partners/preferred/team-abc-123" \ -H "x-api-key: YOUR_API_KEY" ``` ## Exclusive Offers for Preferred Partners ### Better Pricing Offer same service at different price points: ```json theme={null} // Public offer { "name": "Business Internet 1000 Mbps", "visibility": "public", "monthlyPrice": 99.99 } // Preferred offer { "name": "Business Internet 1000 Mbps - Preferred Pricing", "visibility": "preferred", "monthlyPrice": 89.99, "internalName": "Bus-1000-Preferred" } ``` ### Premium Services Services only available to preferred partners: ```json theme={null} { "name": "Enterprise Fiber - White Label", "visibility": "preferred", "monthlyPrice": 299.99, "description": "Premium enterprise fiber with white-label options for your brand. Preferred partners only." } ``` ### Early Access Launch new services to preferred partners first: ```json theme={null} { "name": "New 5G Home Internet - Early Access", "visibility": "preferred", "monthlyPrice": 79.99, "metadata": { "earlyAccess": true, "publicLaunchDate": "2025-03-01" } } ``` ## Program Communication ### Partner Portal Provide a partner portal or dashboard showing: * Current tier status * Performance metrics * Next tier requirements * Exclusive offers available ### Regular Updates **Monthly newsletter**: * New exclusive offers * Performance highlights * Tips for improving metrics * Industry insights **Quarterly reviews**: * Formal performance scorecards * Future planning * Feedback sessions ## Measuring Success Track program impact: ### Partner Metrics * **Enrollment**: # of partners in preferred tier * **Retention**: % of preferred partners retained quarter-over-quarter * **Graduation rate**: # of partners moving to preferred per month ### Business Metrics * **Order volume**: Total orders from preferred vs. public partners * **Quality**: Acceptance and completion rates by tier * **Revenue**: Revenue per partner by tier * **Efficiency**: Fulfillment cost per order by tier ### ROI Analysis Compare costs vs. benefits: **Costs**: * Reduced pricing (margin loss) * Support resources * Program management time **Benefits**: * Higher order volume * Better completion rates * Lower customer acquisition cost * Predictable revenue ## Best Practices Use measurable metrics (order volume, completion rate) as primary qualifiers. Minimize subjective decisions. Make it clear what partners need to do to qualify. Transparency motivates improvement. Audit partner status quarterly. Remove partners who no longer meet standards. Ensure preferred status actually helps partners succeed. Better pricing, exclusive access, or priority support. When adding, removing, or changing tier criteria, communicate clearly and early. Launch with a small group of top performers. Expand as you refine the program. ## Next Steps Managing partner relationships Control who sees your offers Learn about creating offers View complete API documentation # Pricing Strategies Source: https://offergrid.io/docs/providers/pricing-strategies Understanding different pricing models and how to set them up ## Pricing Types Offergrid supports four pricing models to match your business needs: ## Fixed Pricing **Type**: `fixed` A simple, predictable monthly price that doesn't change based on usage or customer characteristics. **Best for**: * Standard service packages * Simple offerings with consistent costs * When you want predictable revenue **Example**: ```json theme={null} { "pricingType": "fixed", "monthlyPrice": 59.99 } ``` **Use case**: "High-Speed Internet 1000 Mbps for \$59.99/month" ## Variable Pricing **Type**: `variable` Pricing that changes based on usage, customer characteristics, or market conditions. **Best for**: * Usage-based services (energy, metered internet) * Services with fluctuating costs * Custom quotes per customer **Example**: ```json theme={null} { "pricingType": "variable", "monthlyPrice": null, "description": "Pricing varies based on usage. Estimated $0.12 per kWh." } ``` **Use case**: Energy plans with per-kWh pricing For variable pricing, use the `description` field to explain how pricing is calculated. The `monthlyPrice` can be `null` or an estimated average. ## Tiered Pricing **Type**: `tiered` Different price points based on service level, volume, or features. **Best for**: * Multiple service tiers (Good/Better/Best) * Volume-based pricing * Feature-differentiated packages **Example**: ```json theme={null} { "pricingType": "tiered", "monthlyPrice": 99.99, "description": "Premium tier. Basic: $49.99, Standard: $79.99, Premium: $99.99", "metadata": { "tier": "premium", "tiers": [ { "name": "Basic", "price": 49.99 }, { "name": "Standard", "price": 79.99 }, { "name": "Premium", "price": 99.99 } ] } } ``` **Use case**: Internet plans at 100 Mbps, 500 Mbps, and 1000 Mbps speeds ## Custom Pricing **Type**: `custom` Completely custom pricing requiring quotes, negotiations, or complex calculations. **Best for**: * Enterprise services * Complex bundled offerings * Services requiring site surveys or assessments **Example**: ```json theme={null} { "pricingType": "custom", "monthlyPrice": null, "description": "Contact for custom quote. Pricing based on property size and required coverage." } ``` **Use case**: Enterprise electricity contracts, custom internet builds ## Additional Fees Beyond monthly pricing, include other costs in your offer: ### One-Time Fees * **Installation/Setup**: `installationFee` * **Equipment**: `equipmentFee` * **Activation**: `activationFee` * **Deposit**: `depositRequired` ### Recurring Add-Ons * **Equipment Rental**: Monthly router/modem fees * **Premium Features**: Enhanced support, extra services * **Overage Charges**: Usage beyond included limits **Example with fees**: ```json theme={null} { "name": "Professional Security System", "monthlyPrice": 49.99, "pricingType": "fixed", "metadata": { "installationFee": 199.00, "equipmentFee": 0, "contractLength": "36 months", "earlyTerminationFee": 300.00 } } ``` ## Promotional Pricing Use the `metadata` field to track promotional pricing: ```json theme={null} { "monthlyPrice": 39.99, "metadata": { "regularPrice": 59.99, "promotionalPeriod": "First 12 months", "promoEndDate": "2025-12-31" }, "marketingHeadline": "Special offer: $39.99/mo for the first year!" } ``` ## Pricing Best Practices Include ALL fees upfront—installation, equipment, activation, early termination. Hidden fees create friction and reduce conversion. Explain what's included and what costs extra. Make it easy for resellers to quote customers accurately. Update offers promptly when pricing changes. Outdated pricing leads to order failures and unhappy customers. Use the `marketingHeadline` and `marketingDescription` fields to showcase special pricing clearly. Research market rates for similar services. Competitive pricing helps resellers choose your offers. Create multiple offers at different price points to see what resonates with resellers and customers. ## Regional Pricing If you need different pricing by region: 1. Create separate offers for each region 2. Use `internalName` to track variations (e.g., "Internet-1000-West", "Internet-1000-East") 3. Set appropriate availability zones for each **Example**: ```json theme={null} // West Coast offer { "name": "Gigabit Internet", "internalName": "Gigabit-West", "monthlyPrice": 79.99, "metadata": { "region": "West" } } // East Coast offer { "name": "Gigabit Internet", "internalName": "Gigabit-East", "monthlyPrice": 69.99, "metadata": { "region": "East" } } ``` ## Next Steps Learn how to create offers Category-specific requirements # Provider Quick Start Source: https://offergrid.io/docs/providers/quickstart Get up and running as a service provider in minutes ## Provider Onboarding Checklist Follow these steps to start distributing your services through Offergrid. ### 1. Account Setup 1. Visit [offergrid.io](https://offergrid.io) 2. Click **Sign Up** 3. Choose **Provider** as your organization type 4. Complete your company profile Provide business verification details to gain access to the provider platform. This typically takes 1-2 business days. 1. Go to **Settings** → **API Keys** 2. Click **Generate New Key** 3. Save your key securely (you won't see it again!) 4. Store it in your environment variables ### 2. Create Your First Offer 1. Navigate to **Offers** in the sidebar 2. Click **Create New Offer** 3. Fill in offer details: * Name and category * Pricing and billing * Service specifications * Availability zones 4. Add marketing content and images 5. Click **Save Draft** or **Publish** ```bash theme={null} curl -X POST https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "High-Speed Internet 1000 Mbps", "category": "internet", "status": "active", "monthlyPrice": 59.99, "description": "Lightning-fast fiber internet", "serviceSpecificData": { "downloadSpeed": "1000 Mbps", "uploadSpeed": "1000 Mbps", "connectionType": "Fiber" } }' ``` See the full [API documentation](/docs/api-reference/introduction) for all available fields. ### 3. Configure Offer Visibility Decide who can see your offer: * **All Resellers**: Public visibility (default) * **Preferred Partners**: Only approved resellers * **Selected Resellers**: Specific partners If using Preferred or Selected visibility, add approved resellers to your partner list. Once you're ready, change the status to `active` to make it available to resellers. ### 4. Set Up Order Fulfillment Define how you want to receive order details: * Submission URL (webhook endpoint) * Required form fields * Custom metadata fields Create a test order to ensure your fulfillment system is ready: 1. Use a test reseller account or ask support 2. Place a test order 3. Verify you receive the order data 4. Practice updating order status Configure how you want to be notified about new orders: * Email notifications * Webhook callbacks * Dashboard alerts ### 5. Start Receiving Orders You're all set! Resellers can now browse your offers and place orders. Monitor your dashboard for: * New order notifications * Order status by fulfillment stage * Partner activity and performance ## Next Steps Learn about creating and managing service offerings Understand the order fulfillment workflow Control reseller access to your offers Integrate with your existing systems ## Common Questions Business verification typically takes 1-2 business days. You'll receive an email when your account is approved. Yes! Create offers with `draft` status and work with our support team to test the order flow before making offers public. You can update offers anytime via the dashboard or API. Changes take effect immediately. Use the `availableZipCodes` or `serviceArea` fields to specify where each offer is available. ## Need Help? Contact us at [support@offergrid.io](mailto:support@offergrid.io) or check out our [API documentation](/docs/api-reference/introduction). # Receiving Orders Source: https://offergrid.io/docs/providers/receiving-orders Understanding incoming orders from resellers ## Overview When resellers place orders for your services, you'll receive structured order data with all the information needed to fulfill the customer request. ## How You Receive Orders ### Dashboard Notifications View all incoming orders in your provider dashboard: 1. Navigate to **Orders** in the sidebar 2. See new orders marked as `pending` or `submitted_to_provider` 3. Click on an order to view full details 4. Take action (accept, reject, schedule) ### API Polling Retrieve orders programmatically: ```bash theme={null} curl -X GET "https://api.offergrid.io/provider/orders?status=pending" \ -H "x-api-key: YOUR_API_KEY" ``` Filter by status to get orders that need attention: * `pending` - Newly submitted, awaiting acceptance * `submitted_to_provider` - Sent to your fulfillment system * `accepted` - Accepted and in progress ### Webhook Notifications Set up webhooks to receive real-time notifications when new orders arrive: ```json theme={null} { "event": "order.created", "orderId": "ord-123-abc", "itemId": "item-456-def", "offerId": "off-789-ghi", "timestamp": "2025-01-02T10:00:00Z" } ``` See [Webhooks](/docs/providers/webhooks) for setup instructions. ## Order Structure Each order contains: ### Order Item Details ```json theme={null} { "id": "item-456-def", "orderId": "ord-123-abc", "offerId": "off-789-ghi", "offerName": "High-Speed Internet 1000 Mbps", "status": "pending", "createdAt": "2025-01-02T10:00:00Z" } ``` ### Customer Information ```json theme={null} { "customerInfo": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+1-555-123-4567" } } ``` ### Service Address ```json theme={null} { "serviceAddress": { "street": "123 Main St", "city": "San Francisco", "state": "CA", "zipCode": "94102", "country": "US" } } ``` ### Additional Details ```json theme={null} { "notes": "Customer prefers afternoon installations", "metadata": { "referralSource": "property-listing", "unitNumber": "4B", "moveInDate": "2025-01-15" } } ``` ## Order Workflow When you receive an order: Check customer information, service address, and any special notes from the reseller Confirm that the service is available at the customer's location Update the order status to `accepted` if you can fulfill it, or `rejected` if not If accepted, schedule installation or activation and update status to `scheduled` After successful installation, update status to `completed` or `active` ## Accepting Orders To accept an order: ```bash theme={null} curl -X PATCH "https://api.offergrid.io/provider/orders/ITEM_ID/status" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "accepted", "providerNotes": "Order accepted. We will contact customer to schedule installation." }' ``` Include helpful notes in `providerNotes` to keep resellers informed about next steps. ## Rejecting Orders If you cannot fulfill an order, reject it with a clear reason: ```bash theme={null} curl -X PATCH "https://api.offergrid.io/provider/orders/ITEM_ID/status" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "rejected", "providerNotes": "Service not available at this address. Building does not have fiber infrastructure." }' ``` Common rejection reasons: * Service not available at location * Address outside service area * Technical limitations (building wiring, line of sight) * Credit check failure * Duplicate order ## Order Filtering Filter orders by status to focus on what needs attention: ```bash theme={null} # Get pending orders GET /provider/orders?status=pending # Get orders needing scheduling GET /provider/orders?status=accepted # Get active services GET /provider/orders?status=active ``` ## Best Practices Accept or reject orders within 24 hours. Fast response times improve reseller satisfaction and customer experience. Double-check service addresses before accepting. Address errors are a common cause of fulfillment delays. When rejecting, explain why so resellers can address issues or find alternative solutions. When accepting, tell resellers what happens next and when to expect follow-up. Use webhooks to integrate orders into your fulfillment systems automatically. Track incoming order patterns to forecast capacity needs and staffing. ## Automated Order Processing For high-volume providers, consider automating order acceptance: ```typescript theme={null} // Example: Auto-accept if service is available async function processNewOrder(orderId: string) { const order = await getOrderDetails(orderId); const available = await checkServiceAvailability(order.serviceAddress); if (available) { await updateOrderStatus(orderId, { status: 'accepted', providerNotes: 'Auto-accepted. Customer will be contacted within 24 hours.', }); // Trigger internal fulfillment workflow await scheduleInstallation(order); } else { await updateOrderStatus(orderId, { status: 'rejected', providerNotes: 'Service not available at this location.', }); } } ``` ## Next Steps Understanding the complete fulfillment lifecycle Tips for smooth order fulfillment Set up real-time order notifications View complete API documentation # Managing Reseller Relationships Source: https://offergrid.io/docs/providers/reseller-relationships Building and maintaining partnerships with resellers ## Overview Strong reseller partnerships drive sustained growth. Offergrid gives you tools to identify, nurture, and manage relationships with your distribution partners. ## Types of Reseller Partnerships ### Public Distribution **Who**: All verified resellers on Offergrid **Best for**: * Maximizing order volume * Brand awareness * Standard commodity services * Expanding market reach **Management**: Minimal required. Monitor order quality and address issues as they arise. ### Preferred Partners **Who**: Resellers who meet your quality and performance standards **Best for**: * Mid-tier services requiring some expertise * Partners who consistently deliver * Building recurring business * Balancing reach and quality **Management**: Regular performance reviews, periodic communication, special pricing or incentives. ### Strategic Partners **Who**: Top-performing resellers with exclusive access **Best for**: * Premium or complex services * High-value contracts * Regional exclusives * Long-term commitments **Management**: Dedicated account managers, custom pricing, co-marketing, quarterly business reviews. ## Building Your Partner Network ### Step 1: Start Broad Launch with public offers to: * Discover which resellers are interested * Test market demand * Identify high performers Monitor early orders to see who's sending quality business. ### Step 2: Identify Top Performers Track metrics like: * **Order volume**: Total orders per month * **Order quality**: Acceptance rate, completion rate * **Customer satisfaction**: Feedback from end customers * **Response time**: How quickly they respond to issues ### Step 3: Create Preferred Tier Move top performers to a preferred partner list: ```bash theme={null} POST /provider/partners/preferred { "resellerTeamId": "team-abc-123", "tier": "preferred", "notes": "High-volume partner with 95% completion rate" } ``` Give preferred partners: * Access to exclusive offers * Better pricing * Priority support * Early access to new services ### Step 4: Develop Strategic Partnerships For your very best partners: * Create custom offers with negotiated pricing * Use `visibility: "selected"` for exclusive access * Provide dedicated support contacts * Collaborate on marketing ## Partner Communication ### Regular Check-Ins **Monthly** (preferred partners): * Review order metrics * Discuss upcoming services * Address any issues * Share market insights **Quarterly** (strategic partners): * Formal business reviews * Performance scorecards * Planning for next quarter * Contract renewals or adjustments ### Proactive Updates Notify partners when: * New services launch * Pricing changes * Service area expansions * Promotional offers available * Platform updates or downtime ### Feedback Loops Ask for input on: * What services they need * Which ZIP codes have high demand * Competitive offerings * Process improvements ## Performance Tracking Monitor these partner metrics: ### Order Metrics * **Total orders**: Volume over time * **Acceptance rate**: % of their orders you accept * **Completion rate**: % successfully fulfilled * **Average order value**: Revenue per order ### Quality Metrics * **Customer satisfaction**: End-customer feedback * **Cancellation rate**: Orders cancelled before completion * **Address accuracy**: Quality of customer information * **Response time**: Speed of partner communication ### Growth Metrics * **Month-over-month growth**: Order volume trends * **New vs. repeat**: Customer acquisition patterns * **Service mix**: Which services they prefer ## Partner Tiers & Incentives ### Tier Structure Example **Bronze** (All resellers): * Standard pricing * Public offers * Standard support **Silver** (Preferred partners): * 5% better pricing * Early access to new services * Priority support queue * Monthly check-ins **Gold** (Strategic partners): * 10% better pricing * Exclusive offers * Dedicated account manager * Quarterly business reviews * Co-marketing opportunities ### Promotion Criteria Define clear requirements for tier advancement: ``` Bronze → Silver: - 10+ orders per month - 90%+ acceptance rate - 4.5+ star customer rating - 3 months tenure Silver → Gold: - 50+ orders per month - 95%+ acceptance rate - 4.8+ star customer rating - 12 months tenure - Minimum revenue threshold ``` ## Managing Problem Partners ### Address Issues Early If a partner has: * High cancellation rates * Poor address quality * Customer complaints * Slow response times **Action**: Reach out proactively to discuss and resolve. ### Progressive Response 1. **First issue**: Friendly email or call to understand what happened 2. **Repeat issues**: Formal conversation about expectations 3. **Ongoing problems**: Remove from preferred list 4. **Serious violations**: Block access to specific or all offers ### Documentation Keep notes on partner interactions: ```json theme={null} { "resellerTeamId": "team-xyz-789", "tier": "silver", "notes": [ { "date": "2025-01-15", "type": "performance_review", "summary": "Discussed high cancellation rate (18%). Partner implementing new address validation process." } ] } ``` ## Exclusive Partnerships ### Regional Exclusives Grant one partner exclusive rights in a region: 1. Create region-specific offer 2. Set `visibility: "selected"` 3. Add only that partner 4. Define performance requirements 5. Set review period (quarterly or annually) **Example agreement terms**: * Minimum monthly volume (20 orders) * Performance standards (95% completion rate) * Contract term (12 months) * Renewal criteria ### Service Exclusives Give a partner exclusive rights to a specific service type: ```json theme={null} { "name": "Premium Business Internet", "visibility": "selected", "allowedResellers": ["partner-abc-123"], "metadata": { "exclusiveUntil": "2025-12-31", "minimumMonthlyOrders": 15 } } ``` ## Best Practices Let actual order performance guide tier assignments, not personal relationships. Publish clear requirements so all partners know how to advance. When promoting or demoting partners, explain why and what changed. Ensure top performers get tangible benefits—better pricing, exclusive access, dedicated support. Apply standards fairly across all partners. Don't play favorites. Audit partner performance quarterly. Market conditions and partner circumstances change. ## Next Steps Setting up preferred partner programs Control who can see your offers Managing orders from partners View complete API documentation # Serviceability Integration Source: https://offergrid.io/docs/providers/serviceability-integration Expose one endpoint and Offergrid shows address-level availability and live pricing for your offers ## The whole thing in five sentences 1. You publish offers on Offergrid, each covering a list of ZIP codes. 2. ZIP codes are too coarse when availability is really decided building by building, so you expose one endpoint that answers *"can you serve this exact address, and at what price?"* 3. We call it the moment a shopper types a full address, and use your answer to confirm or hide your offers and to show your real price. 4. We call it once more at checkout, so nobody orders something you can't actually deliver. 5. The order lands in your Offergrid dashboard and, if you want, as a signed webhook to your own system. **In plain words:** someone knocks on your door holding an address. You say yes or no. If yes, you say what you can sell them and for how much. That's the entire integration. ```mermaid theme={null} sequenceDiagram participant C as Shopper participant O as Offergrid participant Y as Your side Note over C,Y: While the shopper is browsing C->>O: Enters a full street address
line1 · city · state · ZIP O->>Y: POST the address Y-->>O: serviceable + products Note over O: Answer cached 24h per address.
Each offer matched by product key. O-->>C: Offer cards, priced per offer
your real price, not the list price Note over C,Y: At checkout — the same endpoint, cache bypassed C->>O: Places the order O->>Y: Re-check the same address Y-->>O: Final yes / no + price Note over O: A gated "no" stops the order here. O->>O: Order created
your verified price stored on it O->>Y: Signed webhook to your fulfillment system ``` The whole integration. Everything in the **Your side** lane is what you build — one endpoint, plus whatever already receives your orders. Note that the same endpoint is called twice: once while the shopper is browsing, once at checkout with the cache deliberately bypassed. ## Why ZIP codes aren't enough Most offers are listed against a ZIP-code footprint: if the customer's ZIP is in your service area, the offer shows. That is coarse for services where availability is decided at the individual address — wired internet, fixed wireless, anything with a physical network path to the building. A ZIP code can contain 20,000 homes when you only reach 6,000 of them. Listing the offer across the whole ZIP shows it to people you can't serve; listing it nowhere hides it from people you can. Both cost you orders. A **serviceability integration** is how you get out of that trade. **In plain words:** the ZIP code is the neighborhood. The endpoint is the actual house. This is optional. Offers without a serviceability source work exactly as they always have — ZIP-level availability and list pricing. ### What you set up first Three things come before serviceability matters at all. All are ordinary product setup in the dashboard, and none need engineering. | What | What it is | Who does it | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | | **Offers** | One per thing a customer can buy — "Fiber 1 Gig", "Fiber 500". Name, category, list price, promo price, contract terms, install fee. | You, in the dashboard | | **Service areas** | Where the offer is broadly available, as ZIP codes, states, or a drawn map area. This is the coarse footprint. | You, in the dashboard | | **Visibility** | Who can sell it: every reseller on Offergrid, your preferred partners only, or a hand-picked list. | You, per offer | ## Three ways to onboard You expose an endpoint shaped like the contract below. Offergrid-side setup is one integration record and one secret. **Fastest path, no mapping work.** Your API stays exactly as it is. We describe it in the integration's config — which fields carry the address, where the products live in your response. No code on either side. For APIs that can't be expressed as a field map (multi-step handshakes, session tokens, non-JSON payloads). Engineering-scoped — talk to us early. Tier 2 covers most existing serviceability APIs, so **you do not need to build anything new to integrate**. Tier 1 exists because it is the cheapest to stand up and the easiest to support: if you're building the endpoint from scratch anyway, build it to this shape and the entire integration is configuration. The rest of this page describes the tier-1 contract. [Mapping an existing API](#mapping-an-existing-api-tier-2) at the end covers tier 2. ## The contract ### Request Offergrid sends a JSON `POST` from its servers — never from the shopper's browser, so your endpoint is never exposed to end users. If it requires source-IP allow-listing, contact support before you build, so we can confirm what we can commit to. ```http theme={null} POST /serviceability HTTP/1.1 Host: api.example.com Content-Type: application/json Authorization: ``` ```json theme={null} { "line1": "1600 Pennsylvania Ave NW", "line2": "Apt 4", "city": "Washington", "state": "DC", "zipCode": "20500", "country": "US" } ``` | Field | Type | Notes | | --------- | ------ | --------------------------------------------------------------------------------------------------------------------- | | `line1` | string | Street address. Always sent. | | `line2` | string | Unit/apt. **Omitted from the body entirely when the shopper left it blank** — treat absent as empty, not as an error. | | `city` | string | Always sent. | | `state` | string | Two-letter US state code. Always sent. | | `zipCode` | string | 5-digit ZIP. Always sent. | | `country` | string | ISO country code, typically `US`. Omitted when unknown. | We only call once the address is complete enough to be worth asking about — by default `line1`, `city`, `state`, and `zipCode` must all be present. On ZIP-only surfaces (catalog browse, map search) the integration stays dormant and your offers fall back to their ZIP footprint. That threshold is configurable per integration. **Authentication** is a single static header: you name the header, we send a secret value in it. The value is sent verbatim, so if your scheme needs a prefix (`Bearer …`, `Token …`), include it in the secret itself. The secret is held as a platform environment variable on our side and is **never stored in our database** or shown in the dashboard. `POST` is required. The connector sends the address in the request body, so a `GET`-only endpoint has no way to receive it. ### Response Return `200` with this JSON: ```json theme={null} { "serviceable": true, "products": [ { "key": "fiber-1g", "name": "Fiber 1 Gig", "technology": "fiber", "monthlyPrice": "79.99", "promoPrice": "59.99", "installFee": "0", "contractLength": "12 months" }, { "key": "fiber-500", "name": "Fiber 500", "technology": "fiber", "monthlyPrice": "59.99", "promoPrice": "49.99", "installFee": "0", "contractLength": "12 months" } ] } ``` Whether you can serve this address. This is the answer that gates or confirms availability. What is purchasable at this address. Empty (or omitted) when `serviceable` is `false`. Stable, opaque identifier for the product in your system. This is what ties a product to an Offergrid offer — each offer stores the `key` of the product it represents. Matched case-insensitively and trimmed, but it must not change over time: a renamed key silently detaches the offer from its pricing. Plan name as you'd like it displayed, e.g. `Fiber 1 Gig`. Delivery technology, e.g. `fiber`, `cable`, `fixed_wireless`. Standard monthly rate. Plain decimal, no currency symbol — `79.99` or `"79.99"`. Promotional monthly rate, if any. Displayed in preference to `monthlyPrice`. One-time installation charge. `0` for free install. Term commitment, displayed verbatim — `12 months`, `No contract`. Extra fields are fine and ignored. Fields you can't supply should be omitted rather than sent as empty strings. ### Rules Return `200` with `"serviceable": false` and an empty `products` array. A non-2xx status means *we failed to get an answer*, which we handle very differently (see below) from *the answer is no*. On a non-2xx status, a timeout, or unparsable JSON, Offergrid falls back to the most recent cached answer for that address; with no cached answer, the integration goes quiet and your offers behave as if they had no serviceability source (ZIP-level availability, list pricing). Nothing breaks and no order is lost — but nothing is confirmed either. Prefer `200` with `serviceable: false` over an error whenever you actually know the answer. The call runs while a shopper waits for offer cards to render. Sub-second is ideal. If your upstream is slow, cache on your side — the request is the same normalized address every time. We may call the same address more than once: on browse, when the cached answer expires, and again at checkout. The call must not create a lead, consume a quota, or otherwise mutate state on your side. `serviceable: false` with a non-empty `products` array is contradictory, and different parts of the platform may read either field. When you can't serve the address, say so and return no products. ## What Offergrid does with the answer **Caching.** Answers are cached per (integration, address) with a TTL — 24 hours by default, configurable per integration. Within a single page render, one address costs exactly one call to you no matter how many of your offers are on screen. **Coverage: confirm or gate.** When you attach the integration to a service area you choose one of two behaviors: | Choice | Behavior | | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Show in my areas, confirm at the address** (default) | Offers appear across the service area's ZIP footprint. Once a full address is entered, your answer decorates the card with the real price and an availability indicator, but it never hides the offer. | | **Only show where the source confirms availability** | Your `serviceable: false` removes the offer from results for that address. If no answer is available (ZIP-only search, or your endpoint is down), the offer falls back to its ZIP footprint rather than disappearing. | **In plain words:** confirm means "show it, then tell them the truth". Gate means "don't even show it unless I say yes". Gate is honest but unforgiving; confirm sells more but shows offers you may have to decline. **Pricing, per offer.** This is the part people get wrong, so here it is concretely. You return two products in one response; on Offergrid you have two offers. Each offer stores the `key` of the product it represents — the field labelled **"Product identifier in your system"** in the offer editor. | Offergrid offer | Product identifier | Price shown | | ---------------- | ------------------ | ----------- | | Acme Fiber 1 Gig | `fiber-1g` | \$79.99 | | Acme Fiber 500 | `fiber-500` | \$59.99 | One call to you, two cards, two correct prices. An offer with no key — or a key that isn't in the response — falls back to the first product in the array, which is usually not what you want, so set them all. **Checkout re-verification.** At order submit, Offergrid calls you again, bypassing the cache, against the order's service address: * On-net → the fresh price, plan, technology, and install fee are recorded on the order alongside the price the customer agreed to, so fulfillment (and any dispute) can compare the two. * Off-net on a **gating** service area → the order is rejected at review with a clear message. The customer never places an order you'd have to cancel. * No answer (your endpoint is unreachable) → the order proceeds and is flagged as unverified. Serviceability is never a payment gate. Offers with no serviceability source record nothing here, which stays distinguishable from "we asked and got nothing back". ## How the order reaches you Orders arrive from resellers placing them on a customer's behalf, from a public link a reseller shared, or from Offergrid's consumer storefront. However it started, it lands the same way. | Channel | What you get | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Dashboard** | Every order on your Orders page, with the customer, service address, items, and the verified price snapshot. | | **Email & in-app** | Notifications on new orders and status changes, to your team's members. | | **Webhooks** | Signed callbacks to your system: `order.created`, `order.item.created`, `order.item.status_changed`, `order.cancelled`. Every delivery attempt is logged and queryable, so "did it arrive?" is always answerable. | You move each item along as you fulfill it, from the dashboard or through the API. Resellers and customers are notified automatically at the points that matter to them. ```text Order item lifecycle theme={null} pending → submitted_to_provider → accepted → scheduled → in_progress → completed → active exits: rejected · cancelled · failed ``` **In plain words:** you told us who you can serve. We found them, quoted your price, and checked with you again before taking the order. Now it's a normal order in your queue. ## Connecting it Deploy it and confirm it answers a known-serviceable and a known-unserviceable address correctly. Share the auth header value with support through a secure channel. We set it as a platform environment variable and reference it by name — it never enters the database. In your dashboard, go to **Integrations** → **New integration**, pick a **Source key** (a short, permanent identifier for this connection, e.g. `acme-serviceability` — it can't be changed later, because your service areas point at it), and paste the config below. It's validated on save, so typos surface as field errors rather than silent no-ops. Use **Test lookup** on the integration to run a real address through the live endpoint, bypassing the cache. It reports the on-net answer, the projected price fields, whether the result came from cache or live, and the upstream error verbatim if the call failed. Either add a **Serviceability** area to a service area (picking the integration and the confirm-or-gate choice), or — for internet offers — do it inline from the offer's **Service areas** step, which writes the ZIP footprint and the serviceability connection together. In the offer editor, set **Product identifier in your system** to the product's `key`. Do this for every offer that should price from this integration. ### Config for a spec-conformant endpoint Because the request fields and response shape already match, the config is near-identity — it declares the endpoint, the auth header, and the (1:1) mapping: ```json theme={null} { "endpoint": { "method": "POST", "url": "https://api.example.com/serviceability" }, "auth": { "header": "Authorization", "secretEnv": "ACME_SERVICEABILITY_KEY" }, "requiredAddressFields": ["line1", "city", "state", "zipCode"], "request": { "body": { "line1": { "field": "line1" }, "line2": { "field": "line2" }, "city": { "field": "city" }, "state": { "field": "state" }, "zipCode": { "field": "zipCode" }, "country": { "field": "country" } } }, "response": { "onNetWhenAnyNonEmpty": ["serviceable"], "technologyPath": "products.0.technology" }, "display": { "planNamePath": "products.0.name", "monthlyPricePath": "products.0.monthlyPrice", "promoPricePath": "products.0.promoPrice", "installFeePath": "products.0.installFee", "contractLengthPath": "products.0.contractLength", "technologyPath": "products.0.technology" }, "products": { "path": "products", "keyPath": "key", "display": { "planNamePath": "name", "monthlyPricePath": "monthlyPrice", "promoPricePath": "promoPrice", "installFeePath": "installFee", "contractLengthPath": "contractLength", "technologyPath": "technology" } }, "ttlMs": 86400000 } ``` What each block does: * **`request.body`** — how address fields become your request body. Here the names are identical on both sides. * **`response.onNetWhenAnyNonEmpty`** — the paths that decide the on-net answer: on-net when any of them holds a truthy value or a non-empty array. For this contract the `serviceable` boolean answers it directly. (`["products"]` is equivalent for a conformant endpoint, since an unserviceable address returns no products.) * **`display`** — where to read price/plan fields when an offer has no product identifier. Points at the first product. * **`products`** — where the product list lives (`path`), which field identifies a product (`keyPath`, matched against the offer's product identifier), and where to read each display field **inside** the matched entry. * **`ttlMs`** — how long a cached answer stays fresh. 24 hours here. ## Mapping an existing API (tier 2) If your serviceability API already exists and can't change shape, the same config describes it — only the values differ: * **Different field names?** `"address1": { "field": "line1" }` sends our `line1` as your `address1`. Constants your API requires (a partner ID, a promo code) are declared inline: `"clientName": { "const": "offergrid" }`. * **No `serviceable` boolean?** Point `onNetWhenAnyNonEmpty` at the arrays that imply availability, e.g. `["products", "fixedWirelessProducts"]` — on-net when any is non-empty. * **Products nested elsewhere?** `products.path` is a dot-path: `"data.availablePlans"` works. `keyPath` can be any stable identity field in an entry — `"serviceId"`, `"planCode"`, even `"name"`. * **Prices in a nested object?** Every display path is a dot-path relative to the matched product entry: `"monthlyPricePath": "pricing.monthly.amount"`. The mapping language is deliberately small — field copies, constants, dot-paths, and one on-net predicate. It has no conditionals, expressions, or templating, which is what keeps a new integration a support conversation rather than an engineering project. An API that genuinely can't be expressed this way is tier 3. **In plain words:** send us a sample request and a sample response from whatever you already have, and we'll tell you which tier you're in. ## What's automated today So you can plan operations around what exists rather than what's described above in the abstract. The fresh call at submit is live for orders placed through Offergrid's consumer storefront. Reseller-placed and shared-link orders are checked against your endpoint while the reseller browses — including the gate — but don't yet make a second call at submit. Extending it is in progress. Auth values are held as platform environment variables, so a new secret needs us to install it before your integration goes live. That's deliberate — it keeps credentials out of the database — but it makes step 2 a short back-and-forth rather than self-serve. Orders reach you as signed webhooks and in the dashboard. There is no structured submission into your order-entry system: no passing your quote or session identifier back, no install-window selection, no payment details. Fulfillment starts from the order we hand you. The one-step "set coverage inside the offer" flow exists for internet offers. Other categories set the same thing up from the **Service areas** page — same engine, one more click. ## Checklist Endpoint accepts `POST` with a JSON body and returns `200` JSON Unserviceable addresses return `200` with `serviceable: false` and no products Every product carries a stable `key` that won't change Prices are plain decimals with no currency symbol Missing `line2`/`country` are tolerated (absent, not empty string) Responses land in under 2 seconds Repeat calls for the same address are safe and side-effect free Auth is a single static header whose value we can hold as a secret Every offer that should price from the endpoint has its product identifier set Each service area is explicitly set to confirm or gate — chosen, not defaulted Questions, or an API that doesn't fit? Email [support@offergrid.io](mailto:support@offergrid.io) with a sample request and response and we'll tell you which tier you're in. # Webhooks Source: https://offergrid.io/docs/providers/webhooks Receive signed, real-time notifications about orders and order items ## Overview Webhooks push order-lifecycle events to your systems as they happen, so you can start fulfillment without polling `GET /provider/orders`. Register an HTTPS endpoint, subscribe it to the event types you care about, and Offergrid POSTs a signed JSON envelope to it every time one of those events occurs on an order that includes one of your offers. Every delivery attempt — success or failure — is recorded and queryable via [`GET /provider/webhooks/{id}/deliveries`](/docs/provider-api-reference/provider-webhooks/list-recent-delivery-attempts-for-a-webhook). That log is the first place to look when something appears to be missing. ## Registering a webhook Create the webhook with the Provider API. The response contains the signing secret, and it is shown **only once** — store it before you discard the response. ```bash theme={null} curl -X POST https://api.offergrid.io/provider/webhooks \ -H "x-api-key: YOUR_TEAM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://provider-system.example.com/offergrid-callback", "events": ["order.item.created", "order.item.status_changed"] }' ``` The `url` must be `https://` — plain HTTP is rejected at validation. | Operation | Endpoint | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Register a webhook | [`POST /provider/webhooks`](/docs/provider-api-reference/provider-webhooks/register-a-webhook) | | List your webhooks | [`GET /provider/webhooks`](/docs/provider-api-reference/provider-webhooks/list-your-registered-webhooks) | | Update url, events, or active state | [`PATCH /provider/webhooks/{id}`](/docs/provider-api-reference/provider-webhooks/update-a-webhook-url-subscribed-events-or-active-state) | | Delete a webhook | [`DELETE /provider/webhooks/{id}`](/docs/provider-api-reference/provider-webhooks/delete-a-webhook) | | Inspect delivery attempts | [`GET /provider/webhooks/{id}/deliveries`](/docs/provider-api-reference/provider-webhooks/list-recent-delivery-attempts-for-a-webhook) | To pause deliveries without losing the webhook or its secret, `PATCH` it with `{"isActive": false}` rather than deleting it. ## Event types These are the four event types you can subscribe to. Subscribing to a type not on this list is rejected. | Event | When it fires | Scoped to | | --------------------------- | ------------------------------------- | ----------------------------------------- | | `order.item.created` | A reseller ordered one of your offers | The provider that owns the item | | `order.item.status_changed` | An item's fulfillment status changed | The provider that owns the item | | `order.created` | An order was placed | Every provider with an item on that order | | `order.cancelled` | An order was cancelled | Every provider with an item on that order | `order.item.created` is the one to build fulfillment on. It carries the specific item you need to fulfill; `order.created` describes the order as a whole, which may span several providers. Resellers subscribe to these same four types on their own endpoint and receive them scoped to the orders they placed — see [Reseller webhooks](/docs/resellers/webhooks). If your team is hybrid, one webhook covers both roles and each event is delivered exactly once. ## Payload structure Every delivery is a POST with `Content-Type: application/json` and this envelope: ```json theme={null} { "id": "8f2a1c6e-...", "type": "order.item.created", "version": 1, "data": { } } ``` | Field | Description | | --------- | ----------------------------------------------------------------------------------------------------- | | `id` | Unique event id. Use it as your idempotency key. | | `type` | One of the four event types above. | | `version` | Envelope version, currently `1`. Bumped only on an incompatible change to an existing type's payload. | | `data` | Event-specific payload, snapshotted at the time the event was emitted. | Payloads snapshot order-time data — offer name, price, address, customer info — rather than referencing live rows, so a delivery stays accurate even if the offer changes afterwards. ### `order.item.created` ```json theme={null} { "id": "8f2a1c6e-4b2d-4d0a-9a7f-2c8f1e3b5a90", "type": "order.item.created", "version": 1, "data": { "orderId": "3c9d8b7a-1e2f-4a5b-8c7d-6e5f4a3b2c1d", "orderItemId": "7b6a5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d", "offerId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "offerName": "High-Speed Internet 1000 Mbps", "category": "internet", "monthlyPrice": "79.99", "promoMonthlyPrice": "49.99", "contractLength": "12_months", "providerTeamId": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b", "resellerTeamId": "2f1e0d9c-8b7a-4655-9483-1a2b3c4d5e6f" } } ``` ### `order.item.status_changed` ```json theme={null} { "id": "b4c3d2e1-...", "type": "order.item.status_changed", "version": 1, "data": { "orderId": "3c9d8b7a-1e2f-4a5b-8c7d-6e5f4a3b2c1d", "orderItemId": "7b6a5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d", "providerTeamId": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b", "resellerTeamId": "2f1e0d9c-8b7a-4655-9483-1a2b3c4d5e6f", "from": "pending", "to": "accepted", "orderStatus": "in_progress" } } ``` `from` and `to` are the item's statuses; `orderStatus` is the parent order's status recomputed from all of its items. ### `order.created` ```json theme={null} { "id": "c5d4e3f2-...", "type": "order.created", "version": 1, "data": { "orderId": "3c9d8b7a-1e2f-4a5b-8c7d-6e5f4a3b2c1d", "resellerTeamId": "2f1e0d9c-8b7a-4655-9483-1a2b3c4d5e6f", "linkId": null, "serviceAddress": { "street": "123 Main St", "city": "San Francisco", "state": "CA", "zipCode": "94102" }, "customerInfo": { "firstName": "Jordan", "lastName": "Reyes", "email": "jordan@example.com", "phone": "+14155550123" }, "totalMonthly": "129.98" } } ``` ### `order.cancelled` ```json theme={null} { "id": "d6e5f4a3-...", "type": "order.cancelled", "version": 1, "data": { "orderId": "3c9d8b7a-1e2f-4a5b-8c7d-6e5f4a3b2c1d", "resellerTeamId": "2f1e0d9c-8b7a-4655-9483-1a2b3c4d5e6f", "orderStatus": "cancelled" } } ``` ## Verifying signatures Signed deliveries carry a single header: ``` X-Offergrid-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd ``` There is **no separate timestamp header** — the timestamp lives in the `t=` component and is also part of the signed payload, so a verifier can reject stale or replayed deliveries without any shared clock beyond a tolerance window. The signature is `HMAC-SHA256` over the string `` `${t}.${rawBody}` ``, keyed with your webhook secret, hex-encoded. Sign over the **raw request body**, not a re-serialized copy. `JSON.parse` followed by `JSON.stringify` can reorder keys or change number formatting, and the signature will not match. Capture the raw bytes before body parsing. ```typescript Node / Express theme={null} import crypto from 'crypto'; import express from 'express'; const app = express(); // Capture the raw body — express.json() alone gives you a parsed object, // which cannot be re-serialized back to the exact signed bytes. app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf; } })); function verifySignature(rawBody: Buffer, header: string, secret: string): boolean { const parts = Object.fromEntries( header.split(',').map((part) => part.split('=') as [string, string]), ); const timestamp = Number(parts.t); const signature = parts.v1; if (!timestamp || !signature) return false; // Reject deliveries older than 5 minutes (replay protection). if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) return false; const expected = crypto .createHmac('sha256', secret) .update(`${timestamp}.${rawBody.toString('utf8')}`) .digest('hex'); const expectedBuffer = Buffer.from(expected, 'hex'); const actualBuffer = Buffer.from(signature, 'hex'); // timingSafeEqual throws on length mismatch — check first. if (expectedBuffer.length !== actualBuffer.length) return false; return crypto.timingSafeEqual(expectedBuffer, actualBuffer); } app.post('/offergrid-callback', (req, res) => { const header = req.headers['x-offergrid-signature'] as string | undefined; if (!header || !verifySignature((req as any).rawBody, header, process.env.OFFERGRID_WEBHOOK_SECRET!)) { return res.status(401).send('Invalid signature'); } // Acknowledge first, process asynchronously. void enqueue(req.body); res.status(200).send('OK'); }); ``` ```python Python / Flask theme={null} import hashlib, hmac, os, time from flask import Flask, request app = Flask(__name__) def verify_signature(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(part.split("=", 1) for part in header.split(",")) try: timestamp = int(parts["t"]) signature = parts["v1"] except (KeyError, ValueError): return False # Reject deliveries older than 5 minutes (replay protection). if abs(int(time.time()) - timestamp) > 300: return False expected = hmac.new( secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature) @app.post("/offergrid-callback") def callback(): header = request.headers.get("X-Offergrid-Signature", "") if not verify_signature(request.get_data(), header, os.environ["OFFERGRID_WEBHOOK_SECRET"]): return "Invalid signature", 401 enqueue(request.get_json()) return "OK", 200 ``` One case is unsigned: if an offer has a `submissionUrl` and your team has no registered webhook at all, `order.item.created` is delivered to that URL without a signature, because there is no secret to sign with. Register a webhook — even an inactive one — to get signed submissions. ## Delivery behaviour What Offergrid guarantees from the sending side: | Property | Behaviour | | ------------------ | ------------------------------------------------------------------------------------------------------------------------- | | Request timeout | 10 seconds. A slower endpoint is recorded as a failed attempt. | | Success condition | Any 2xx response. Everything else — including 3xx — counts as a failure. | | Retries | Up to 5 attempts per event with exponential backoff. | | Delivery semantics | At-least-once. Expect duplicates and deduplicate on the envelope `id`. | | Ordering | Not guaranteed. Do not assume `order.item.created` arrives before its first `order.item.status_changed`. | | Logging | Every attempt is written to the delivery log with status code and a truncated response body, whether it succeeded or not. | Retries are per-event, not per-target. If you have several webhooks subscribed to the same event and one fails, the retry re-sends to all of them — which is another reason to deduplicate on the envelope `id`. ### Responding Return 2xx as soon as you have durably accepted the event, and do the real work in a background job. You have 10 seconds, but treating that as a budget rather than a target keeps you off the retry path. ```typescript theme={null} app.post('/offergrid-callback', async (req, res) => { if (!verified(req)) return res.status(401).send('Invalid signature'); await queue.add('offergrid-event', req.body); // durable, fast res.status(200).send('OK'); // then acknowledge }); ``` ### Deduplicating ```typescript theme={null} async function handleEvent(envelope: { id: string; type: string; data: unknown }) { // The envelope id is stable across retries — the natural idempotency key. const inserted = await db.processedEvents.insertIfAbsent(envelope.id); if (!inserted) return; // already handled await processEvent(envelope.type, envelope.data); } ``` ## Testing your endpoint Point a webhook at a tunnel while you develop: ```bash theme={null} ngrok http 4000 ``` Register the tunnel URL, then place a test order from a reseller account to trigger a real, correctly-signed delivery. This is the only way to exercise signature verification end to end — a hand-rolled cURL request carries no valid signature, so a correct verifier will reject it. To confirm what Offergrid actually sent and what your endpoint returned: ```bash theme={null} curl https://api.offergrid.io/provider/webhooks/{webhookId}/deliveries \ -H "x-api-key: YOUR_TEAM_API_KEY" ``` ## Troubleshooting 1. Confirm the webhook is `isActive: true`. 2. Confirm it subscribes to the event type you expect — a webhook only receives types listed in its `events` array. 3. Check the delivery log. If attempts are recorded with a non-2xx status, the problem is on your side; if there are no attempts at all, no matching event was emitted for your team. 4. Confirm the order actually contains one of *your* offers — item-scoped events only go to the provider that owns the item. 1. Sign the **raw** body, not a re-serialized copy. 2. Parse the timestamp out of the `t=` component of `X-Offergrid-Signature`. There is no `x-offergrid-timestamp` header. 3. The signed string is `` `${t}.${rawBody}` `` — the timestamp, a literal dot, then the body. 4. Compare hex-decoded buffers, and check lengths first: Node's `timingSafeEqual` throws on a length mismatch. 5. Confirm you stored the secret from the creation response — it is shown only once and cannot be retrieved later. A null status code means the request never completed — DNS failure, TLS error, connection refused, or your endpoint exceeded the 10-second timeout. The recorded response body holds the underlying error message. Expected. Delivery is at-least-once, and a retry re-sends to every subscribed target. Deduplicate on the envelope `id`. ## Next steps What to do with an order once the webhook lands The status lifecycle behind `order.item.status_changed` Every provider endpoint, including webhook management Broader integration patterns # Browse consumer-enabled offers Source: https://offergrid.io/docs/public-api-reference/public/browse-consumer-enabled-offers /openapi/openapi-public.json get /public/shop/offers Returns active, consumer-enabled offers, optionally filtered by ZIP/city/state (coverage-checked through the offer's markets) and category. Unauthenticated — backs the public /shop storefront. # Check address-level serviceability for on-screen offers Source: https://offergrid.io/docs/public-api-reference/public/check-address-level-serviceability-for-on-screen-offers /openapi/openapi-public.json post /public/shop/serviceability Given a full street address, returns per-offer serviceability + exact price for offers whose markets reference an external serviceability source. Layered on top of ZIP coverage (not a gate) — offers without a source are simply absent from the result map. Unauthenticated. # Get a consumer offer by its public slug Source: https://offergrid.io/docs/public-api-reference/public/get-a-consumer-offer-by-its-public-slug /openapi/openapi-public.json get /public/shop/offers/{publicSlug} Full pricing/compliance detail for one consumer-enabled offer. Pass `zip` to re-check coverage for that ZIP. Unauthenticated. # Get available offers for a link Source: https://offergrid.io/docs/public-api-reference/public/get-available-offers-for-a-link /openapi/openapi-public.json get /public/links/{slug}/offers Retrieve all service offers available for the address associated with this link. Offers are grouped by category (internet, electricity, other). This endpoint does not require authentication. # Get link details Source: https://offergrid.io/docs/public-api-reference/public/get-link-details /openapi/openapi-public.json get /public/links/{slug} Retrieve public information about a shareable link, including the service address and property name. This endpoint does not require authentication. # Place a checkout-mode shop order Source: https://offergrid.io/docs/public-api-reference/public/place-a-checkout-mode-shop-order /openapi/openapi-public.json post /public/shop/orders Creates an order through the canonical transactional path (idempotent, snapshotted, outbox-emitting), attributed to the house reseller team. Re-checks coverage against the submitted address; unauthenticated. # Record an outbound shop click Source: https://offergrid.io/docs/public-api-reference/public/record-an-outbound-shop-click /openapi/openapi-public.json post /public/shop/clicks Logs a click for a consumer offer — a lead_gen click returns a redirectUrl (the provider URL with Offergrid attribution merged on); a checkout-mode click is fire-and-forget funnel logging with no redirect. Unauthenticated, lightly rate-limited per IP. # Submit an order via a shareable link Source: https://offergrid.io/docs/public-api-reference/public/submit-an-order-via-a-shareable-link /openapi/openapi-public.json post /public/links/{slug}/orders Create a new order for the selected service offers. The order will be associated with the reseller who created the link. This endpoint does not require authentication. # Quick Start Source: https://offergrid.io/docs/quickstart Get started with Offergrid in minutes ## Choose Your Role Offergrid serves two distinct user types. Select the path that matches your role: Service providers offering internet or electricity services Resellers helping customers find and purchase services ## Quick Setup (Both Roles) ### 1. Create Your Account Visit [offergrid.io](https://offergrid.io) and click **Sign Up** Select **Provider** or **Reseller** based on your business Fill in your company information and contact details Complete verification (typically takes 1-2 business days) ### 2. Generate API Key Navigate to **Settings** → **API Keys** in your dashboard Click **Generate New API Key** Copy the key immediately and store it in environment variables ```bash theme={null} export OFFERGRID_API_KEY="your-api-key-here" ``` You won't be able to see this key again! ### 3. Make Your First API Call Test your API key with a simple request: ```bash cURL theme={null} # Providers: List your offers curl -X GET https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_API_KEY" # Resellers: Browse catalog curl -X GET https://api.offergrid.io/reseller/catalog \ -H "x-api-key: YOUR_API_KEY" ``` ```typescript TypeScript theme={null} // Providers: List offers const response = await fetch('https://api.offergrid.io/provider/offers', { headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, }, }); const offers = await response.json(); console.log(offers); ``` ```python Python theme={null} import requests import os # Providers: List offers response = requests.get( 'https://api.offergrid.io/provider/offers', headers={'x-api-key': os.environ['OFFERGRID_API_KEY']} ) offers = response.json() print(offers) ``` ## Provider Quick Start ### Create Your First Offer ```bash theme={null} curl -X POST https://api.offergrid.io/provider/offers \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "High-Speed Internet 1000 Mbps", "category": "internet", "status": "active", "monthlyPrice": 59.99, "description": "Blazing fast fiber internet", "keyFeatures": ["1000 Mbps download", "Unlimited data"] }' ``` ### View Incoming Orders ```bash theme={null} curl -X GET https://api.offergrid.io/provider/orders \ -H "x-api-key: YOUR_API_KEY" ``` ### Next Steps for Providers Complete provider onboarding checklist Learn how to create service offerings Manage incoming orders API integration guide ## Reseller Quick Start ### Browse Available Services ```bash theme={null} curl -X GET "https://api.offergrid.io/reseller/catalog?category=internet&zipCode=94102" \ -H "x-api-key: YOUR_API_KEY" ``` ### Place Your First Order ```bash theme={null} curl -X POST https://api.offergrid.io/reseller/orders \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [{ "offerId": "off-123-abc" }], "customerInfo": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+1-555-123-4567" }, "serviceAddress": { "street": "123 Main St", "city": "San Francisco", "state": "CA", "zipCode": "94102", "country": "US" } }' ``` ### Track Orders ```bash theme={null} curl -X GET https://api.offergrid.io/reseller/orders \ -H "x-api-key: YOUR_API_KEY" ``` ### Next Steps for Resellers Complete reseller onboarding checklist Find services for your customers Learn how to place orders API integration guide ## Common Questions Account verification typically takes 1-2 business days. You'll receive an email when your account is approved. Yes! Some teams have hybrid access and can use both provider and reseller endpoints with the same API key. Go to **Settings** → **API Keys** in your dashboard. If you lost your key, revoke it and generate a new one. * Burst: 100 requests per minute * Sustained: 10,000 requests per hour Yes, create offers with `status: "draft"` to test without making them visible. Work with support to create test orders. ## Additional Resources Platform overview and workflows API authentication guide Complete API documentation ## Need Help? Questions? Reach out to our team at [support@offergrid.io](mailto:support@offergrid.io) # Find available offers for an address Source: https://offergrid.io/docs/reseller-api-reference/reseller-availability/find-available-offers-for-an-address /openapi/openapi-reseller.json post /reseller/availability Given a service address, returns every offer your reseller team can sell at that location. Coverage is evaluated against each offer’s availability rules — postal codes and active market areas (including state-wide and city-level coverage) — and provider visibility settings (all resellers, preferred resellers, or selected resellers). This is the same availability logic used by shareable checkout links, exposed as a dedicated address-driven endpoint. Results are returned both as a flat list and grouped by category for checkout UIs. # Browse available offers Source: https://offergrid.io/docs/reseller-api-reference/reseller-catalog/browse-available-offers /openapi/openapi-reseller.json get /reseller/catalog Browse all service offers available to your reseller team. Visibility is based on provider settings (all resellers, preferred resellers, or selected resellers). Supports filtering by category, price range, ZIP code, and search terms. Results are sorted by relevance! # Get offer details Source: https://offergrid.io/docs/reseller-api-reference/reseller-catalog/get-offer-details /openapi/openapi-reseller.json get /reseller/catalog/{id} Retrieve detailed information about a specific offer in the catalog. You can only view offers that are available to your reseller team. # Add a customer or lead manually Source: https://offergrid.io/docs/reseller-api-reference/reseller-customers/add-a-customer-or-lead-manually /openapi/openapi-reseller.json post /reseller/customers Create a new lead or customer record. Resellers use this to track contacts from off-platform sources before they place an order. # Get customer detail (with this reseller's orders) Source: https://offergrid.io/docs/reseller-api-reference/reseller-customers/get-customer-detail-with-this-resellers-orders /openapi/openapi-reseller.json get /reseller/customers/{customerId} # List customers and leads Source: https://offergrid.io/docs/reseller-api-reference/reseller-customers/list-customers-and-leads /openapi/openapi-reseller.json get /reseller/customers Returns this reseller team's customers (people who have placed an order) and leads (people added manually, by API import, or by event). Filter via the kind query param. # Remove customer from this reseller's list Source: https://offergrid.io/docs/reseller-api-reference/reseller-customers/remove-customer-from-this-resellers-list /openapi/openapi-reseller.json delete /reseller/customers/{customerId} Removes the link between this reseller and the customer. The shared Customer record itself is preserved so other teams' views are unaffected. # Update customer notes / tags / status / kind Source: https://offergrid.io/docs/reseller-api-reference/reseller-customers/update-customer-notes-tags-status-kind /openapi/openapi-reseller.json patch /reseller/customers/{customerId} Update reseller-private fields. To update the underlying contact info (name, email, address) the contact themselves must place a new order, since those fields are shared across all teams that know this person. # Create a shareable link Source: https://offergrid.io/docs/reseller-api-reference/reseller-links/create-a-shareable-link /openapi/openapi-reseller.json post /reseller/links Generate a shareable link for tenants to order services at a specific address. The link can be shared via email or SMS. Tenants can use the link to browse available offers and place orders without needing to create an account. # Delete a link Source: https://offergrid.io/docs/reseller-api-reference/reseller-links/delete-a-link /openapi/openapi-reseller.json delete /reseller/links/{id} Permanently delete a shareable link. Orders placed via this link will be preserved. # Get link details Source: https://offergrid.io/docs/reseller-api-reference/reseller-links/get-link-details /openapi/openapi-reseller.json get /reseller/links/{id} Retrieve detailed information about a specific link, including recent orders placed via the link. # List all shareable links Source: https://offergrid.io/docs/reseller-api-reference/reseller-links/list-all-shareable-links /openapi/openapi-reseller.json get /reseller/links Retrieve all shareable links created by your reseller team. Includes view and order counts for analytics. # Update a link Source: https://offergrid.io/docs/reseller-api-reference/reseller-links/update-a-link /openapi/openapi-reseller.json patch /reseller/links/{id} Update link properties such as property name, move-in date, or status. Set status to "inactive" to disable a link without deleting it. # Cancel an order Source: https://offergrid.io/docs/reseller-api-reference/reseller-orders/cancel-an-order /openapi/openapi-reseller.json patch /reseller/orders/{id}/cancel Cancel a pending or submitted order. Orders can only be cancelled if they have not been accepted by providers. # Get order details Source: https://offergrid.io/docs/reseller-api-reference/reseller-orders/get-order-details /openapi/openapi-reseller.json get /reseller/orders/{id} Retrieve detailed information about a specific order, including all items and their fulfillment status. # List your orders Source: https://offergrid.io/docs/reseller-api-reference/reseller-orders/list-your-orders /openapi/openapi-reseller.json get /reseller/orders Retrieve all orders placed by your reseller team. Includes order items and their fulfillment status. # Place a new order Source: https://offergrid.io/docs/reseller-api-reference/reseller-orders/place-a-new-order /openapi/openapi-reseller.json post /reseller/orders Create a new order for one or more service offers. Each offer will be sent to its respective provider for fulfillment. You can only order offers that are available to your reseller team. # Delete a webhook Source: https://offergrid.io/docs/reseller-api-reference/reseller-webhooks/delete-a-webhook /openapi/openapi-reseller.json delete /reseller/webhooks/{id} # Get a webhook by id Source: https://offergrid.io/docs/reseller-api-reference/reseller-webhooks/get-a-webhook-by-id /openapi/openapi-reseller.json get /reseller/webhooks/{id} # List recent delivery attempts for a webhook Source: https://offergrid.io/docs/reseller-api-reference/reseller-webhooks/list-recent-delivery-attempts-for-a-webhook /openapi/openapi-reseller.json get /reseller/webhooks/{id}/deliveries The 50 most recent attempts, newest first. Every attempt is logged whether it succeeded or failed — start here when a delivery appears to be missing. # List your registered webhooks Source: https://offergrid.io/docs/reseller-api-reference/reseller-webhooks/list-your-registered-webhooks /openapi/openapi-reseller.json get /reseller/webhooks Secrets are masked — the full value is only ever returned at creation. # Register a webhook Source: https://offergrid.io/docs/reseller-api-reference/reseller-webhooks/register-a-webhook /openapi/openapi-reseller.json post /reseller/webhooks Register an HTTPS endpoint to receive signed order-event deliveries for orders your team placed. The response includes the signing secret — it is shown only this once. # Update a webhook (url, subscribed events, or active state) Source: https://offergrid.io/docs/reseller-api-reference/reseller-webhooks/update-a-webhook-url-subscribed-events-or-active-state /openapi/openapi-reseller.json patch /reseller/webhooks/{id} # Reseller Documentation Source: https://offergrid.io/docs/reseller-documentation Complete guide for reseller partners using Offergrid ## Reseller Documentation Everything you need to know about using Offergrid as a reseller partner. Browse catalogs, place orders, track fulfillment, and earn commissions. ## Getting Started Learn about reseller benefits, features, and how Offergrid works for partners Get up and running in minutes with step-by-step instructions ## Finding Offers Search and filter available service offerings Understand different service types and categories Evaluate and compare offerings from multiple providers Verify service availability for specific locations ## Placing Orders Learn how to create and submit orders for your customers Understand what customer details are required Submit orders and understand the submission process ## Managing Orders Monitor order status and fulfillment progress Understand the complete order journey from submission to activation Handle order cancellations and refunds ## Integration Integrate Offergrid with your existing systems Set up real-time notifications for orders and updates Complete API documentation and reference # API Integration Guide Source: https://offergrid.io/docs/resellers/api-integration Integrate Offergrid into your existing systems ## Overview The Offergrid Reseller API lets you browse services, place orders, and track fulfillment programmatically, enabling seamless integration with your existing platforms. ## Getting Started ### 1. Get Your API Key 1. Sign in to [offergrid.io](https://offergrid.io) 2. Go to **Settings** → **API Keys** 3. Click **Generate New Key** 4. Save the key securely See [Authentication](/docs/authentication) for details. ### 2. Choose Your Integration Approach **Option A: Real-Time API Calls** * Search catalog on-demand * Place orders immediately * Best for interactive applications **Option B: Webhook Notifications** * Receive order status updates automatically * Event-driven architecture * Best for automation **Option C: Scheduled Sync** * Cache catalog data locally * Refresh periodically * Best for high-volume or offline-capable apps ## Common Integration Patterns ### Pattern 1: Property Management Software Integrate Offergrid into property management platforms: ```typescript theme={null} // Example: Add "Order Services" button to tenant portal async function orderServicesForTenant(tenant, property) { // 1. Get available services for property address const catalog = await fetchCatalog(property.zipCode); // 2. Let tenant select services const selectedServices = await showServiceSelection(catalog); // 3. Place orders const orders = await Promise.all( selectedServices.map((service) => placeOrder({ offerId: service.id, customerInfo: { firstName: tenant.firstName, lastName: tenant.lastName, email: tenant.email, phone: tenant.phone, }, serviceAddress: property.address, }) ) ); // 4. Track and notify tenant await trackOrders(orders); } ``` ### Pattern 2: Real Estate Platform Add service ordering to real estate workflows: ```typescript theme={null} // Example: Pre-close service coordination async function coordinateServicesForClosing(property, buyer) { const moveInDate = property.closingDate; // Get services available at property const services = await getAvailableServices(property.zipCode); // Recommend essential services const recommended = recommendServices(services, property.type); // Place orders timed for move-in const orders = await placeOrdersWithTiming(recommended, buyer, moveInDate); return orders; } ``` ### Pattern 3: Lead Generation Website Capture and convert service leads: ```typescript theme={null} // Example: Service comparison tool async function buildServiceComparison(zipCode, serviceType) { // Fetch available offers const offers = await fetch( `https://api.offergrid.io/reseller/catalog?zipCode=${zipCode}&category=${serviceType}`, { headers: { 'x-api-key': process.env.OFFERGRID_API_KEY, }, } ); const services = await offers.json(); // Build comparison table const comparison = services.map((s) => ({ name: s.name, price: s.monthlyPrice, features: s.keyFeatures, provider: s.providerName, })); return comparison; } // When user selects service async function captureLeadAndOrder(service, customerInfo) { // Save lead to CRM await saveToCRM(customerInfo); // Place order on Offergrid const order = await placeOrder({ offerId: service.id, customerInfo, serviceAddress: customerInfo.address, }); // Track order in CRM await updateCRMWithOrder(customerInfo.id, order.orderId); return order; } ``` ## Core API Operations ### Browsing Catalog ```bash theme={null} # Get all services GET /reseller/catalog # Filter by category GET /reseller/catalog?category=internet # Filter by location GET /reseller/catalog?zipCode=94102 # Search GET /reseller/catalog?search=fiber&minPrice=50&maxPrice=100 ``` ### Viewing Offer Details ```bash theme={null} GET /reseller/catalog/{offerId} ``` Returns complete offer information. ### Placing Orders ```bash theme={null} POST /reseller/orders Content-Type: application/json { "items": [{ "offerId": "off-123-abc" }], "customerInfo": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+1-555-123-4567" }, "serviceAddress": { "street": "123 Main St", "city": "San Francisco", "state": "CA", "zipCode": "94102", "country": "US" } } ``` ### Tracking Orders ```bash theme={null} # List all orders GET /reseller/orders # Get specific order GET /reseller/orders/{orderId} # Filter by status GET /reseller/orders?status=scheduled ``` ### Canceling Orders ```bash theme={null} PATCH /reseller/orders/{orderId}/cancel Content-Type: application/json { "reason": "Customer no longer needs service" } ``` ## Error Handling ```typescript theme={null} async function safeApiCall(apiFunction, retries = 3) { for (let i = 0; i < retries; i++) { try { return await apiFunction(); } catch (error) { if (error.status === 401) { throw new Error('Invalid API key'); } if (error.status === 429) { // Rate limited - wait and retry await sleep(2 ** i * 1000); continue; } if (error.status >= 500) { // Server error - retry await sleep(2 ** i * 1000); continue; } // Client error - don't retry throw error; } } throw new Error('Max retries exceeded'); } ``` ## Caching Strategies ### Catalog Caching Cache catalog data to reduce API calls: ```typescript theme={null} const catalogCache = { data: null, timestamp: null, ttl: 3600000, // 1 hour async get(zipCode) { if (this.isValid()) { return this.data; } this.data = await fetchCatalog(zipCode); this.timestamp = Date.now(); return this.data; }, isValid() { return this.data && Date.now() - this.timestamp < this.ttl; }, }; ``` ### Order Status Caching Cache order status with shorter TTL: ```typescript theme={null} const orderCache = new Map(); const ORDER_CACHE_TTL = 300000; // 5 minutes async function getOrderStatus(orderId) { const cached = orderCache.get(orderId); if (cached && Date.now() - cached.timestamp < ORDER_CACHE_TTL) { return cached.data; } const order = await fetchOrder(orderId); orderCache.set(orderId, { data: order, timestamp: Date.now(), }); return order; } ``` ## Webhooks for Real-Time Updates Instead of polling, use webhooks: ```typescript theme={null} app.post('/webhooks/offergrid', async (req, res) => { const { event, orderId, status } = req.body; if (event === 'order.status_changed') { // Update your database await updateOrderInDatabase(orderId, status); // Notify customer await sendCustomerNotification(orderId, status); } res.sendStatus(200); }); ``` See [Webhooks](/docs/resellers/webhooks) for setup details. ## Rate Limiting * **Burst**: 100 requests per minute * **Sustained**: 10,000 requests per hour Implement rate limiting: ```typescript theme={null} const RateLimiter = require('bottleneck'); const limiter = new RateLimiter({ maxConcurrent: 5, minTime: 600, // 600ms between requests = 100/min }); const rateLimitedFetch = limiter.wrap(fetch); // Use rate-limited fetch const response = await rateLimitedFetch(url, options); ``` ## Best Practices Catalog changes infrequently. Cache for 1-6 hours to reduce API calls. Don't poll for order status. Use webhooks for real-time notifications. Handle temporary failures with exponential backoff. Retry 429 and 5xx errors. Check customer info and addresses locally before making API calls. Map Offergrid order IDs to your internal order/lead IDs for tracking. Provide clear error messages to users. Don't expose technical details. ## Testing ### Use Test Mode Create test orders without real fulfillment: ```json theme={null} { "items": [{ "offerId": "test-offer-id" }], "customerInfo": { "firstName": "Test", "lastName": "Customer", "email": "test@example.com", "phone": "+1-555-000-0000" }, "metadata": { "test": true } } ``` ### Monitor Integration Health Track: * API success rate * Average response time * Error rates by endpoint * Order acceptance rate ## Next Steps Set up real-time notifications Complete API documentation API key management Order placement guide # Browsing the Catalog Source: https://offergrid.io/docs/resellers/browsing-catalog Search and filter available service offerings ## Overview The Offergrid catalog contains services from multiple providers across all major categories. Use powerful search and filtering to find exactly what your customers need. ## Accessing the Catalog ### Via Dashboard 1. Navigate to **Catalog** in the sidebar 2. Browse all available offers 3. Use filters to narrow results 4. Click on any offer for full details ### Via API Retrieve offers programmatically: ```bash theme={null} curl -X GET "https://api.offergrid.io/reseller/catalog" \ -H "x-api-key: YOUR_API_KEY" ``` ## Search and Filter Options ### Filter by Category ```bash theme={null} GET /reseller/catalog?category=internet ``` Available categories: * `internet` - Broadband, fiber, cable, wireless * `electricity` - Deregulated retail electricity plans * `other` - Additional services ### Filter by Price Range ```bash theme={null} GET /reseller/catalog?minPrice=50&maxPrice=100 ``` Find offers within your customer's budget. ### Filter by ZIP Code ```bash theme={null} GET /reseller/catalog?zipCode=94102 ``` Only show offers available at specific locations. Always filter by ZIP code before presenting options to customers. Not all services are available everywhere. ### Search by Keywords ```bash theme={null} GET /reseller/catalog?search=fiber+internet ``` Search across: * Offer names * Descriptions * Marketing headlines * Key features ### Combine Filters Use multiple filters together: ```bash theme={null} GET /reseller/catalog?category=internet&minPrice=0&maxPrice=100&zipCode=94102&search=fiber ``` ## Offer Details Each offer includes: * **Basic Information**: Name, category, provider * **Pricing**: Monthly price, setup fees, pricing type * **Description**: Detailed service description * **Key Features**: Bullet points of main benefits * **Service Specifications**: Category-specific details (speeds, channels, etc.) * **Availability**: Service area, ZIP codes * **Marketing Content**: Headlines, images ### Viewing Full Details Click an offer or fetch via API: ```bash theme={null} GET /reseller/catalog/{offerId} ``` Returns complete offer information including all fields. ## Sorting Results Sort offers by: * **Price**: Low to high or high to low * **Name**: Alphabetical * **Recently Added**: Newest offers first * **Provider**: Group by service provider Example: ```bash theme={null} GET /reseller/catalog?category=internet&sortBy=price&sortOrder=asc ``` ## Understanding Offer Visibility You can only see offers that: * Are marked as `active` by the provider * Are either: * Public (visible to all resellers) * Preferred-only AND you're on that provider's preferred list * Selected resellers AND you're specifically added Your access level affects which offers you see. Build strong relationships with providers to gain access to exclusive offerings. ## Best Practices Filter by ZIP code before showing options to customers. Avoid disappointment from unavailable services. Show customers 2-3 options when available. Competition drives better decisions. Don't rely on name and price alone. Review service details, fees, and limitations. If you serve specific ZIP codes or categories, save common search filters. Providers add new services frequently. Review the catalog weekly for fresh options. ## Next Steps Understanding service types How to evaluate and compare offers Verify service availability Complete API documentation # Canceling Orders Source: https://offergrid.io/docs/resellers/cancellations How to cancel orders when needed ## When to Cancel Common reasons: * Customer changed mind * Customer found alternative * Address error cannot be fixed * Customer no longer needs service * Service not available (should be rejected by provider instead) ## Cancellation Windows ### Before Provider Acceptance **Status**: `pending` or `submitted_to_provider` **Result**: Easy cancellation, no issues **Impact**: No commission earned, no penalties ### After Provider Acceptance **Status**: `accepted`, `scheduled`, or `in_progress` **Result**: May have complications **Impact**: Depends on provider policy and stage Once a provider has scheduled or started installation, cancellation may result in fees or impact your account standing. ## How to Cancel ### Via Dashboard Navigate to **Orders** in the sidebar Locate the order you want to cancel Click **Cancel Order** button Enter cancellation reason (helps improve service) Confirm cancellation ### Via API ```bash theme={null} PATCH /reseller/orders/{orderId}/cancel ``` Example: ```bash theme={null} curl -X PATCH "https://api.offergrid.io/reseller/orders/ord-123-abc/cancel" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "Customer found alternative service" }' ``` ## What Happens After Cancellation Status changes to `cancelled` Provider receives cancellation notice If scheduled, appointment is cancelled Order does not earn commission ## Cancellation Policies by Stage ### Pending/Submitted **Can cancel**: Yes, always **Process**: Immediate **Fees**: None **Impact**: None ### Accepted (Not Yet Scheduled) **Can cancel**: Yes **Process**: Usually immediate **Fees**: Typically none **Impact**: Minimal, but track cancellation rate ### Scheduled (Appointment Set) **Can cancel**: Yes, but may have consequences **Process**: Contact provider **Fees**: Possible cancellation fee depending on notice **Impact**: May affect your account rating Cancel at least 24-48 hours before scheduled appointment to avoid fees. ### In Progress (Technician On-Site) **Can cancel**: Not recommended **Process**: Contact provider immediately **Fees**: Likely charges for time/materials **Impact**: Negative impact on account ### Completed/Active **Can cancel**: No (must cancel service through provider) **Process**: Customer works directly with provider **Fees**: Per customer's service agreement **Impact**: Commission may be clawed back ## Best Practices Verify customer commitment before submitting order. Reduce unnecessary cancellations. If cancellation is needed, do it as soon as possible. Don't wait. Help providers improve by explaining why customers cancel. For scheduled or in-progress orders, call provider support before canceling through system. If you have high cancellation rates, identify and address root causes. Before canceling, see if a different service would work for the customer. ## Reducing Cancellations ### Pre-Order Checklist Before submitting: * [ ] Customer confirmed interest * [ ] Budget approved * [ ] Service address verified * [ ] Availability checked * [ ] Customer understands timeline * [ ] Alternative options discussed ### Setting Expectations Tell customers: * Typical installation timeline * What to expect from provider * Cancellation policies and potential fees * Installation requirements (home access, etc.) ### Following Up After order submission: * Confirm with customer * Share acceptance notification * Remind about scheduled appointments * Be available for questions ## Impact on Your Account ### Metrics Tracked Providers may monitor: * **Cancellation rate**: % of orders you cancel * **Late cancellations**: Cancellations after scheduling * **Reason patterns**: Common cancellation reasons ### Consequences High cancellation rates may result in: * Account review * Loss of preferred partner status * Reduced access to exclusive offers * Commission adjustments **Target**: Keep cancellation rate below 10% ## When Customer Wants to Cancel Service If a customer wants to cancel **after service activation**: 1. **Don't use Offergrid**: Active service cancellation goes through provider 2. **Direct customer to provider**: Give them provider's customer service contact 3. **Not your responsibility**: Once active, it's between customer and provider 4. **Commission implications**: Some providers may claw back commission for early cancellations ## Next Steps Monitor order status Understanding order progression Best practices for placing orders Complete API documentation # Checking Availability Source: https://offergrid.io/docs/resellers/checking-availability Verify service availability before placing orders ## Overview Not all services are available everywhere. Always check availability before presenting options to avoid customer disappointment and order rejections. ## Checking via ZIP Code ### Dashboard 1. Go to **Catalog** 2. Enter ZIP code in the search filter 3. View only services available in that area ### API ```bash theme={null} GET /reseller/catalog?zipCode=94102 ``` Returns only offers available at that ZIP code. Always filter by ZIP code first, then show customers their available options. ## Understanding Service Areas ### ZIP Code Boundaries * Some providers serve entire ZIP codes * Others serve partial ZIP codes * Always verify specific addresses ### Technology Availability **Fiber**: Limited to areas with fiber infrastructure **Cable**: Widely available in urban/suburban areas **DSL**: Available where phone lines exist **Wireless**: Coverage based on tower proximity **Satellite**: Available almost everywhere ## Address-Specific Verification ### When ZIP Code Isn't Enough Some situations require address-level verification: * Multi-dwelling units (apartments, condos) * Rural areas with sparse coverage * New construction or recently developed areas * Edge of service area boundaries ### How to Verify **During order placement**: * Submit the order with full address details * Provider will accept or reject based on serviceability * Wait for provider confirmation before promising customer **Before order placement** (if available): * Contact provider support for pre-qualification * Use provider's own serviceability check * Ask about building-specific requirements ## Common Availability Issues ### Not in Service Area **Problem**: Address is outside provider's coverage **Solution**: * Try alternative providers * Check if service area is expanding * Consider different technology types ### Building Restrictions **Problem**: HOA, landlord, or building policies prevent installation **Customer should**: * Check with property management * Review HOA rules * Get written approval if needed **You should**: * Note restrictions in order * Alert provider in advance * Set customer expectations ### Technical Limitations **Problem**: Physical obstacles prevent service Examples: * No line of sight for satellite/wireless * Building wiring doesn't support fiber * Distance from telephone exchange (DSL) **Solution**: * Provider will identify during site survey * Present alternative technologies * Set realistic expectations upfront ## Best Practices Never show customers offers without checking ZIP code availability first. Get complete address including unit number, building name, etc. Tell customers that final availability is confirmed by the provider during order review. If first choice isn't available, have alternatives from other providers or technologies. Single-family homes rarely have issues. Apartments, condos, and commercial buildings may require special approval. ## Communicating Availability to Customers ### When Service IS Available "Great news! \[Service Name] is available at your address. The provider offers \[speeds/features] for \$\[price]/month." ### When Service Might Be Available "This service appears to be available in your ZIP code, but the provider will need to verify your specific address. I'll submit the order and they'll confirm within 24 hours." ### When Service Is NOT Available "Unfortunately, \[Service Name] isn't available at your address yet. However, I found \[Alternative Service] which offers similar features..." ## Next Steps How to evaluate options Place orders for customers # Comparing Offers Source: https://offergrid.io/docs/resellers/comparing-offers How to evaluate and compare service offerings ## Overview Presenting customers with 2-3 well-compared options increases conversion and builds trust. Learn how to quickly evaluate and present offers effectively. ## Key Comparison Factors ### Price **Monthly recurring cost**: * Base monthly price * Equipment rental fees * Additional service fees * Promotional vs. regular pricing **One-time costs**: * Installation/setup fees * Equipment purchase * Activation fees * Deposits **Long-term costs**: * Contract length and terms * Early termination fees * Price increases after promotional period Calculate total first-year cost to give customers accurate comparison. ### Service Quality **Performance metrics**: * Internet: Download/upload speeds * Electricity: Rate structure, renewable percentage **Reliability**: * Provider reputation * Service uptime guarantees * Customer reviews * Network technology (fiber vs. cable) ### Features and Benefits Compare what's included: * Equipment (router, DVR, cameras, etc.) * Professional installation * Customer support (24/7, phone, chat) * Mobile apps * Smart home integration * Bundling options ## Creating Comparison Tables ### Simple Three-Column Format | Feature | Provider A | Provider B | Provider C | | ---------------- | ----------- | ---------- | ---------- | | **Speed** | 500 Mbps | 1000 Mbps | 300 Mbps | | **Price** | \$59.99/mo | \$79.99/mo | \$49.99/mo | | **Data Cap** | Unlimited | Unlimited | 1 TB | | **Contract** | No contract | 12 months | 24 months | | **Installation** | \$99 | Free | \$50 | ### Highlighting Best Value Use tags or indicators: * **Best Value**: Lowest total cost * **Best Performance**: Highest specs * **Most Popular**: Commonly chosen * **Recommended**: Your suggestion based on needs ## Qualifying Customers Ask the right questions to narrow options: ### Budget "What monthly budget are you comfortable with?" * Under \$50 * $50-$100 * $100-$150 * Over \$150 ### Priorities "What's most important to you?" * Lowest price * Best performance * No contract flexibility * Specific features (sports channels, smart home, etc.) ### Current Situation "What do you have now?" * Current provider and pricing * Pain points or issues * Usage patterns * Satisfaction level ## Presenting Options ### Good/Better/Best Structure **Good** (\$50/mo): * Basic tier, meets minimum needs * Budget-friendly * May have limitations **Better** (\$75/mo): * Middle tier, most popular * Good balance of price and features * Recommended for most customers **Best** (\$100/mo): * Premium tier, no compromises * Best performance and features * For power users or specific needs ### Pro/Con Format **Provider A**: ✅ Pros: * Fastest speeds (1000 Mbps) * No data caps * Free installation ❌ Cons: * Higher monthly cost * 12-month contract required ## Red Flags to Watch For Avoid recommending offers with: * Unclear pricing or hidden fees * Very long contracts (36+ months) without good reason * Extremely limited availability * Poor provider reputation ## Commission Considerations Balance commission with customer needs: **High commission offers** might be: * Right for the customer (premium services they'll use) * Wrong for the customer (overpriced for their needs) **Build long-term relationships** by: * Prioritizing customer fit over commission * Being transparent about tradeoffs * Following up after installation Happy customers lead to referrals and repeat business. Don't sacrifice customer satisfaction for short-term commission gains. ## Next Steps Verify service availability Place orders for customers # Creating Orders Source: https://offergrid.io/docs/resellers/creating-orders How to place orders for your customers ## Overview Placing orders on Offergrid is straightforward. Collect the necessary customer information, select services, and submit. Providers handle fulfillment. ## Required Information Before placing an order, collect: ### Customer Information * Full name * Email address * Phone number ### Service Address * Street address * City * State * ZIP code * Country * Unit/apartment number (if applicable) ### Optional Details * Customer preferences or notes * Installation preferences * Contact time preferences * Special requirements Double-check addresses before submitting. Address errors are the #1 cause of order rejections. ## Placing an Order ### Via Dashboard Browse catalog and click **Order** on chosen offer Fill in customer name, email, and phone number Provide complete service address including unit number Include any special requests or customer preferences Verify all information is correct, then click **Place Order** ### Via API ```bash theme={null} curl -X POST https://api.offergrid.io/reseller/orders \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "offerId": "off-123-abc" } ], "customerInfo": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+1-555-123-4567" }, "serviceAddress": { "street": "123 Main St", "city": "San Francisco", "state": "CA", "zipCode": "94102", "country": "US" }, "notes": "Customer prefers afternoon installations", "metadata": { "referralSource": "property-listing", "unitNumber": "4B" } }' ``` ## Multi-Service Orders Order multiple services at once: ```json theme={null} { "items": [ { "offerId": "internet-offer-id" }, { "offerId": "electricity-offer-id" } ], "customerInfo": { ... }, "serviceAddress": { ... } } ``` Each service becomes a separate order item sent to its respective provider. ## Best Practices Ensure addresses are complete and properly formatted. Include apartment/unit numbers. Use the end customer's contact info, not your own. Providers may need to reach them directly. Include details that help providers: preferred contact times, gate codes, special access instructions. Review all details with customer before placing order. Changes after submission are difficult. Tell customers when to expect contact from providers (usually 24-48 hours). ## After Submitting What happens next: You receive order confirmation with order ID Order automatically routed to the service provider Provider checks serviceability and accepts or rejects (usually within 24 hours) If accepted, provider contacts customer to schedule Provider completes installation and activates service Commission earned upon successful activation ## Order Confirmations You'll receive: **Immediate**: Order ID and confirmation **Within 24 hours**: Provider acceptance or rejection **Updates**: Status changes throughout fulfillment Set up webhooks for real-time notifications. ## Next Steps Required customer details What happens after submission Monitor order status Complete API documentation # Customer Information Requirements Source: https://offergrid.io/docs/resellers/customer-information What customer details you need to collect ## Required Fields Every order requires these customer details: ### Full Name **Format**: First and last name **Examples**: * ✅ "John Doe" * ✅ "Maria Garcia" * ❌ "John" (last name missing) * ❌ "J. Doe" (unclear first name) **Why required**: Provider needs to verify identity and create account ### Email Address **Format**: Valid email address **Examples**: * ✅ "[john@example.com](mailto:john@example.com)" * ✅ "[maria.garcia@company.com](mailto:maria.garcia@company.com)" * ❌ "john@" (incomplete) * ❌ "not-an-email" (invalid format) **Why required**: Provider communications, account setup, billing Use the end customer's email, not your reseller email. Providers may need to contact the customer directly. ### Phone Number **Format**: Full phone number with country code **Examples**: * ✅ "+1-555-123-4567" * ✅ "(555) 123-4567" * ❌ "5551234567" (no formatting) * ❌ "123-4567" (incomplete) **Why required**: Scheduling installation, service activation, support ### Service Address **Required components**: * Street address * City * State/Province * ZIP/Postal code * Country **Important details**: * **Unit numbers**: Critical for apartments/condos * **Building names**: Helpful for large complexes * **Gate codes**: Include in notes if applicable * **Access instructions**: Delivery notes, parking info **Examples**: ✅ **Good**: ``` 123 Main Street, Apt 4B San Francisco, CA 94102 United States ``` ❌ **Bad**: ``` 123 Main St SF ``` (Missing unit number, city abbreviation, no ZIP code) ## Optional But Recommended ### Customer Preferences Help providers deliver better service: * **Preferred contact time**: Morning, afternoon, evening * **Installation preferences**: Specific dates or time windows * **Special requirements**: Accessibility needs, language preferences * **Equipment preferences**: Own router vs. provider equipment ### Order Notes Include helpful context: ```json theme={null} { "notes": "Customer works from home, needs installation scheduled on weekend. Prefers email communication. Building has restricted access Mon-Fri 9-5 only." } ``` ### Metadata Track additional information: ```json theme={null} { "metadata": { "referralSource": "property-listing", "propertyId": "PROP-12345", "moveInDate": "2025-02-01", "unitNumber": "4B", "buildingName": "Sunset Towers" } } ``` ## Data Privacy ### Handle Customer Data Responsibly * **Secure storage**: Encrypt customer data at rest * **Secure transmission**: Always use HTTPS * **Access control**: Limit who can view customer info * **Retention**: Only keep data as long as needed * **Compliance**: Follow GDPR, CCPA, and other privacy regulations ### Don't Share Without Permission * Customer data is for order fulfillment only * Don't sell or share customer lists * Don't use customer emails for marketing without opt-in * Follow all applicable privacy laws ## Validating Customer Data ### Pre-Submission Checks ```typescript theme={null} function validateCustomerInfo(customerInfo) { const errors = []; // Name validation — send firstName + lastName (a legacy single fullName // field is still accepted and split on the last space) if (!customerInfo.firstName?.trim() || !customerInfo.lastName?.trim()) { errors.push('First and last name are required'); } // Email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(customerInfo.email)) { errors.push('Valid email is required'); } // Phone validation const phoneRegex = /^\+?[\d\s\-\(\)]+$/; if (!phoneRegex.test(customerInfo.phone) || customerInfo.phone.replace(/\D/g, '').length < 10) { errors.push('Valid 10-digit phone number is required'); } return errors; } function validateServiceAddress(address) { const errors = []; if (!address.street) errors.push('Street address is required'); if (!address.city) errors.push('City is required'); if (!address.state) errors.push('State is required'); if (!address.zipCode) errors.push('ZIP code is required'); if (!address.country) errors.push('Country is required'); return errors; } ``` ## Common Mistakes to Avoid **Wrong**: Using reseller email/phone **Right**: Using customer's actual contact info Why: Providers need to reach the customer directly for scheduling and support. **Wrong**: Missing unit numbers, abbreviated city names **Right**: Complete address with all components Why: Address errors cause order rejections and delays. **Wrong**: Local number without area code, international number without country code **Right**: Full phone number with proper formatting Why: Providers can't schedule without valid contact number. **Wrong**: Using disposable or temporary email addresses **Right**: Customer's permanent email address Why: Customers need ongoing access for account management and billing. ## Next Steps How to place orders What happens after submission # Reseller Overview Source: https://offergrid.io/docs/resellers/index Start selling essential services through the Offergrid marketplace ## Welcome, Resellers Offergrid connects you with service providers offering internet and electricity services. Access a comprehensive catalog, place orders for your customers, and track fulfillment—all through one platform. ## Why Use Offergrid? Access service offers from multiple providers across internet and electricity—all in one place. No need to manage individual partnerships or integrations. APIs built specifically for resellers and agents. Integrate seamlessly into your existing workflows, whether you're using property management systems, CRM platforms, or custom tools. ## Getting Started Sign up at [offergrid.io](https://offergrid.io) and register as a reseller. Complete reseller verification to access the full service catalog. Generate a Team API Key to access the catalog and place orders via API. Search and filter services by category, price, location, and features. Submit an order with customer details and start earning commissions. ## How It Works ### 1. Browse Service Offers Search the catalog by category, price range, and service address. See exactly what's available at any location—internet and electricity services from multiple providers. ### 2. Compare Options Evaluate offers side-by-side based on pricing, service specifications, provider reputation, and availability in your markets. Find the best fit for each customer. ### 3. Place Orders Submit orders with customer contact information, service address, selected services, and any special notes or preferences. Orders are automatically routed to the right provider. ### 4. Track Fulfillment Monitor order progress from provider acceptance through scheduling, installation, service activation, and completion. Stay informed every step of the way. ## What You Can Do Search and filter available services Submit orders for your customers Monitor fulfillment status Integrate with your systems ## Service Categories Available services: * **Internet**: Broadband, fiber, cable, DSL, wireless * **Electricity**: Deregulated retail electricity plans * **Other**: Additional service categories ## Common Use Cases ### Property Management Order services for: * New tenant move-ins * Property improvements * Bulk service packages * Multi-unit buildings ### Real Estate Coordinate services for: * Homebuyers before move-in * New construction properties * Home staging needs * Closing gifts and incentives ### Corporate Facilities Source services for: * New office locations * Office expansions * Vendor changes * Multiple properties ### Service Aggregation Bundle services for: * Hospitality properties * Multi-family housing * Commercial clients * Corporate accounts ## Need Help? Follow our step-by-step checklist Explore the Reseller API endpoints Questions? Contact us at [support@offergrid.io](mailto:support@offergrid.io) # Order Lifecycle Source: https://offergrid.io/docs/resellers/order-lifecycle Understanding how orders progress from submission to completion ## Complete Order Flow ``` Submission → Provider Review → Acceptance → Scheduling → Installation → Activation → Payment ``` ## Stage-by-Stage Breakdown ### 1. Order Submission **Your action**: Submit order with customer details **Status**: `pending` **Duration**: Instant **What happens**: Order validated and created in system ### 2. Provider Review **Provider action**: Review order and check serviceability **Status**: `submitted_to_provider` **Duration**: 0-48 hours (usually within 24 hours) **What happens**: * Provider verifies address * Checks service availability * Confirms capacity ### 3. Acceptance or Rejection **Provider action**: Accept or reject with reason **Status**: `accepted` or `rejected` **Duration**: Immediate after review **What happens**: * **If accepted**: Proceeds to scheduling * **If rejected**: Order ends, provider gives reason ### 4. Customer Contact & Scheduling **Provider action**: Contact customer to schedule **Status**: `scheduled` **Duration**: 1-3 days after acceptance **What happens**: * Provider calls/emails customer * Installation date/time confirmed * Appointment details finalized Typical installation windows: 5-10 days from acceptance, depending on service type and provider capacity. ### 5. Installation & Activation **Provider action**: Send technician, install service **Status**: `in_progress` → `completed` or `active` **Duration**: 1-4 hours on-site **What happens**: * Technician arrives * Installs equipment * Activates service * Tests functionality * Customer signs off ### 6. Service Active **Status**: `active` **What happens**: * Service is live * Customer can use service * Provider creates account * Billing begins ### 7. Commission Payment **Your reward**: Earn commission **Timing**: Varies by provider **What happens**: * Commission credited to your account * Paid according to payment schedule (usually monthly) * Recurring commissions (if applicable) ## Typical Timelines ### Fast Track (3-7 days) Services with no installation: * Some electricity plans * Instant activation services ``` Day 0: Submit order Day 1: Accepted and activated Day 2: Commission earned ``` ### Standard Track (7-14 days) Most services: * Internet installations ``` Day 0: Submit order Day 1: Provider accepts Day 3: Installation scheduled Day 10: Installation completed Day 11: Commission earned ``` ### Extended Track (14-30 days) Complex installations: * Business services * Multi-unit properties * Custom solutions ``` Day 0: Submit order Day 2: Provider accepts Day 5: Site survey scheduled Day 10: Installation scheduled Day 20: Installation completed Day 21: Commission earned ``` ## Status Updates You'll Receive ### Automatic Notifications You'll be notified when: * Order accepted * Order rejected * Installation scheduled * Installation completed * Order cancelled ### Provider Notes At each stage, providers may include: * Next steps * Timeline estimates * Customer contact info * Account details * Special instructions ## What Can Go Wrong ### Common Issues **Address problems**: * Invalid address * Missing unit number * Outside service area **Customer issues**: * Can't reach customer * Customer not home for installation * Customer cancels **Technical problems**: * Equipment not available * Installation complications * Service interruptions ### How to Respond Contact customer, explain reason, offer alternatives Update customer immediately, get new timeline from provider Verify contact info, try alternate methods, update provider Work with provider to reschedule or resolve technical issues ## Lifecycle by Service Type ### Internet ``` Submit → Accepted (24h) → Scheduled (3-5 days) → Installed (7-10 days) → Active ``` ### Electricity ``` Submit → Accepted (24h) → Account Setup (3-5 days) → Active ``` (Often no installation required) ## Tracking Multiple Orders When managing many orders: **By status**: * Pending: Need to monitor for acceptance * Scheduled: Remind customers of appointments * In Progress: Available for customer questions * Completed: Follow up for satisfaction **By timeline**: * This week: Orders installing soon * Next week: Upcoming installations * Overdue: Orders taking longer than expected ## Best Practices Tell customers typical timelines for their service type. Under-promise, over-deliver. Update them before they ask. Proactive communication builds trust. If an order is delayed beyond reasonable timelines, contact provider support. Monitor how long each stage typically takes. Identify outliers. ## Next Steps How to monitor orders Canceling orders # Order Submission Process Source: https://offergrid.io/docs/resellers/order-submission What happens after you submit an order ## Submission Workflow After you click "Place Order" or submit via API: Offergrid validates the order data (customer info, service address, offer availability) Order is created with status `pending`. You receive an order ID. Order is routed to the service provider with status `submitted_to_provider` Provider checks serviceability and capacity (usually within 24 hours) Provider either accepts the order or rejects it with a reason ## Order Status Progression ### Success Path ``` pending → submitted_to_provider → accepted → scheduled → in_progress → completed → active ``` ### Rejection Path ``` pending → submitted_to_provider → rejected ``` ## What You Receive ### Immediate Response Upon successful submission: ```json theme={null} { "orderId": "ord-123-abc", "items": [ { "itemId": "item-456-def", "offerId": "off-789-ghi", "status": "pending" } ], "createdAt": "2025-01-02T10:00:00Z" } ``` Save the `orderId` and `itemId` values to track this order. ### Provider Response (within 24-48 hours) **If accepted**: ```json theme={null} { "status": "accepted", "providerNotes": "Order accepted. Customer will be contacted within 24 hours to schedule installation." } ``` **If rejected**: ```json theme={null} { "status": "rejected", "providerNotes": "Service not available at this address. Building does not have fiber infrastructure." } ``` ## Tracking Submissions ### Via Dashboard 1. Navigate to **Orders** 2. View all submitted orders 3. Filter by status to see pending orders 4. Click an order for detailed history ### Via API ```bash theme={null} # List all your orders GET /reseller/orders # Get specific order GET /reseller/orders/{orderId} # Filter by status GET /reseller/orders?status=pending ``` ### Via Webhooks Receive real-time notifications: ```json theme={null} { "event": "order.status_changed", "orderId": "ord-123-abc", "itemId": "item-456-def", "newStatus": "accepted", "providerNotes": "..." } ``` ## Common Submission Issues ### Order Validation Errors **Invalid customer email**: ```json theme={null} { "error": "Invalid email format", "field": "customerInfo.email" } ``` **Missing required field**: ```json theme={null} { "error": "Service address is required", "field": "serviceAddress" } ``` **Offer not found**: ```json theme={null} { "error": "Offer not found or not available to your team", "field": "items[0].offerId" } ``` ### Provider Rejections Common rejection reasons: 1. **Service not available** * Address outside service area * ZIP code served but not specific address * Technology unavailable (no fiber infrastructure) 2. **Address issues** * Invalid or incomplete address * Cannot verify address * Building restrictions (HOA, landlord policy) 3. **Technical limitations** * No line of sight (satellite/wireless) * Distance too far from equipment (DSL) * Building wiring incompatible 4. **Business reasons** * Credit check requirement not met * Duplicate order for same customer * Service area at capacity ## Handling Rejections When an order is rejected: Understand why the order was rejected Explain the reason clearly and professionally * Different technology (cable instead of fiber) * Different provider * Different service tier * Correct address errors * Obtain building approvals * Resolve credit check requirements ## Resubmitting Orders If you can fix the issue: 1. **Don't resubmit identical order** - It will likely be rejected again 2. **Address the root cause** - Fix the address, get approvals, etc. 3. **Submit new order** - Create a fresh order with corrections 4. **Add notes** - Explain what was fixed Example: ```json theme={null} { "items": [{ "offerId": "off-789-ghi" }], "customerInfo": { ... }, "serviceAddress": { "street": "123 Main St, Building B", "city": "San Francisco", "state": "CA", "zipCode": "94102", "country": "US" }, "notes": "Resubmitting with corrected building designation. Previous order rejected due to incomplete address." } ``` ## Best Practices Store order and item IDs in your system for future reference and tracking. Tell customers it may take 24-48 hours for provider confirmation, then 5-10 days for installation. Check pending orders daily. Follow up if no response after 48 hours. Track common rejection reasons and address them proactively in future orders. Update customers as soon as you hear from providers, whether accepted or rejected. ## Next Steps Monitor order fulfillment Understanding order status progression How to cancel orders Complete API documentation # Reseller Quick Start Source: https://offergrid.io/docs/resellers/quickstart Get up and running as a reseller in minutes ## Reseller Onboarding Checklist Follow these steps to start selling services through Offergrid. ### 1. Account Setup 1. Visit [offergrid.io](https://offergrid.io) 2. Click **Sign Up** 3. Choose **Reseller** as your organization type 4. Complete your company profile Complete reseller verification: * Business information * Tax ID (if applicable) * Contact details * Banking information for commission payments Verification typically takes 1-2 business days. 1. Go to **Settings** → **API Keys** 2. Click **Generate New Key** 3. Save your key securely 4. Store it in environment variables ### 2. Explore the Catalog 1. Navigate to **Catalog** in the sidebar 2. Browse available services 3. Use filters to narrow results: * Category (Internet, Electricity, Other) * Price range * ZIP code availability * Search terms 4. Click on an offer to see full details ```bash theme={null} curl -X GET "https://api.offergrid.io/reseller/catalog?category=internet&zipCode=94102" \ -H "x-api-key: YOUR_API_KEY" ``` See the full [API documentation](/docs/api-reference/introduction) for all available filters. ### 3. Place Your First Order Choose an offer that matches your customer's needs Collect: * Full name * Email address * Phone number * Service address (including unit number if applicable) Via dashboard or API: ```bash theme={null} curl -X POST https://api.offergrid.io/reseller/orders \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [{ "offerId": "off-123-abc" }], "customerInfo": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+1-555-123-4567" }, "serviceAddress": { "street": "123 Main St", "city": "San Francisco", "state": "CA", "zipCode": "94102", "country": "US" }, "notes": "Customer prefers afternoon installations" }' ``` Monitor order status in your dashboard or via API polling/webhooks ### 4. Track Order Fulfillment 1. Navigate to **Orders** in the sidebar 2. View all orders and their current status 3. Click on an order for detailed history 4. See provider updates and notes ```bash theme={null} # List all your orders curl -X GET "https://api.offergrid.io/reseller/orders" \ -H "x-api-key: YOUR_API_KEY" # Get specific order details curl -X GET "https://api.offergrid.io/reseller/orders/ORDER_ID" \ -H "x-api-key: YOUR_API_KEY" ``` Receive real-time notifications when order status changes. See [Webhooks](/docs/resellers/webhooks) for setup. ### 5. Get Paid Earn commission when services are successfully activated Check your commission dashboard for pending and paid amounts Payments processed according to your payment schedule (typically monthly) ## Next Steps Learn advanced catalog search and filtering Understand order requirements and best practices Monitor fulfillment and communicate with customers Integrate Offergrid into your systems ## Common Questions Reseller verification typically takes 1-2 business days. You'll receive an email when approved. Limited access is available before verification. Full catalog access requires completed verification. You earn commission when orders are successfully fulfilled and activated. Commission rates vary by provider and service type. If a customer cancels before service activation, no commission is earned. Cancel orders through the dashboard or API. Yes! Search the catalog by ZIP code to find services available in any location. ## Tips for Success Double-check service addresses before submitting orders. Address errors cause delays and rejections. Inform customers about typical installation timelines (usually 3-10 days depending on service). Share order status updates with customers as providers move through the fulfillment workflow. Show customers 2-3 options when available to increase conversion and provide value. Internet is typically highest volume. Electricity plans often have higher commissions. ## Need Help? Contact us at [support@offergrid.io](mailto:support@offergrid.io) or check out our [API documentation](/docs/api-reference/introduction). # Understanding Service Categories Source: https://offergrid.io/docs/resellers/service-categories Learn about different service types available on Offergrid ## Service Categories Offergrid offers two main service categories, plus a catch-all `other` for anything else. Understanding each helps you match customers with the right solutions. ## Internet Services **Typical pricing**: $40-$120/month **Key factors to compare**: * Download/upload speeds * Data caps or unlimited * Connection type (Fiber, Cable, DSL, Wireless) * Contract terms * Installation fees **Customer questions to ask**: * How many people/devices will use it? * What do they use internet for? (streaming, gaming, work-from-home) * Is fiber available at their address? **High-commission opportunities**: Fiber plans, business internet ## Electricity Services **Typical pricing**: Varies by usage **Key factors**: * Fixed vs. variable rates * Contract length * Renewable energy percentage * Early termination fees **Customer questions**: * Current provider and rate? * Average monthly usage? * Interest in renewable energy? **High-commission opportunities**: Long-term fixed-rate contracts ## Best Practices by Category Most customers care about speed first. Ask about usage patterns to recommend appropriate tiers. Compare current rate to offered rate. Show potential monthly/annual savings. ## Next Steps Search and filter services Evaluate and compare options # Tracking Orders Source: https://offergrid.io/docs/resellers/tracking-orders Monitor order status and communicate with customers ## Overview Track order progress in real-time from submission through activation. Keep customers informed and address issues proactively. ## Viewing Orders ### Via Dashboard 1. Navigate to **Orders** in the sidebar 2. View all orders with current status 3. Filter by status, date, or customer 4. Click an order for full details and history ### Via API ```bash theme={null} # List all orders GET /reseller/orders # Get specific order GET /reseller/orders/{orderId} # Filter by status GET /reseller/orders?status=scheduled ``` ## Order Status Meanings ### Active States **`pending`** - Just submitted, awaiting provider review **`submitted_to_provider`** - Sent to provider, awaiting acceptance **`accepted`** - Provider confirmed they can fulfill, will schedule **`scheduled`** - Installation/activation date set **`in_progress`** - Installation happening now **`completed`** - Installation finished **`active`** - Service live and operational ### Inactive States **`rejected`** - Provider cannot fulfill **`cancelled`** - Order cancelled by you or customer **`failed`** - Installation attempted but failed ## Order Timeline View complete history: ```json theme={null} { "orderId": "ord-123-abc", "status": "scheduled", "history": [ { "status": "pending", "timestamp": "2025-01-02T10:00:00Z", "notes": "Order created" }, { "status": "submitted_to_provider", "timestamp": "2025-01-02T10:00:05Z", "notes": "Sent to provider" }, { "status": "accepted", "timestamp": "2025-01-02T14:30:00Z", "providerNotes": "Order accepted. Customer will be contacted within 24 hours." }, { "status": "scheduled", "timestamp": "2025-01-03T09:15:00Z", "providerNotes": "Installation scheduled for Tuesday, Jan 15, 1-5 PM", "scheduledFor": "2025-01-15T13:00:00Z" } ] } ``` ## Provider Notes Providers include helpful updates: **At acceptance**: ``` "Order accepted. Customer will be contacted within 24 hours to schedule installation." ``` **At scheduling**: ``` "Installation scheduled for Tuesday, Jan 15, 1-5 PM. Technician Mike Johnson will call 30 minutes before arrival." ``` **At completion**: ``` "Service activated successfully. Customer account #12345. All services tested and operational." ``` Relay these updates to your customers! ## Communicating with Customers ### At Submission "I've submitted your order for \[Service Name]. The provider will review and contact you within 24-48 hours to schedule installation." ### After Acceptance "Good news! \[Provider] accepted your order and will call you today or tomorrow to schedule installation." ### After Scheduling "Your installation is scheduled for \[Date], \[Time Window]. The technician will call 30 minutes before arrival. Here's the confirmation: \[Details]" ### After Completion "Your service is now active! Account #\[Number]. Contact \[Provider] at \[Phone] if you have any questions." ## Monitoring Multiple Orders ### Dashboard Views * **All Orders**: Complete order history * **Pending**: Awaiting provider response * **In Progress**: Currently being fulfilled * **Completed**: Successfully activated ### Setting Up Alerts Get notified when: * Order accepted by provider * Order rejected (investigate and contact customer) * Installation scheduled (share details with customer) * Order completed (confirm customer satisfaction) ## Handling Delays ### If No Response After 48 Hours 1. Check order status in dashboard 2. Contact provider support if still pending 3. Update customer: "Checking on your order status, will update shortly" 4. Follow up daily until resolved ### If Installation Delayed 1. Check provider notes for explanation 2. Contact provider for updated timeline 3. Inform customer immediately 4. Offer alternatives if significant delay ## Order Metrics Track your performance: ### Volume Metrics * Total orders submitted * Orders per week/month * Growth trends ### Quality Metrics * Acceptance rate (target: >85%) * Cancellation rate (target: \<10%) * Time to completion (average days) ### Revenue Metrics * Commission earned * Average order value * Repeat customer rate ## Best Practices Review new status updates every morning. Respond to changes promptly. Update customers before they ask. Share provider notes as soon as you receive them. Store account numbers, confirmation codes, and technician info for customer reference. Contact customers 1-2 days after installation to ensure satisfaction. Note any issues or delays. Use patterns to improve future orders. ## Next Steps Understanding order progression How to cancel orders Automate order tracking Complete API documentation # Webhooks Source: https://offergrid.io/docs/resellers/webhooks Receive signed, real-time events for the orders your team placed ## Overview Webhooks push order-lifecycle events to your systems as they happen, so you can react to a provider accepting, scheduling, or completing an order without polling `GET /reseller/orders`. Register an HTTPS endpoint, subscribe it to the event types you care about, and Offergrid POSTs a signed JSON envelope to it every time one of those events occurs **on an order your team placed**. Every delivery attempt — success or failure — is recorded and queryable via [`GET /reseller/webhooks/{id}/deliveries`](/docs/reseller-api-reference/reseller-webhooks/list-recent-delivery-attempts-for-a-webhook). That log is the first place to look when something appears to be missing. ## Registering a webhook The response contains the signing secret, and it is shown **only once** — store it before you discard the response. ```bash theme={null} curl -X POST https://api.offergrid.io/reseller/webhooks \ -H "x-api-key: YOUR_TEAM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-system.example.com/offergrid-callback", "events": ["order.item.status_changed", "order.created"] }' ``` The `url` must be `https://` — plain HTTP is rejected at validation. | Operation | Endpoint | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Register a webhook | [`POST /reseller/webhooks`](/docs/reseller-api-reference/reseller-webhooks/register-a-webhook) | | List your webhooks | [`GET /reseller/webhooks`](/docs/reseller-api-reference/reseller-webhooks/list-your-registered-webhooks) | | Update url, events, or active state | [`PATCH /reseller/webhooks/{id}`](/docs/reseller-api-reference/reseller-webhooks/update-a-webhook-url-subscribed-events-or-active-state) | | Delete a webhook | [`DELETE /reseller/webhooks/{id}`](/docs/reseller-api-reference/reseller-webhooks/delete-a-webhook) | | Inspect delivery attempts | [`GET /reseller/webhooks/{id}/deliveries`](/docs/reseller-api-reference/reseller-webhooks/list-recent-delivery-attempts-for-a-webhook) | To pause deliveries without losing the webhook or its secret, `PATCH` it with `{"isActive": false}` rather than deleting it. ## Event types | Event | When it fires | Why you care | | --------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------- | | `order.item.status_changed` | A provider moved one of your order items | The main one — accepted, rejected, scheduled, completed, failed | | `order.created` | An order was placed under your team | Catches orders you did not place yourself: shareable links and shop checkout | | `order.item.created` | An item was added to one of your orders | Per-item detail at order time, including the provider and snapshotted price | | `order.cancelled` | One of your orders was cancelled | Stop any downstream workflow | You only receive events for orders where your team is the reseller. Providers subscribe to the same four event types and receive them scoped to *their* offers instead — same event, different audience. If your team is **hybrid**, one webhook covers both roles and you receive each event exactly once. Compare `providerTeamId` and `resellerTeamId` in the payload against your own team id to tell which side of an order you are on. ## Payload structure Every delivery is a POST with `Content-Type: application/json` and this envelope: ```json theme={null} { "id": "8f2a1c6e-...", "type": "order.item.status_changed", "version": 1, "data": { } } ``` | Field | Description | | --------- | --------------------------------------------------------------- | | `id` | Unique event id. Use it as your idempotency key. | | `type` | One of the four event types above. | | `version` | Envelope version, currently `1`. | | `data` | Event-specific payload, snapshotted when the event was emitted. | ### `order.item.status_changed` ```json theme={null} { "id": "b4c3d2e1-0000-4000-8000-000000000001", "type": "order.item.status_changed", "version": 1, "data": { "orderId": "3c9d8b7a-1e2f-4a5b-8c7d-6e5f4a3b2c1d", "orderItemId": "7b6a5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d", "providerTeamId": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b", "resellerTeamId": "2f1e0d9c-8b7a-4655-9483-1a2b3c4d5e6f", "from": "pending", "to": "accepted", "orderStatus": "in_progress" } } ``` `from` and `to` are the item's statuses; `orderStatus` is the parent order's status recomputed from all of its items — so you can update an order-level view without a follow-up read. See [Order Lifecycle](/docs/resellers/order-lifecycle) for the full status set. ### Other event payloads `order.created`, `order.item.created`, and `order.cancelled` carry the same payloads documented on the provider side: [order.created](/docs/providers/webhooks#order-created) · [order.item.created](/docs/providers/webhooks#order-item-created) · [order.cancelled](/docs/providers/webhooks#order-cancelled). ## Verifying signatures Signed deliveries carry a single header: ``` X-Offergrid-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd ``` There is **no separate timestamp header** — the timestamp is the `t=` component and is also part of the signed payload, so a verifier can reject stale or replayed deliveries. The signature is `HMAC-SHA256` over the string `` `${t}.${rawBody}` ``, keyed with your webhook secret, hex-encoded. Sign over the **raw request body**, not a re-serialized copy. `JSON.parse` followed by `JSON.stringify` can reorder keys or change number formatting, and the signature will not match. The scheme is identical for both roles — the worked Node and Python verifiers in [Verifying signatures](/docs/providers/webhooks#verifying-signatures) apply unchanged. ## Delivery behaviour | Property | Behaviour | | ------------------ | ------------------------------------------------------------------------ | | Request timeout | 10 seconds. A slower endpoint is recorded as a failed attempt. | | Success condition | Any 2xx response. Everything else — including 3xx — counts as a failure. | | Retries | Up to 5 attempts per event with exponential backoff. | | Delivery semantics | At-least-once. Expect duplicates and deduplicate on the envelope `id`. | | Ordering | Not guaranteed. Reconcile against the API rather than assuming sequence. | | Logging | Every attempt is logged with status code and a truncated response body. | Return 2xx as soon as you have durably accepted the event, then do the work in a background job: ```typescript theme={null} app.post('/offergrid-callback', async (req, res) => { if (!verified(req)) return res.status(401).send('Invalid signature'); await queue.add('offergrid-event', req.body); // durable, fast res.status(200).send('OK'); // then acknowledge }); ``` Deduplicate on the envelope `id`, which is stable across retries: ```typescript theme={null} async function handleEvent(envelope: { id: string; type: string; data: unknown }) { const inserted = await db.processedEvents.insertIfAbsent(envelope.id); if (!inserted) return; // already handled await processEvent(envelope.type, envelope.data); } ``` ## Testing your endpoint Point a webhook at a tunnel while you develop: ```bash theme={null} ngrok http 4000 ``` Register the tunnel URL, then place a test order and have the provider move it. That is the only way to exercise signature verification end to end — a hand-rolled cURL request carries no valid signature, so a correct verifier will reject it. To see what Offergrid actually sent and what your endpoint returned: ```bash theme={null} curl https://api.offergrid.io/reseller/webhooks/WEBHOOK_ID/deliveries \ -H "x-api-key: YOUR_TEAM_API_KEY" ``` ## Still want to poll? Webhooks do not replace reconciliation. Because delivery is at-least-once and unordered, a periodic sweep of [`GET /reseller/orders`](/docs/reseller-api-reference/reseller-orders/list-your-orders) is a good safety net — daily is plenty once webhooks are live. See [Tracking Orders](/docs/resellers/tracking-orders). ## Troubleshooting 1. Confirm the webhook is `isActive: true`. 2. Confirm it subscribes to the event type you expect — a webhook only receives types listed in its `events` array. 3. Check the delivery log. Attempts with a non-2xx status mean the problem is on your side; no attempts at all means no matching event was emitted for your team. 4. Confirm your team is the reseller on the order. You do not receive events for orders another reseller placed, even for the same offer. 1. Sign the **raw** body, not a re-serialized copy. 2. Read the timestamp from the `t=` component of `X-Offergrid-Signature`. There is no `x-offergrid-timestamp` header. 3. The signed string is the timestamp, a literal dot, then the raw body. 4. Compare hex-decoded buffers, checking lengths first: Node's `timingSafeEqual` throws on a length mismatch. 5. Confirm you stored the secret from the creation response — it is shown only once and cannot be retrieved later. Expected. Delivery is at-least-once, and a retry re-sends to every subscribed target. Deduplicate on the envelope `id`. ## Next steps Monitoring order status in the dashboard Every status an order and its items can be in Every reseller endpoint, including webhook management Broader integration patterns