---
url: https://docs.dataflair.ai/marketplace/ad-server-api/conformance.md
description: >-
  Replay the DataFlair fixtures against your ad server and see which rule a
  failing answer breaks. It runs on your machine and needs no DataFlair account.
---

# Conformance check

The conformance check replays the [fixtures](/marketplace/ad-server-api/fixtures) against your ad server. It prints one line for each case. A failing line says what is wrong and links to the section that explains it.

It runs on your own machine, against localhost or a staging server. It does not need a DataFlair account. It checks each answer against the schemas in the [OpenAPI file](/marketplace/ad-server-api/api-reference), the same file the API reference is built from, so the check and the reference cannot disagree.

::: warning The checker is not published to npm yet
`npx` cannot fetch it yet. It is in the DataFlair docs repository, in `docs-site/tools/adserver-check`. Run it from the `docs-site` folder, after `npm ci`. The fixtures work without it: send each `request.json` with `curl` and compare the answer with `expected-response.json`.
:::

## Run it

```bash
node tools/adserver-check/bin/adserver-check.mjs http://localhost:3000/api/v1 --key sk_test_local
```

Use the base URL you give DataFlair, and a test key. A command line stays in your shell history, so you can set `ADSERVER_CHECK_KEY` instead of `--key`.

| Option | Meaning |
| --- | --- |
| `--key <key>` | The key to send as a Bearer token. Or set `ADSERVER_CHECK_KEY`. |
| `--only <endpoint>` | Run one endpoint: `health`, `inventory`, `forecast`, `orders` or `reports`. |
| `--slot <inventory_id>` | Book against this slot, instead of the first slot that `GET /inventory` lists. |
| `--docs <url>` | Where the failure links point. The default is `https://docs.dataflair.ai`. |
| `--fixtures <folder>` | Replay a folder of fixtures instead of the bundled ones. |

The exit code is 0 when your server is ready to connect, 1 when a case failed, and 2 when the command was used wrongly.

## A passing run

Every case passes against a server that follows the contract:

```text
✓ GET  /health         200  Returns the account and its capabilities
✓ GET  /health         401  A wrong key is a 401 with an error body
✓ GET  /inventory      200  Lists the bookable slots
✓ POST /forecast       200  Returns available and forecasted impressions
✓ POST /forecast       404  An unknown inventory_id is a 404 with an error body
✓ POST /orders         201  Creates a draft order
✓ POST /orders         409  The same idempotency_key with a different body is a 409
✓ POST /orders         201  A booking with no creative still returns draft line items that do not serve
✓ POST /orders         200  A repeat of the same idempotency_key and body returns the same ids
✓ POST /orders         404  An unknown inventory_id is a 404 with an error body
✓ POST /orders         200  A new idempotency_key with the same external_ref updates the existing order
✓ POST /reports        200  Returns impressions and clicks per line
✓ POST /reports        200  granularity daily returns a date on every row

13 passed · 0 failed. Ready to connect.
```

## A failing run

