Appearance
Conventions
Rules that apply to every endpoint. Get these right and the integration works on the unhappy paths as well as the happy one.
Transport and versioning
- HTTPS only. DataFlair rejects plain HTTP before it sends a request.
- JSON in, JSON out. Send
Content-Type: application/jsonon requests with a body. Returnapplication/json. - Version in the path. Root your API at a versioned base, for example
https://ads.example.com/api/v1. Ship breaking changes as/v2. Do not change the meaning of a field in place.
Dates, timezone and flights
- All dates are
YYYY-MM-DD. They are calendar dates, not timestamps, unless a field is explicitly a datetime. - Dates are read in the account timezone you return from
GET /health. DataFlair sends flightstart_dateandend_datein that zone. A flight of2026-08-01to2026-08-31means the whole of August in your timezone, including both ends. - Declare the timezone. GAM and Revive both drive line item flights from the network timezone. A silent mismatch is the classic "the campaign started a day early or late" bug.
Currency
- Use ISO-4217 codes (
EUR,USDand so on), declared once inGET /health. - DataFlair checks a booking's currency against it before it pushes an order. It stops on a mismatch and does not guess. Its GAM integration follows the same discipline.
- You do not receive prices on the wire. Billing belongs to DataFlair.
Units
- Impressions and clicks are integers.
- Impression goals and forecast numbers are impression counts (
unit_type: "IMPRESSIONS").
Idempotency
- Write calls (
POST /orders) carry anidempotency_keyin the body and anIdempotency-Keyheader with the same value. idempotency_keyprotects one call against being replayed. It is not a stable identifier for the life of the order. A repeat with the same key and the same body must return the same result (200) and create nothing new. A repeat with the same key and a different body is a409. It means something upstream retried incorrectly. Do not apply the new body.- To update an order or line item you already created, for example to attach a creative once it clears approval, DataFlair sends a new
POST /orders. It carries a freshidempotency_keyand the sameorder.external_refandline_items[].external_refas the original call. Match onexternal_ref, update only what changed, and return200with the existingorder_idandline_item_idvalues. The full recovery contract is on POST /orders. - Read calls (
/health,/inventory,/forecast,/reports) are idempotent by nature. DataFlair can repeat them freely.
Pagination
List responses (GET /inventory, and any large POST /reports) use cursor pagination, not offset.
- Request.
limitandcursortravel as query parameters on aGET(?limit=<n>&cursor=<opaque>). On aPOSTthey travel as body fields next to the rest of the JSON payload, because that call already has a body. Use a sane default forlimit(for example 50) and a cap (for example 200). - Response. Include
next_cursor. Leave it out, or returnnull, on the last page. - Keyset paging, not
OFFSET, keeps DataFlair from missing or double-reading rows when data changes in the middle of a page.
Rate limiting
- If DataFlair goes over a budget, return
429 Too Many Requestswith aRetry-Afterheader in seconds. - DataFlair backs off and retries per
Retry-After. It does not hammer your server. Publish your per-credential budget in your handoff notes so DataFlair can pace itself.
Error model
Every non-2xx response returns a JSON body:
json
{
"code": "inventory_not_found",
"message": "No ad slot exists for inventory_id 'slot_x'.",
"details": {
"inventory_id": "slot_x"
}
}| Field | Type | Required | Meaning |
|---|---|---|---|
code | string | yes | A stable, machine-readable snake_case slug. |
message | string | yes | Human-readable text. DataFlair keeps it as the last error. Do not put secrets in it. |
details | object | no | Optional structured context. |
HTTP status usage
| Status | When | DataFlair's reaction |
|---|---|---|
200, 201 | Success | Proceeds |
400 | Malformed request | Treats it as a bug, logs it and shows it |
401 | Missing, invalid or expired credential | Marks the connection as error. The operator re-checks the credential. |
403 | Authenticated, but missing a scope | Marks the connection as error. The operator widens the credential's scope. |
404 | Unknown inventory_id, order_id or line_item_id | Skips that item and reports it. It does not force the item onto a booking. |
409 | Idempotency conflict (same key, different payload) | Logs it. DataFlair does not retry it blindly. |
422 | Valid shape, invalid values (a bad date range, an incompatible size) | Shows your message, so the operator can fix the booking |
429 | Rate limited | Backs off per Retry-After and retries |
501 | A capability is not implemented (for example forecast for one slot) | Degrades gracefully. See POST /forecast. |
5xx | Server error | Retries with backoff. A persistent 5xx shows as "your platform is unreachable". |
Security expectations
- Hash credentials at rest. Do not log them in plaintext. DataFlair holds the keys it stores to the same rule.
- Do not echo the credential, or any secret, in a response body or an error.
- Do not put credentials or personal data in a URL or query string. DataFlair sends credentials only in the
Authorizationheader. - The credential you issue must be revocable, so either side can rotate it if it leaks, without downtime.