---
name: advalidation-integration
description: "Guide developers through integrating with the Advalidation ad creative validation API. Use this skill whenever someone wants to validate ad creatives (display banners, video files, VAST tags), integrate with Advalidation, use the advalidation npm package, call the Advalidation REST API, interpret scan results or test arrays, handle VAST media files or variations, or build any workflow that checks ad creatives for compliance. Also use when you see imports from 'advalidation' or references to advalidation.io in the codebase."
---

# Advalidation Integration

Advalidation is an automated ad creative validation platform. Upload ad creatives (display banners, video files, VAST tags) and each creative is evaluated through automated tests that return structured results -- file weight, dimensions, duration, click-through verification, SSL compliance, and more.

**Base URL:** The API host is `app.advalidation.io` for most accounts. Some accounts use `app2.advalidation.io` instead. The SDK accepts `baseUrl` as a constructor option or via the `ADVALIDATION_BASE_URL` environment variable. For REST API calls, replace the host in the base URL accordingly. All examples below use `app.advalidation.io`.

## How to guide developers

Always recommend the **TypeScript SDK** first. It handles campaign creation, upload, polling, and result retrieval in a single call. Only fall back to the REST API if the developer has a specific reason (non-Node environment, need for fine-grained control, etc.).

### Decision tree

1. **"I want to validate a creative"** -- start with the [SDK quick start](#sdk-quick-start)
2. **"I need to work with the REST API directly"** -- see the [REST API reference](#rest-api-reference)
3. **"What does this test result mean?"** -- look up the test name in the [test array reference](#test-array-reference)
4. **"I'm working with VAST tags"** -- the [VAST hierarchy](#vast-complexity) section covers variations and media files. The SDK handles this automatically with `details: true`.

### SDK quick start

`npm install advalidation` -- then see the [full quick start](#quick-start) below. One call to validate a creative, zero dependencies, Node.js >= 18.

### Key concepts

- **Creative inputs**: exactly one of `url`, `tag`, or `file`
- **Targeting**: exactly one of `type` ("display" | "video"), `spec` (adspec ID), or `campaign` (existing campaign ID)
- **Summary vs details**: by default you get pass/fail + issue count. Pass `details: true` for full test breakdown including VAST media files and variations
- **Serverless**: use `submit()` + `getResults()` to split across separate requests when `validate()` would timeout
- **Errors**: all extend `AdvalidationError` -- `AuthenticationError`, `InputError`, `ApiError`, `RateLimitError`, `ScanFailedError`, `ScanCancelledError`, `TimeoutError`, `AbortError`

### REST API base

All REST requests use `https://app.advalidation.io/v2` with header `X-API-Key: your-api-key`.

The core workflow: create campaign -> upload creative -> poll until finished -> fetch scan results.

### VAST complexity

VAST creatives can have nested structures. The SDK handles this automatically when `details: true` is set. For the REST API, the hierarchy depends on whether the VAST tag returns variations:

- **No variations**: creative scan (VAST XML tests) + media files (video tests each)
- **With variations** (detected by `nbVariations` on creative): top-level scan has aggregate issues only, variations endpoint returns per-variation XML + video scans

### OpenAPI spec

The full machine-readable API specification is at `https://advalidation.com/openapi.json`.

## Contact

Questions, need an API key, or want to discuss integration: hello@advalidation.com

---

# TypeScript SDK Reference

The fastest way to integrate Advalidation. One call to validate a creative, no manual polling, no response parsing. Zero runtime dependencies.

Source code: [github.com/advalidation/sdk](https://github.com/advalidation/sdk)

## Install

```
npm install advalidation
```

## Quick start

```ts
import { Advalidation } from "advalidation";

const client = new Advalidation({ apiKey: "your-api-key" });

const result = await client.validate({
  url: "https://example.com/vast.xml",
  type: "video",
});

console.log(result.passed);    // true or false
console.log(result.issues);    // number of failed tests
console.log(result.reportUrl); // link to the full visual report
```

The SDK resolves the ad specification, creates a campaign, uploads, polls until done, and returns the result.

## Authentication

```ts
// Pass directly
const client = new Advalidation({
  apiKey: "your-api-key",
  baseUrl: "https://app.advalidation.io",
});

// Or use environment variables (ADVALIDATION_API_KEY, ADVALIDATION_BASE_URL)
const client = new Advalidation();
```

Constructor options take precedence over environment variables. `baseUrl` defaults to `https://app.advalidation.io`.

## Creative input types

Exactly one of `url`, `tag`, or `file` must be provided.

**URL** -- hosted creative, VAST XML endpoint, or ad tag URL. The file is fetched server-side by Advalidation, so there is no upload size limit.

```ts
await client.validate({ url: "https://example.com/ad.html", type: "display" });
```

**Tag** -- raw HTML/JavaScript ad tag or VAST XML string.

```ts
await client.validate({ tag: "<script src='https://example.com/ad.js'></script>", type: "display" });
```

**File** -- local file path. Supports ZIP archives, images, video files, and HTML files. Uploads are limited to **16 MB** -- use `url` instead for larger files.

```ts
await client.validate({ file: "/path/to/video.mp4", type: "video" });
```

> The creative must match the campaign type. Uploading a video file against a `type: "display"` spec (or vice versa) will fail with an `ApiError`.

## Target options

Every validation needs to know where to put the creative and which ad specification to use. Provide exactly one of `campaign`, `spec`, or `type`.

**Existing campaign** -- upload into an existing campaign (adspec is inherited):

```ts
await client.validate({ url: "https://example.com/ad.html", campaign: 12345 });
```

**By spec ID** -- use a specific ad specification (creates a new campaign):

```ts
await client.validate({ url: "https://example.com/ad.html", spec: "123" });
```

**By type** -- use the default ad specification for a type (creates a new campaign):

```ts
await client.validate({ url: "https://example.com/ad.html", type: "display" });
await client.validate({ url: "https://example.com/ad.html", type: "video" });
```

> `type` resolves to whichever ad specification is marked as default for that type in the Advalidation UI. If the default is changed in the UI, subsequent SDK runs will use the new one. Use `spec` with an explicit ID if you need a pinned ad specification.

## Summary vs detailed results

By default, `validate()` and `getResults()` return a **summary** with pass/fail, issue count, and report URL -- no extra API calls beyond what's needed for scanning.

```ts
const result = await client.validate({ url: "https://example.com/ad.html", type: "display" });
console.log(result.passed);    // true
console.log(result.issues);    // 0
console.log(result.reportUrl); // "https://app.advalidation.com/share/..."
console.log(result.tests);     // [] (empty in summary mode)
```

Pass `details: true` to fetch the full test breakdown, including individual test results, VAST media files, and variations. This requires additional API calls (20+ for complex VAST creatives).

```ts
const result = await client.validate({
  url: "https://example.com/vast.xml",
  type: "video",
  details: true,
});
console.log(result.tests);      // full test results
console.log(result.mediaFiles); // VAST media files with their tests
```

You can also start with a summary and fetch details later using `getResults()`:

```ts
// Fast CI gate -- summary only
const summary = await client.validate({ url: "https://example.com/ad.html", type: "display" });

if (!summary.passed) {
  // Fetch full details for the failure report
  const detailed = await client.getResults(summary.creativeId, { details: true });
  if (detailed.status === "finished") {
    console.log(detailed.tests);
  }
}
```

## Fetch existing results

Already have a creative ID from a previous run or the Advalidation UI? Skip the upload and poll:

```ts
const response = await client.getResults(creativeId);

if (response.status === "finished") {
  console.log(response.passed, response.issues, response.reportUrl);
} else {
  console.log(response.status); // "pending", "failed", or "cancelled"
}

// Full test breakdown (only available when finished)
const detailed = await client.getResults(creativeId, { details: true });
if (detailed.status === "finished") {
  console.log(detailed.tests);
}
```

`getResults()` returns a discriminated union -- check `status` before accessing result fields.

| Option    | Type      | Default | Description                                      |
|-----------|-----------|---------|--------------------------------------------------|
| `verbose` | `boolean` | `false` | Log progress to console.                         |
| `details` | `boolean` | `false` | Fetch full test breakdown including VAST variations and media files. Only applies when status is `"finished"`. |

## Serverless / split workflow

`validate()` bundles upload + polling in a single long-running call. In serverless environments (Vercel, AWS Lambda, Cloudflare Workers) the function may timeout before the scan completes. Use `submit()` + `getResults()` to split the workflow across separate requests.

```ts
// Request 1: submit the creative (fast -- no polling)
const { creativeId } = await client.submit({
  url: "https://example.com/vast.xml",
  type: "video",
});

// Store creativeId (database, KV, cookie, query param, etc.)

// Request 2+: poll from separate short-lived requests
const response = await client.getResults(creativeId);
if (response.status === "finished") {
  console.log(response.passed, response.issues);
} else if (response.status === "pending") {
  // Not done yet -- try again in a few seconds
}
```

`submit()` accepts the same creative and targeting options as `validate()`, minus `timeout` and `details` (irrelevant without polling).

## Options reference

All options are passed in the same object as the creative input. `submit()` accepts the same options as `validate()` except `timeout` and `details`.

| Field     | Type                       | Default   | `validate` | `submit` | Description                                      |
|-----------|----------------------------|-----------|:----------:|:--------:|--------------------------------------------------|
| `url`     | `string`                   | -         | x | x | URL of the hosted creative. Mutually exclusive with `file`, `tag`, and `data`. |
| `file`    | `string`                   | -         | x | x | Local file path. Mutually exclusive with `url`, `tag`, and `data`. |
| `tag`     | `string`                   | -         | x | x | Raw ad tag markup. Mutually exclusive with `url`, `file`, and `data`. |
| `data`    | `Buffer \| Uint8Array`     | -         | x | x | Raw file bytes. Mutually exclusive with `url`, `file`, and `tag`. |
| `fileName`| `string`                   | -         | x | x | Filename sent with `data` uploads. Only used with `data`. |
| `campaign`| `number`                   | -         | x | x | Existing campaign ID. Adspec is inherited. Mutually exclusive with `spec` and `type`. |
| `spec`    | `string`                   | -         | x | x | Ad specification ID. Creates a new campaign. Mutually exclusive with `campaign` and `type`. |
| `type`    | `"display" \| "video"`     | -         | x | x | Use the default ad specification for this type. Creates a new campaign. Mutually exclusive with `campaign` and `spec`. |
| `name`    | `string`                   | auto      | x | x | Campaign name. Auto-generated from the input if omitted. |
| `timeout` | `number`                   | `300000`  | x | - | Polling timeout in milliseconds (default 5 minutes). |
| `signal`  | `AbortSignal`              | -         | x | x | Standard `AbortSignal` for cancellation.         |
| `verbose` | `boolean`                  | `false`   | x | x | Log progress messages to console.                |
| `details` | `boolean`                  | `false`   | x | - | Fetch full test breakdown including VAST variations and media files. |

## Result shape

`validate()` returns `ValidationResult` directly (polling guarantees a finished scan).

`getResults()` returns `GetResultsResponse` -- a discriminated union:

```ts
type GetResultsResponse =
  | { status: "pending"; creativeId: number }
  | { status: "failed"; creativeId: number }
  | { status: "cancelled"; creativeId: number }
  | (ValidationResult & { status: "finished" });
```

`submit()` returns `SubmitResult`:

```ts
interface SubmitResult {
  campaignId: number;
  creativeId: number;
}
```

`ValidationResult` (returned by `validate()` and embedded in `GetResultsResponse` when finished):

```ts
interface ValidationResult {
  campaignId: number;
  creativeId: number;
  scanId: number;
  passed: boolean;        // true if zero issues
  reportUrl: string;      // link to the full visual report
  issues: number;         // count of failed tests
  tests: Test[];          // test results for this creative
  mediaFiles: MediaFile[]; // VAST child video files (non-variation VAST only)
  variations: Variation[]; // VAST variations (multi-variation VAST only)
}

interface Test {
  name: string;                              // test identifier
  value: string | number | boolean | null;   // measured value
  valueFormatted: string | null;             // human-readable value
  result: "pass" | "fail" | "warn";          // outcome
  spec: string | null;                       // threshold from the adspec
}
```

For non-VAST creatives, `tests` has the results and both `mediaFiles` and `variations` are empty. For VAST creatives the result is nested:

```ts
// VAST without variations -- media files at the top level
result.mediaFiles[0].tests          // tests for the first video rendition
result.mediaFiles[0].issues         // failed test count for that rendition

// VAST with variations -- variations contain media files
result.variations[0].label          // "Variation A"
result.variations[0].mediaFiles[0].tests  // tests for that rendition
```

## Error handling

All errors extend `AdvalidationError`.

| Error class          | When thrown                                                  |
|----------------------|--------------------------------------------------------------|
| `AuthenticationError`| API returns 401 (invalid or missing API key).                |
| `InputError`         | Invalid parameters (missing input, both `spec` and `type` provided, etc). |
| `ApiError`           | API returns a non-401 error. Has `status` and `body` properties. |
| `RateLimitError`     | API returned 429 repeatedly (after 3 retries with backoff).  |
| `ScanFailedError`    | The scan finished with a `failed` status.                    |
| `ScanCancelledError` | The scan was cancelled.                                      |
| `TimeoutError`       | Scan did not complete within the timeout period.             |
| `AbortError`         | The operation was aborted via the provided `AbortSignal`.    |

```ts
import {
  Advalidation,
  AuthenticationError,
  InputError,
  ApiError,
  TimeoutError,
} from "advalidation";

try {
  const result = await client.validate({
    url: "https://example.com/ad.html",
    type: "display",
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error("Bad API key");
  } else if (error instanceof InputError) {
    console.error("Invalid input:", error.message);
  } else if (error instanceof ApiError) {
    console.error(`API error ${error.status}:`, error.body);
  } else if (error instanceof TimeoutError) {
    console.error("Scan timed out");
  }
}
```

## Requirements

- Node.js >= 18 (uses native `fetch`)
- Zero runtime dependencies

---

# REST API Reference

All requests require the `X-API-Key` header and use the base URL `https://app.advalidation.io/v2`.

The full machine-readable API specification is at [openapi.json](https://advalidation.com/openapi.json).

## Getting started

### Setup

```js
const BASE_URL = "https://app.advalidation.io/v2";
const headers = {
  Accept: "application/json",
  "X-API-Key": "your-api-key-here",
};
```

### 1. Verify your API key

```bash
curl https://app.advalidation.io/v2/users/me \
  -H "Accept: application/json" \
  -H "X-API-Key: your-api-key-here"
```

```js
const response = await fetch(`${BASE_URL}/users/me`, { headers });
const { data } = await response.json();
```

### 2. List available ad specifications

Ad specifications define what tests run against your creatives. Each account has a default specification for display and one for video. Pass `adspecId: null` when creating a campaign to use the default.

```js
const response = await fetch(`${BASE_URL}/ad-specifications`, { headers });
const { data } = await response.json();
```

### 3. Create a new campaign

Campaigns group creatives together. Set `type` to `display` or `video`.

```js
const response = await fetch(`${BASE_URL}/campaigns`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    name: "My first campaign",
    type: "display",
    adspecId: null,
  }),
});

const { data } = await response.json();
const campaignId = data.id;
```

### 4. Upload a creative

The simplest method sends a URL or HTML tag as a JSON payload.

```js
const response = await fetch(
  `${BASE_URL}/campaigns/${campaignId}/creatives`,
  {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({
      payload: '<iframe src="https://example.com/ad.html" style="border:0; width:300px; height:250px;" scrolling="no"></iframe>',
    }),
  }
);

const { data } = await response.json();
const creativeId = data[0].id;
```

### 5. Poll until the scan finishes

Most scans complete within 30 seconds. Poll every 20 seconds until `processingStatus` is `finished`.

```js
async function waitForScan(creativeId) {
  while (true) {
    const response = await fetch(`${BASE_URL}/creatives/${creativeId}`, { headers });
    const { data } = await response.json();
    const status = data.latestScanStatus?.processingStatus;

    if (status === "finished") {
      return data.latestScanStatus.id;
    }

    if (status !== "queued" && status !== "processing") {
      throw new Error(`Unexpected status: ${status}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 20000));
  }
}

const scanId = await waitForScan(creativeId);
```

### 6. Retrieve scan results

Test results live on the scan object, not the creative.

```js
const response = await fetch(`${BASE_URL}/scans/${scanId}`, { headers });
const { data } = await response.json();

console.log("Issues found:", data.nbIssues);
console.log("Tests:", data.tests);
```

### 7. Re-scan a creative

If the ad specification changes or remote tag assets are updated, re-scan without re-uploading. The request body must be an empty JSON object (`{}`).

```js
const response = await fetch(`${BASE_URL}/creatives/${creativeId}/rescan`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({}),
});
```

---

## Data model

Campaigns contain creatives. Every creative has a `latestScanStatus.id` (a scan ID). Call `/scans/{scanId}` on any scan ID to get its test results.

### Display creative

All display types (hosted images, HTML5, tags) follow the same flat structure. One creative, one scan.

```
Creative  ->  /scans/{scanId}  ->  display tests
```

### Hosted video

Same flat structure as display. One file, one scan.

```
Creative  ->  /scans/{scanId}  ->  video tests
```

### VAST without variations

The creative holds the VAST XML scan. Its media files each have their own video scan.

```
Creative
|-- /scans/{scanId}                  -> VAST XML tests
|-- /creatives/{id}/media-files
    |-- Media file  -> /scans/{id}   -> video tests
    |-- Media file  -> /scans/{id}   -> video tests
```

### VAST with variations

Detected by `nbVariations` being present on the creative. The variations endpoint returns both the variation XML scan AND its child video scans.

```
Creative (nbVariations present)
|-- /scans/{scanId}                  -> top-level nbIssues only (no tests array)
|-- /creatives/{id}/variations
    |-- Variation A  (creativeId, nbObservations)
    |-- Variation B
        |-- /creatives/{id}/variations/{varId}
            |-- vast-variation    -> /scans/{id}  -> VAST XML tests
            |-- vast-child-video  -> /scans/{id}  -> video tests
            |-- vast-child-video  -> /scans/{id}  -> video tests
```

### Navigating the hierarchy

```
For each creative:

  Has nbVariations?
    YES -> VAST with variations
      1. /scans/{latestScanStatus.id}            -> top-level nbIssues (no tests array)
      2. /creatives/{id}/variations               -> list variations
      3. /creatives/{id}/variations/{varId}        -> variation + media files
      4. /scans/{id} for each item                -> XML or video tests

    NO -> fetch /creatives/{id}/media-files
      Has results -> VAST without variations
        1. /scans/{latestScanStatus.id}           -> VAST XML tests
        2. /scans/{id} for each media file        -> video tests

      Empty -> flat creative (display or hosted video)
        1. /scans/{latestScanStatus.id}           -> all tests in one response
```

### Key rules

- `/scans/{scanId}` is the only endpoint that returns test results. Everything else is about discovering scan IDs.
- `nbVariations` on the creative determines the path. Only VAST creatives can have variations.
- The variations detail endpoint includes media files. No need to call `/media-files` separately.
- Every node has `latestScanStatus.id`. Whether it's a creative, variation, or media file -- if it has `latestScanStatus.id`, call `/scans/{id}` on it.
- Display creatives are always flat. No variations, no media files.

---

## VAST case study

### Step 1: Find the campaign

```
GET /campaigns -> find the campaign ID
```

### Step 2: List campaign creatives

```
GET /campaigns/{campaignId}/creatives
```

Key fields: `id`, `name`, `nbVariations`, `latestScanStatus.id`, `latestScanStatus.nbIssues`.

- **Has `nbVariations`**: top-level scan has aggregate `nbIssues` but no `tests` array. Actual results live on variations.
- **No `nbVariations`**: `latestScanStatus.id` gives VAST XML scan with test results. Fetch `/media-files` for video results.

### Step 3a: Variations

When Advalidation detects varying VAST responses, it records each variation. It downloads the VAST XML ten times and compares Ad IDs and Creative IDs. `nbObservations` shows how many times each appeared.

1. List variations: `GET /creatives/{creativeId}/variations`
2. Get variation details: `GET /creatives/{mainCreativeId}/variations/{variationCreativeId}`

The response includes items with `sourceType: "vast-variation"` (VAST XML analysis) and `sourceType: "vast-child-video"` (video media files). Use `latestScanStatus.id` from each to fetch test results.

### Step 3b: Media files (no variations)

```
GET /creatives/{creativeId}/media-files
```

Each media file has `latestScanStatus.id`. Fetch test results for each, plus the parent creative's VAST XML scan.

### Step 4: Fetch test results

```
GET /scans/{scanId}
```

Returns `nbIssues` and `tests` array with `name`, `value`, `result`, and `attributes`.

### Retrieving the ad specification

```
GET /ad-specifications/{adspecId}
```

Returns the test configuration including `conditionsString` (human-readable expected value) and `evaluationExpression` (JsonLogic evaluation logic).

---

## File upload methods

All uploads go to `POST /campaigns/{campaignId}/creatives`.

Common headers: `X-API-Key`, `Accept: application/json`, `Content-Type` (varies), `X-Filename` (optional display name).

### JSON payload

**Content-Type:** `application/json`
**Best for:** URLs, HTML tags, VAST tags.

```js
body: JSON.stringify({ payload: '<iframe src="https://example.com/ad.html"></iframe>' })
```

### JSON with base64

**Content-Type:** `application/json`
**Best for:** Binary files when JSON is required. ~30% size overhead.

```js
const base64 = file.toString("base64");
body: JSON.stringify({ payload: base64 })
```

### Plain text

**Content-Type:** `text/plain`
**Best for:** Raw HTML files.

```js
body: htmlContent
```

### Binary

**Content-Type:** `application/octet-stream`
**Best for:** ZIP, images, videos. Most efficient for large files.

```js
body: fileBuffer
```

### Comparison

| Method | Content-Type | Encoding overhead | Best for |
|--------|-------------|-------------------|----------|
| JSON payload | `application/json` | None (text) | URLs, HTML tags, VAST tags |
| JSON base64 | `application/json` | ~30% larger | Binary files when JSON is required |
| Plain text | `text/plain` | None | Raw HTML files |
| Binary | `application/octet-stream` | None | ZIP, images, videos (most efficient) |

Upload limit: 16 MB (does not apply to URL submissions -- the file is downloaded by Advalidation directly).

---

## Conventions

- Successful responses return `200 OK` or `204 No Content`
- Data is wrapped in a `data` property. Single resources return an object, collections return an array
- Collection endpoints return empty `data` array (not 404) when no items match
- Single-resource endpoints return `404` when the ID doesn't exist
- Error responses include an `error` property explaining the issue

---

## Rate limiting

IP-based rate limiting. Every response includes:

| Header | Description |
|--------|-------------|
| `X-RateLimit-IPLimit` | Maximum requests allowed in the current window |
| `X-RateLimit-IPRemaining` | Requests remaining in the current window |

Authenticated requests get a higher allowance. Exceeding the limit returns `429 Too Many Requests`. Back off and monitor `X-RateLimit-IPRemaining` to pace requests.

---

# Test Array Reference

When you retrieve scan results, the response includes a `tests` array. Each entry has `name`, `value`, `valueFormatted`, `valuePhrase`, `result` ("pass", "fail", or "warn"), and `attributes`.

## Display tests

### Test_Display_AdDimensions
Checks if pixel dimensions match accepted ad unit sizes.
**Attributes:** `Display_Width`, `Display_Height`

### Test_Display_AdVerificationTags
Detects ad verification vendor tags (DoubleVerify, IAS, MOAT) and checks monitor vs. block mode.
**Attributes:** `Display_AdVerificationType`, `Display_AdVerificationVendor`

### Test_Display_AnimationLength
Checks if animation stops within the allowed duration (e.g., 15 seconds).
**Attributes:** `Display_AnimationDuration`

### Test_Display_Audio
Checks if audio plays before user interaction. Most specs prohibit auto-playing audio.
**Attributes:** `Display_AudioBeforeHover`, `Display_AudioAfterHover`

### Test_Display_Border
Checks for a distinct border separating the ad from page content. Returns `warn` (not `fail`) when missing.
**Attributes:** `Display_LowContrastBorder`

### Test_Display_ClickRedirects
Counts redirects between click and landing page. Excessive redirects cause drop-off.
**Attributes:** `Display_ClickRedirects`

### Test_Display_CodeRules
Validates source code against custom rules in the ad specification (prohibited patterns, required scripts).

### Test_Display_Connections
Checks external connections against an approved vendor list.

### Test_Display_Cookies
Counts cookies set by the creative against the allowed limit.
**Attributes:** `Display_TotalCookies`

### Test_Display_Cpu
Measures CPU usage as average and peak percentages. High CPU drains battery on mobile.
**Attributes:** `Display_CPUAverage`, `Display_CPUPeak`

### Test_Display_ExternalRequests
Counts total HTTP requests against the allowed limit. Fewer requests = faster load.
**Attributes:** `Display_FileRequestsTotal`, `Display_FileRequestsInitial`

### Test_Display_Filesize
Checks total hosted file size of assets against the allowed limit.
**Attributes:** `Display_HTML5InflatedSize`

### Test_Display_FlashLso
Checks for Flash local shared objects (Flash cookies). Generally prohibited.

### Test_Display_GdprConsent
Checks if source code contains the GDPR consent macro.

### Test_Display_HeavyAdInterventions
Checks if the creative would trigger Chrome's heavy ad intervention (>4 MB network, >15s CPU in 30s window, or >60s CPU total).

### Test_Display_LandingPage
Verifies the landing page URL resolves to HTTP 200.
**Attributes:** `Display_LandingPageResponseCode`

### Test_Display_LocalStorage
Checks for browser local storage usage. Privacy concern since it persists after clearing cookies.

### Test_Display_MouseClick
Verifies click tracking works via the click macro.
**Attributes:** `Display_ClickTracking`

### Test_Display_Ssl
Checks that all network requests use HTTPS. Non-HTTPS triggers mixed content warnings.
**Attributes:** `Display_NonHTTPSRequests`

### Test_Display_TagLoadInitial
Measures total file size of assets loaded before the browser's `load` event.
**Attributes:** `Display_RemoteLoadInitial`

### Test_Display_TagLoadSubload
Measures total file size of assets loaded after the browser's `load` event.
**Attributes:** `Display_RemoteLoadSubload`

### Test_Display_VisualStart
Measures time to first visible content. Slow-loading ads get scrolled past.
**Attributes:** `Display_VisualStartTime`

---

## Video file tests

These tests check properties of video media files. They appear on hosted video scans and VAST child media file scans.

### Test_Video_AspectRatio
Checks if aspect ratio matches accepted values. Mutually exclusive with `Test_Video_Resolution`.
**Attributes:** `Video_AspectRatioString`

### Test_Video_AudioAverage
Checks average audio loudness (R128/LKFS standard) against allowed range.
**Attributes:** `Audio_VolumeAverage`

### Test_Video_AudioBitrate
Checks audio track bitrate against allowed range. Mutually exclusive with `Test_Video_TotalBitrate`.
**Attributes:** `Audio_BitRate`

### Test_Video_AudioChannels
Checks number of audio channels against accepted values.
**Attributes:** `Audio_Channels`

### Test_Video_AudioCodec
Checks if audio codec matches accepted values (e.g., AAC, MP3).
**Attributes:** `Audio_Codec`

### Test_Video_AudioPeak
Checks true peak audio level against allowed maximum (dBTP).
**Attributes:** `Audio_VolumePeak`

### Test_Video_AudioSampleRate
Checks if audio sample rate matches accepted values.
**Attributes:** `Audio_SamplingRate`

### Test_Video_Boxing
Detects letterboxing (black bars top/bottom) or pillarboxing (black bars left/right).

### Test_Video_ChromaSubsampling
Checks if chroma subsampling format matches accepted values (e.g., 4:2:0, 4:2:2).
**Attributes:** `Video_ChromaSubsampling`

### Test_Video_Codec
Checks if video codec matches accepted values (e.g., H264, H265).
**Attributes:** `Video_Codec`

### Test_Video_ContainerFormat
Checks if container format matches accepted values (e.g., MP4, MOV).
**Attributes:** `Video_ContainerFormat`

### Test_Video_DuplicateFrames
Detects repeated duplicate frames indicating encoding issues or inflated duration.

### Test_Video_Duration
Checks if video duration is within allowed range. Reports both video and audio track durations.
**Attributes:** `Video_PlayTime`

### Test_Video_Filesize
Checks if video file size is within allowed range.
**Attributes:** `File_Length`

### Test_Video_Fps
Checks if frame rate matches accepted values.
**Attributes:** `Video_FrameRate`

### Test_Video_Interlace
Detects interlaced frames by analyzing interlace frame ratio.

### Test_Video_Resolution
Checks if resolution matches accepted values. Mutually exclusive with `Test_Video_AspectRatio`.
**Attributes:** `Video_Height`, `Video_Width`

### Test_Video_ScanType
Checks if scan type (progressive/interlaced) matches accepted values.
**Attributes:** `Video_ScanType`

### Test_Video_Timecode
Checks if timecode information matches accepted formats.

### Test_Video_TotalBitrate
Checks total container bitrate (audio + video) against allowed range. Mutually exclusive with `Test_Video_AudioBitrate` + `Test_Video_VideoBitrate`.
**Attributes:** `General_BitRate`

### Test_Video_VastPropertyDiscrepancy
Compares VAST XML declared properties against actual video file properties. Only appears on VAST child media file scans.

### Test_Video_VideoBitrate
Checks video track bitrate against allowed range. Mutually exclusive with `Test_Video_TotalBitrate`.
**Attributes:** `Video_BitRate`

---

## VAST tag tests

These validate VAST XML structure and tag behavior. They appear on VAST parent scans and variation scans.

### Test_Video_AdVerificationTags
Detects ad verification vendor tags in the VAST response and validates against policy.

### Test_Video_VastApiProperties
Checks for VPAID or other API frameworks against the ad spec's framework policy.

### Test_Video_VastConnectionRules
Validates all URLs in the VAST XML against an approved vendor list.

### Test_Video_VastCreativeCount
Counts `<Creative>` elements in the VAST XML. Multi-creative responses can cause unexpected behavior.

### Test_Video_VastDurationDiscrepancy
Checks whether declared VAST XML duration matches actual media file durations.

### Test_Video_VastNonHttps
Checks for non-HTTPS URLs in the VAST XML.

### Test_Video_VastRequiredMediaFiles
Checks if VAST XML contains all required media file definitions (codec, dimensions, bitrate).

### Test_Video_VastSkipDetection
Checks skip functionality against the ad spec's skip policy.

### Test_Video_VastValidateXml
Validates VAST XML against the VAST schema for syntax and structure.

### Test_Video_VastVariations
Detects varying content on subsequent VAST loads. When variations are detected, `nbVariations` appears on the creative object.

### Test_Video_VastVersion
Checks if VAST version matches accepted values.
**Attributes:** `Video_VASTVersion`

---

## Mutually exclusive tests

Some tests come in pairs -- the ad specification determines which is active:

| Either | Or | Setting |
|--------|----|---------|
| `Test_Video_Resolution` | `Test_Video_AspectRatio` | Dimensions: exact resolution vs. aspect ratio |
| `Test_Video_TotalBitrate` | `Test_Video_AudioBitrate` + `Test_Video_VideoBitrate` | Bitrate: total container vs. separate audio/video tracks |

---

## Tests by context

| Context | Tests |
|---------|-------|
| **Hosted image** | AdDimensions, AnimationLength, Border, Filesize |
| **Hosted HTML5** | AdDimensions, Audio, VisualStart, AnimationLength, Border, MouseClick, Filesize, TagLoadInitial, TagLoadSubload, Cpu, ExternalRequests, Connections, Cookies, Ssl, LocalStorage, CodeRules, FlashLso, AdVerificationTags, HeavyAdInterventions |
| **HTML5 tag** | AdDimensions, Audio, VisualStart, AnimationLength, MouseClick, LandingPage, ClickRedirects, TagLoadInitial, TagLoadSubload, Cpu, ExternalRequests, Connections, Cookies, Ssl, LocalStorage, GdprConsent, CodeRules, FlashLso, AdVerificationTags, HeavyAdInterventions |
| **Tracking tag** | AdDimensions, Audio, TagLoadInitial, TagLoadSubload, Cpu, ExternalRequests, Connections, Cookies, Ssl, LocalStorage, GdprConsent, FlashLso, AdVerificationTags, HeavyAdInterventions |
| **Hosted video** | Resolution, Duration, TotalBitrate, FPS, Filesize, ContainerFormat, Codec, ScanType, AudioCodec, AudioAverage, AudioPeak, AudioSampleRate, ChromaSubsampling, Boxing |
| **VAST creative** | VastDurationDiscrepancy, VastRequiredMediaFiles, VastVersion, VastValidateXml, VastApiProperties, VastSkipDetection, VastCreativeCount, VastVariations, VastConnectionRules, VastNonHttps, AdVerificationTags |
| **VAST variation** | Same as VAST creative (per-variation) |
| **VAST child video** | Duration, FPS, Filesize, Codec, ScanType, AudioCodec, AudioAverage, AudioPeak, AudioSampleRate, VastPropertyDiscrepancy, ChromaSubsampling, Boxing |