Here a server returns its orders as `ACTIVE`. The check was run with `--only orders`. Each failing line names the value and links to the [draft-only rule](/marketplace/ad-server-api/operations/orders#draft-only):

```text
✗ POST /orders         201  Creates a draft order
    Order "ord_55021" returned as "ACTIVE". It must come back as DRAFT and must not serve.
    Line item "li_88012" returned as "ACTIVE". Every line item must come back as DRAFT and must not serve.
    → See https://docs.dataflair.ai/marketplace/ad-server-api/operations/orders#draft-only
✓ POST /orders         409  The same idempotency_key with a different body is a 409
✗ POST /orders         201  A booking with no creative still returns draft line items that do not serve
    Order "ord_55022" returned as "ACTIVE". It must come back as DRAFT and must not serve.
    Line item "li_88013" returned as "ACTIVE". Every line item must come back as DRAFT and must not serve.
    → See https://docs.dataflair.ai/marketplace/ad-server-api/operations/orders#draft-only
✗ POST /orders         200  A repeat of the same idempotency_key and body returns the same ids
    Order "ord_55021" returned as "ACTIVE". It must come back as DRAFT and must not serve.
    Line item "li_88012" returned as "ACTIVE". Every line item must come back as DRAFT and must not serve.
    → See https://docs.dataflair.ai/marketplace/ad-server-api/operations/orders#draft-only
✓ POST /orders         404  An unknown inventory_id is a 404 with an error body
✗ POST /orders         200  A new idempotency_key with the same external_ref updates the existing order
    Order "ord_55021" returned as "ACTIVE". It must come back as DRAFT and must not serve.
    Line item "li_88012" returned as "ACTIVE". Every line item must come back as DRAFT and must not serve.
    → See https://docs.dataflair.ai/marketplace/ad-server-api/operations/orders#draft-only

2 passed · 4 failed. Not ready to connect.
```

## What it sends and changes

The requests are the fixtures. The check changes three things and nothing else:

1. **The campaign token.** The examples use the token `VDWZQ4IT` in every key, order name and `external_ref`. Each run swaps it for a fresh one, so you can run the check again without replaying the last run's keys.
2. **The slot.** The examples book `slot_728x90_home`. The check reads `GET /inventory` and books the first slot that is not archived, and it prefers a slot that takes `728x90`. Pass `--slot` to choose one. The unknown-slot cases keep `slot_x`.
3. **The line item ids in the reports request.** The check sends the ids your server returned from `POST /orders`, not the example ids.

The wrong-key case sends a generated key. Nothing else in a request is changed.

The booking cases create draft orders on your server, named `Summer Launch (DF-CMP-...)` and similar. Delete them when you are done.

With `--only`, the check first reads what the endpoint needs, without printing it. `--only forecast`, `--only orders` and `--only reports` read `GET /inventory` for a slot. `--only forecast` also reads `GET /health`. `--only reports` then books one draft order, because reports asks about a line item.

## What it checks

Every case checks that your server answered, that it did not redirect, that the status is the expected one, and that the body fits the schema. DataFlair does not follow redirects, so a redirect fails the case. If your server does not answer the first request at all, the check stops there and marks the rest as not run.

If `GET /health` says `forecast` is `false`, the forecast cases are skipped. DataFlair does not call `POST /forecast` then. A skipped case is not a pass. A run where every case was skipped is not ready to connect.

These rules apply on top of the schema:

| Rule | What it checks | Explained in |
| --- | --- | --- |
| `health.requiredCapabilities` | `reporting` and `draft_booking` are not `false`. | [Fields](/marketplace/ad-server-api/operations/health#fields) |
| `forecast.echoesSlot` | The answer names the slot that was asked about. | [What you return](/marketplace/ad-server-api/operations/forecast#what-you-return) |
| `forecast.ordering` | `available_impressions` is not higher than `forecasted_impressions`. | [What you return](/marketplace/ad-server-api/operations/forecast#what-you-return) |
| `orders.draftOnly` | The order and every line item come back as `DRAFT`. | [Draft only](/marketplace/ad-server-api/operations/orders#draft-only) |
| `orders.echoesExternalRefs` | Every `external_ref` that was sent comes back, and no other. | [What you return](/marketplace/ad-server-api/operations/orders#what-you-return) |
| `orders.sameIdsAsCreate` | A repeat or an update returns the same `order_id` and `line_item_id` values as the first call. | [Idempotency and recovery](/marketplace/ad-server-api/operations/orders#idempotency-and-recovery) |
| `reports.rowsForRequestedLines` | Every row is for a line that was asked about, with one row per line, or one per line per day. | [What you return](/marketplace/ad-server-api/operations/reports/#what-you-return) |
| `reports.dailyHasDate` | With `granularity` set to `daily`, every row has a `date`. | [What you return](/marketplace/ad-server-api/operations/reports/#what-you-return) |

## What it does not check

* It reads only the first page of `GET /inventory`.
* It waits up to 30 seconds for each answer. It does not measure speed.
* It reads the state your server says it returned. It cannot tell whether a draft really stays out of serving. Look at the drafts in your console.
* It does not test how DataFlair behaves. It tests your server.

If a case fails, follow its link. [Errors](/marketplace/ad-server-api/errors) and [Troubleshooting](/marketplace/ad-server-api/troubleshooting) cover the rest.
