---
url: https://docs.dataflair.ai/marketplace/ad-server-api/operations/orders.md
description: >-
  DataFlair pushes an approved reservation into your ad server as an order with
  draft line items. It does not activate anything.
---

# POST /orders: Create draft order

When you approve an advertiser's reservation in DataFlair, DataFlair pushes it into your ad server as an **order with one draft line item for each booked inventory line**. An inventory line is an ad slot, a geo and a month. A booking that covers two months, or two countries priced separately, arrives as several line items on the same slot. Your ad-ops team then reviews the draft and takes it live.

This is the same operation as GAM's "create a DRAFT order and line items" and Revive's "create an inactive campaign and banners".

DataFlair triggers this call when a reservation is approved. It can call it again for the same campaign. See [Idempotency and recovery](#idempotency-and-recovery).

## Draft only {#draft-only}

::: danger DRAFT ONLY
DataFlair does not call activate, approve, go-live, unpause or publish. None of them is in the adapter, by design. Everything this endpoint creates must be a draft that does not serve.
:::

* Create the order and its line items in your platform's **draft, paused or inactive** state.
* Do not activate, approve, unpause or start serving anything as a side effect of this call.
* Return the objects in that non-serving state, and echo `status: "DRAFT"`.

Going live is a human action in your console. DataFlair applies the same rule to Google Ad Manager, where it does not call `performOrderAction`. It applies it to Revive, where it creates banners as `INACTIVE` and does not link them to a zone. If your `POST /orders` made inventory serve at once, it would break the guarantee DataFlair gives every publisher.

## What we send

::: code-group

```http [HTTP]
POST /orders HTTP/1.1
Host: ads.example.com
Authorization: Bearer sk_live_9f2c…
Content-Type: application/json
Idempotency-Key: DF-CMP-VDWZQ4IT
```

```bash [curl]
curl -X POST "$BASE_URL/orders" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: DF-CMP-VDWZQ4IT" \
  -d @request.json
```

:::

The body, saved as `request.json`:

::: code-group

```json \[exemplary/orders/request.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>"
        }
      ]
    }
  ]
}

```

:::

The JSON on this page is read from the [fixture files](/marketplace/ad-server-api/fixtures), so it cannot drift from the test suite.

### Fields

| Field | Type | Required | Meaning |
| --- | --- | --- | --- |
| `idempotency_key` | string | yes | Unique to **this call**, not to the campaign. Reuse it only to retry the exact same request. A retry with the same key and a different body is rejected (see below). DataFlair also sends it as the `Idempotency-Key` header. |
| `advertiser.name` | string | yes | The buyer. **Resolve or create** an advertiser by this name (see below). |
| `advertiser.external_ref` | string | no | DataFlair's opaque id for the advertiser, if you want to store it. |
| `order.name` | string | yes | A human label, for example `Summer Launch (DF-CMP-VDWZQ4IT)`. It is deterministic, so a lost response can be recovered by name. |
| `order.external_ref` | string | yes | DataFlair's campaign reference, stable for the life of the campaign. Match on this, not on `idempotency_key`, to recognize an update to an order you already created (see below). |
| `line_items[].external_ref` | string | yes | DataFlair's stable id for **this booked line**. It is unique even when a booking splits one slot into several lines by geo or month, for example `DF-{campaign}-{line}` and not `DF-{campaign}-{slot}`. Two lines can share a slot, and they cannot share an `external_ref`. Return it in the response so DataFlair can match your `line_item_id` to it. |
| `line_items[].inventory_id` | string | yes | The slot to book. See [Inventory identity](/marketplace/ad-server-api/inventory). |
| `line_items[].flight` | object | yes | Inclusive `start_date` and `end_date`, `YYYY-MM-DD`, in your account timezone. |
| `line_items[].goal_impressions` | integer | yes | The impression goal the advertiser bought for this line. Feed it into your delivery and pacing engine. |
| `line_items[].targeting.geo` | array of strings | no | ISO-3166-1 alpha-2 countries. Absent or empty means worldwide. |
| `line_items[].sizes` | array of strings | yes | Creative size or sizes for the line. They must fit the slot. |
| `line_items[].creatives` | array | recommended | The creative or creatives to serve: a ready-made tag from DataFlair, stored and served as sent. See [How creatives work](#how-creatives-work). |

### How creatives work

You do not receive an image to host. DataFlair supplies a **ready-to-serve tag that points back to DataFlair**, and your platform stores and serves that tag as sent. The creative asset lives on DataFlair's CDN. An advertiser can swap the approved creative mid-flight, and nothing on your side changes.

DataFlair sends the creative in one of two shapes, depending on what your platform's creative model supports.

**A. Third-party or HTML tag (primary).** An `<iframe>` that points at DataFlair's ad frame. The request above carries this shape.

```html
<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>
```

Store it as a third-party or HTML creative and serve it into the slot. GAM stores it as a `ThirdPartyCreative`. Revive stores it as banner HTML. When it renders, the frame loads the current approved creative from DataFlair's CDN. It also fires its click (`/go/{code}`) and impression (`/imp/{code}`) tracking from inside the frame. You do not build the iframe. DataFlair builds it, and you store it as it arrives.

::: tip Treat the tag as an opaque string
The `/ad`, `/go` and `/imp` paths are DataFlair's live production endpoints. The link code (`ABC123`), the asset host and the query parameters (`df_source=…`, DataFlair's source attribution tag, minted per platform when the adapter is built) are illustrative here. The live tag arrives ready-made for each line item. Store it and serve it exactly as received.
:::

**B. Image fields (fallback).** If your platform only accepts a hosted image with a click-through and cannot serve an HTML tag, DataFlair sends the pieces as structured fields.

```json
{
  "type": "image",
  "width": 728, "height": 90,
  "image_url": "https://cdn.dataflair.ai/c/ABC123.png",
  "click_url": "https://t.dataflair.ai/go/ABC123?df_source=custom&df_ad_server=custom_platform",
  "impression_pixel_url": "https://t.dataflair.ai/imp/ABC123?df_source=custom&df_ad_server=custom_platform"
}
```

Wire the creative's click-through to `click_url`, and fire `impression_pixel_url` when it renders.

Rules for both shapes:

* **Keep the `/go/{code}` click link exactly as sent.** Do not rewrite it or strip it. Every DataFlair campaign must serve through it, because that is how clicks are attributed and reconciled. A line that bypasses it cannot be reconciled.
* **You still count impressions natively.** The impression count your ad server keeps is what you return in [reports](/marketplace/ad-server-api/operations/reports/). The in-frame pixel is DataFlair's independent cross-check. It does not replace your count.
* **No approved creative yet?** Create the line item empty, as a draft. When the creative is approved, expect a follow-up `POST /orders` that carries it. It has a **new** `idempotency_key` and the **same** `order.external_ref` and `line_items[].external_ref`. Match on `external_ref` and update the existing line in place (see below).
* **Optional cache-buster.** If your platform can fill a cache-buster macro inside a third-party tag, as GAM does with `%%CACHEBUSTER%%`, tell DataFlair, and it will append one for tighter impression reconciliation. If your platform cannot, DataFlair behaves as it does for Revive, with no macro.

Where each party sees the creative: your **ad-ops** see the tag, with a preview, on the line item in your console. The **advertiser** manages the actual asset inside DataFlair. The **site visitor** sees the rendered ad once a person takes the line live.

## What you return

Status `201` when you create the order. A repeat or an update returns `200` with the same ids.

::: code-group

```json \[exemplary/orders/expected-response.json]
{
  "order_id": "ord_55021",
  "status": "DRAFT",
  "line_items": [
    {
      "external_ref": "DF-CMP-VDWZQ4IT-329",
      "line_item_id": "li_88012",
      "status": "DRAFT"
    }
  ]
}

```

:::

| Field | Type | Required | Meaning |
| --- | --- | --- | --- |
| `order_id` | string | yes | Your stable id for the order container. DataFlair stores it. |
| `status` | string | yes | Echo `"DRAFT"`. |
| `line_items[].external_ref` | string | yes | The same `external_ref` DataFlair sent, so it can match the pair. |
| `line_items[].line_item_id` | string | yes | **Your stable id for the line you created.** DataFlair stores it and reconciles delivery against it in [reports](/marketplace/ad-server-api/operations/reports/). It must not change for this booked line. |
| `line_items[].status` | string | yes | `"DRAFT"`. |

## Advertiser resolution

Resolve or create the advertiser from `advertiser.name`. If an advertiser with that name exists on your platform, reuse it. Otherwise create it. DataFlair does the same on GAM (`getOrCreateAdvertiser`) and on Revive (find or add by a deterministic name), so a repeat booking from the same brand does not create duplicate advertiser records. You do not need to store DataFlair's `external_ref` unless it helps you.

## Idempotency and recovery {#idempotency-and-recovery}

DataFlair can call `POST /orders` more than once for the same campaign. It happens on an automatic retry, when a publisher clicks "re-push", and when a later creative approval re-traffics. There are two cases. They use different keys.

* **Literal retry: the same `idempotency_key`.** DataFlair repeats the exact same call, for example because the response to the first attempt was lost. Return the same result and create nothing new. If the same key arrives with a **different** body, something upstream is broken. Reject it with `409`. Do not guess which body is the right one. See [Conventions](/marketplace/ad-server-api/conventions#idempotency).
* **Update: a new `idempotency_key` and the same `external_ref`.** A later creative approval, or any other change to an order or line you already created, arrives as a new call. It has a fresh `idempotency_key` and the same `order.external_ref` and `line_items[].external_ref` values you were sent at first. Match on `external_ref`. Update only the fields that changed and leave everything else alone. Return the same `order_id` and `line_item_id` values with `200`.
* In both cases, create only the lines that are genuinely missing. Do not duplicate an advertiser, an order or a line.

DataFlair records `external_ref` as a pending mapping before it calls you. It fills in your `line_item_id` when the response arrives. If the response is lost, it recovers the mapping by looking up the same `external_ref` on the retry. So re-pushing is safe as long as your side keys retry safety on `idempotency_key` and update matching on `external_ref`. The GAM and Revive integrations use the same deterministic naming recovery.

## Currency

DataFlair checks that the booking currency matches your account `currency` (from [`GET /health`](/marketplace/ad-server-api/operations/health)) before it pushes. You do not receive a price on the line, because billing stays entirely in DataFlair. The flight, the goal, the targeting and the creative are everything your ad server needs to serve once a person activates the line.

## Errors

| Status | What caused it | What DataFlair does | What to do |
| --- | --- | --- | --- |
| `400` | Malformed request. | Treats it as a bug, logs it and shows it. | Check the body against the fields above. |
| `401` | Missing, invalid or expired key. | Marks the connection as `error`. | Check the key. |
| `403` | The key lacks a required scope. | Marks the connection as `error`. | Give the key create-draft scope. |
| `404` | Unknown `inventory_id`. | Skips the item and reports it. | Return `404` only for a slot that does not exist. |
| `409` | The same `idempotency_key` arrived with a different body. | Logs it. DataFlair does not retry it blindly. | Do not apply the new body. Reject the call. |
| `422` | Valid shape, invalid values, such as a bad date range or an incompatible size. | Shows your `message`, so the operator can fix the booking. | Return a clear `message`. |
| `429` | Rate limit exceeded. | Backs off and retries per `Retry-After`. | Send `Retry-After` in seconds. |
| `5xx` | Server error. | Retries with backoff. | Check your server's logs. |

Every error body has the shape described in [Conventions](/marketplace/ad-server-api/conventions#error-model).

## Timeouts and retries

DataFlair does not call this endpoint yet. The platform's configured timeouts for calls to your server are 10 seconds to connect and 30 seconds in total.

## Verify

Run the [conformance check](/marketplace/ad-server-api/conformance) with `--only orders` to check this endpoint against the [fixtures](/marketplace/ad-server-api/fixtures).
