Appearance
End-to-end example
One booking, from first connection to final reconciliation, with real request and response bodies. The ids are consistent in every step, so you can trace them.
- Account timezone:
Europe/Berlin. Currency:EUR. - Ad slot:
slot_728x90_home("Homepage Leaderboard"). - Advertiser:
Acme Corp. - DataFlair campaign reference:
CMP-VDWZQ4IT.
Step 1: Connect (once)
An operator enters your base_url and the credential you issued. DataFlair probes with GET /health.
http
GET /health
Authorization: Bearer sk_live_9f2c…bash
curl "$BASE_URL/health" -H "Authorization: Bearer $API_KEY"Your response:
json
{
"account_id": "acct_1029",
"account_name": "Example Media Network",
"timezone": "Europe/Berlin",
"currency": "EUR",
"capabilities": {
"forecast": true,
"reporting": true,
"draft_booking": true
}
}DataFlair records the account, the timezone and the currency, and marks the connection connected.
Step 2: Discover and map inventory
DataFlair calls GET /inventory.
http
GET /inventory?limit=100
Authorization: Bearer sk_live_9f2c…bash
curl "$BASE_URL/inventory?limit=100" -H "Authorization: Bearer $API_KEY"Your response:
json
{
"data": [
{ "inventory_id": "slot_728x90_home", "name": "Homepage Leaderboard", "sizes": ["728x90"], "format": "display", "status": "active" }
],
"next_cursor": null
}The operator maps the DataFlair placement "Homepage Leaderboard" to slot_728x90_home. DataFlair stores that mapping once and reuses it for every future booking of this slot.
Step 3: An advertiser forecasts before booking
Acme Corp is considering all of August, targeting Germany and Austria. DataFlair calls POST /forecast.
http
POST /forecast
Authorization: Bearer sk_live_9f2c…
Content-Type: application/jsonbash
curl -X POST "$BASE_URL/forecast" -H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" -d @request.jsonThe request body:
json
{
"inventory_id": "slot_728x90_home",
"flight": {
"start_date": "2026-08-01",
"end_date": "2026-08-31"
},
"targeting": {
"geo": [
"DE",
"AT"
]
},
"sizes": [
"728x90"
]
}Your response:
json
{
"inventory_id": "slot_728x90_home",
"available_impressions": 1420000,
"forecasted_impressions": 2100000,
"unit_type": "IMPRESSIONS"
}DataFlair shows Acme that about 1.42M impressions are bookable. Acme books 100,000, pays into their DataFlair wallet and submits the reservation.
Step 4: You approve the reservation (inside DataFlair)
Your team reviews the reservation in DataFlair and approves it. This is the trigger. Nothing has reached your ad server yet. The approval causes the next call.
Step 5: DataFlair pushes the booking as a draft
DataFlair calls POST /orders.
http
POST /orders
Authorization: Bearer sk_live_9f2c…
Content-Type: application/json
Idempotency-Key: DF-CMP-VDWZQ4ITbash
curl -X POST "$BASE_URL/orders" -H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" -H "Idempotency-Key: DF-CMP-VDWZQ4IT" \
-d @request.jsonThe request body:
json
{
"idempotency_key": "DF-CMP-VDWZQ4IT",
"advertiser": {
"name": "Acme Corp",
"external_ref": "brand_5501"
},
"order": {
"name": "Summer Launch (DF-CMP-VDWZQ4IT)",
"external_ref": "CMP-VDWZQ4IT"
},
"line_items": [
{
"external_ref": "DF-CMP-VDWZQ4IT-329",
"inventory_id": "slot_728x90_home",
"flight": {
"start_date": "2026-08-01",
"end_date": "2026-08-31"
},
"goal_impressions": 100000,
"targeting": {
"geo": [
"DE",
"AT"
]
},
"sizes": [
"728x90"
],
"creatives": [
{
"type": "third_party_tag",
"width": 728,
"height": 90,
"tag": "<iframe src='https://t.dataflair.ai/ad/ABC123?df_source=custom&df_ad_server=custom_platform' width='728' height='90' frameborder='0' scrolling='no' referrerpolicy='no-referrer-when-downgrade' sandbox='allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-same-origin' style='border:0;display:block' title='Advertisement'></iframe>"
}
]
}
]
}Your platform resolves or creates the advertiser "Acme Corp". It creates a draft order and one draft line item. The line item targets slot_728x90_home for August in DE and AT, with a goal of 100,000 impressions. It attaches the DataFlair tag and returns your ids:
json
{
"order_id": "ord_55021",
"status": "DRAFT",
"line_items": [
{
"external_ref": "DF-CMP-VDWZQ4IT-329",
"line_item_id": "li_88012",
"status": "DRAFT"
}
]
}DataFlair stores DF-CMP-VDWZQ4IT-329 → li_88012. Nothing is serving yet.
Step 6: A person takes it live (on your side)
Your ad-ops open the draft order ord_55021 in your own console, review it and activate it. DataFlair takes no part in this step. The line starts serving on 1 August.
Step 7: DataFlair pulls delivery to reconcile
Part way through the flight, and after it, DataFlair calls POST /reports.
http
POST /reports
Authorization: Bearer sk_live_9f2c…
Content-Type: application/jsonbash
curl -X POST "$BASE_URL/reports" -H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" -d @request.jsonThe request body:
json
{
"line_item_ids": ["li_88012"],
"date_range": { "start_date": "2026-08-01", "end_date": "2026-08-31" },
"granularity": "total"
}Your response:
json
{
"rows": [
{ "line_item_id": "li_88012", "impressions": 98240, "clicks": 173 }
]
}DataFlair matches li_88012 to the booked line DF-CMP-VDWZQ4IT-329. It records 98,240 delivered impressions against the goal of 100,000. It cross-checks its own tracking counts: clicks from the /go link and impressions from the in-frame pixel. Then it settles the campaign and pays the publisher net of commission. Reconciliation is done.
Idempotency in action
Now take a different run of the same booking. Acme's creative was still in review when you approved the reservation. The first POST /orders carried no creatives array, and the creative followed once it was approved. That is an update, not a retry. The second push uses a new idempotency_key with the same order and line external_ref values:
json
{ "idempotency_key": "DF-CMP-VDWZQ4IT-2", "order": { "external_ref": "CMP-VDWZQ4IT" }, "...": "same line external_refs, now with the creative" }Your platform matches on external_ref and does not create a second order or line. It updates the existing line li_88012 with the creative and returns the same ids with 200. DataFlair's stored mapping does not change.
Posting the original idempotency_key again with a changed body is a different case. That is a 409, and it is not an update path. See POST /orders and Conventions.