# Advalidation > 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 such as file weight, dimensions, duration, click-through verification, and SSL compliance. Integrate via the TypeScript SDK (recommended) or the REST API. Questions, need an API key, or want to discuss integration? Email us at hello@advalidation.com The full machine-readable API specification is available at [openapi.json](https://advalidation.com/openapi.json). --- # TypeScript SDK 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://rtr.innovid.com/r1.66f3e735e66ba5.38642747;cb=[timestamp]", 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 ``` That's it. 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", // optional, this is the default }); // 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: "", 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" }); ``` > **Note:** 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" }); ``` > **Note:** `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. See Result shape for the full type. | 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 All requests use the base URL `https://app.advalidation.io/v2` and require the `X-API-Key` header. --- # Getting started This guide walks you through the core API operations: verifying your API key, creating a campaign, uploading a creative, polling for results, and retrieving scan data. ## Prerequisites - An API key -- contact your account manager to get one. ## Setup All examples use the base URL `https://app.advalidation.io/v2` and require the `X-API-Key` header. ```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 Check that your key works by fetching your user profile. **curl:** ```bash curl https://app.advalidation.io/v2/users/me \ -H "Accept: application/json" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript:** ```js const response = await fetch(`${BASE_URL}/users/me`, { headers }); const { data } = await response.json(); console.log(data); ``` ## 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. If the defaults work for you, you can skip this step and pass `adspecId: null` when creating a campaign. Otherwise, list the available specifications and note the `id` of the one you need. **curl:** ```bash curl https://app.advalidation.io/v2/ad-specifications \ -H "Accept: application/json" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript:** ```js const response = await fetch(`${BASE_URL}/ad-specifications`, { headers }); const { data } = await response.json(); console.log(data); ``` ## 3. Create a new campaign Campaigns group creatives together. Set `type` to `display` or `video`, and pass an `adspecId` from step 2 or `null` for the default. **curl:** ```bash curl -X POST https://app.advalidation.io/v2/campaigns \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -d '{"name": "My first campaign", "type": "display", "adspecId": null}' ``` **JavaScript:** ```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; console.log("Campaign ID:", campaignId); ``` ## 4. Upload a creative Upload a creative to your campaign. The simplest method sends a URL or HTML tag as a JSON payload. See the file upload guide for other methods including binary and base64. **curl:** ```bash curl -X POST https://app.advalidation.io/v2/campaigns/CAMPAIGN_ID/creatives \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -d '{"payload": ""}' ``` **JavaScript:** ```js const response = await fetch( `${BASE_URL}/campaigns/${campaignId}/creatives`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ payload: '', }), } ); const { data } = await response.json(); const creativeId = data[0].id; console.log("Creative ID:", creativeId); ``` ## 5. Poll until the scan finishes Most scans complete within 30 seconds. Poll the creative endpoint until `processingStatus` is `finished`. We recommend polling every 20 seconds. **curl:** ```bash # Repeat until processingStatus is "finished" curl https://app.advalidation.io/v2/creatives/CREATIVE_ID \ -H "Accept: application/json" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript:** ```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") { console.log("Scan finished. Scan ID:", data.latestScanStatus.id); 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. Use the scan ID from step 5 to fetch results. For VAST creatives, the scan hierarchy can be more complex (media files, variations). See the data model for the full picture. **curl:** ```bash curl https://app.advalidation.io/v2/scans/SCAN_ID \ -H "Accept: application/json" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript:** ```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); ``` The `tests` array contains each check with its `name`, `value`, `result` (`pass`, `fail`, or `warn`), and detailed `attributes`. For display and hosted video creatives, this single scan contains all test results. For VAST creatives, additional scans may exist at deeper levels (media files, variations). See the data model for how to navigate the hierarchy. ## 7. Re-scan a creative If the ad specification changes or remote tag assets are updated (e.g. a VAST or HTML tag), you can re-scan without re-uploading. The request body must be an empty JSON object (`{}`). **curl:** ```bash curl -X POST https://app.advalidation.io/v2/creatives/CREATIVE_ID/rescan \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -d '{}' ``` **JavaScript:** ```js const response = await fetch(`${BASE_URL}/creatives/${creativeId}/rescan`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({}), }); const { data } = await response.json(); console.log("Rescan ID:", data.id); ``` After triggering a rescan, poll for results using the same approach as step 5. The rescan's `sequenceType` will be `manual-rescan`. --- # 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 creatives are always flat: one creative, one scan. Video creatives can have additional layers (media files and variations). The structures below show how they are organized and which endpoints reveal each level. ## Creative structures ### Display creative All display types (hosted images, HTML5, tags) follow the same flat structure. One creative, one scan. ``` GET /campaigns/{campaignId}/creatives Creative <- has latestScanStatus.id GET /scans/{scanId} <- display tests (dimensions, filesize, load time, etc.) ``` Display `sourceType` values include `image`, `html5`, `tag-html5`, `tag-beacon`, among others. The test set varies by type but the structure is always the same. ### Hosted video Same flat structure as display. One file, one scan. ``` GET /campaigns/{campaignId}/creatives Creative <- has latestScanStatus.id GET /scans/{scanId} <- video tests (resolution, duration, codec, etc.) ``` ### VAST without variations The creative holds the VAST XML scan. Its media files each have their own video scan. ``` GET /campaigns/{campaignId}/creatives Creative <- has latestScanStatus.id | | GET /scans/{scanId} <- VAST XML tests (version, validation, skip, etc.) | +-- GET /creatives/{id}/media-files | +-- Media file <- has latestScanStatus.id | GET /scans/{scanId} <- video tests (duration, codec, audio, etc.) | +-- Media file <- has latestScanStatus.id | GET /scans/{scanId} <- 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, so there is no need to call `/media-files`. ``` GET /campaigns/{campaignId}/creatives Creative <- has latestScanStatus.id (nbVariations present) | | GET /scans/{scanId} <- top-level nbIssues only (no tests array) | +-- GET /creatives/{id}/variations | +-- Variation A (creativeId, nbObservations) +-- Variation B +-- ... | +-- GET /creatives/{id}/variations/{variationCreativeId} | | Returns ALL items for this variation: | +-- vast-variation <- has latestScanStatus.id | GET /scans/{id} <- VAST XML tests for this variation | +-- vast-child-video <- has latestScanStatus.id | GET /scans/{id} <- video tests | +-- vast-child-video <- has latestScanStatus.id | GET /scans/{id} <- video tests | +-- ... ``` ## Navigating the hierarchy Starting from `/campaigns/{campaignId}/creatives`: ``` 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.** When using `/creatives/{id}/variations/{variationCreativeId}`, the response contains both the `vast-variation` item and all `vast-child-video` items. No need to call `/media-files` separately. - **Every node has `latestScanStatus.id`.** Whether it's a creative, a variation, or a media file: if it has `latestScanStatus.id`, you can call `/scans/{id}` on it. - **Display creatives are always flat.** No variations, no media files. One scan covers everything. ## Scan types Tests are prefixed `Test_Display_*` or `Test_Video_*` depending on the campaign type. ### Display tests | Context | Example 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 | ### Video tests | Context | Example tests | |---|---| | 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 | --- # Retrieving results for a VAST tag VAST analysis involves XML validation followed by video asset processing. A VAST tag can return different content on subsequent calls (variations), and each response can reference multiple video files. Retrieving the full scan results may require several API calls. This guide covers the steps involved. > **Your case may be simpler:** This guide walks through the most complex scenario. If your VAST tag has a single media file and no variations, you can skip the sections that don't apply. ## Prerequisites You need a campaign with VAST creatives that have already been processed. Make sure you can see results in the Advalidation UI before trying to fetch them via the API. ## Overview The general flow is: 1. **List creatives** in your campaign from `/campaigns/{campaignId}/creatives` 2. **Check for `nbVariations`** on each creative 3. **If `nbVariations` is present**, use the variations route to get per-variation results (which includes media files) 4. **If no `nbVariations`**, fetch media files from `/creatives/{creativeId}/media-files` and get scan results for each 5. **Fetch test results** from `/scans/{scanId}` for every scan ID discovered See the data model for the full hierarchy and decision logic. ## Step 1: Find the campaign Use `/campaigns` to retrieve your campaigns and find the one you need. Note the `id` -- this is your `campaignId`. ```json { "data": [ { "id": 25, "name": "My VAST campaign", "adspecId": 11 } ] } ``` ## Step 2: List campaign creatives Use `/campaigns/{campaignId}/creatives` to list the creatives. The key fields are: | Field | Description | |-------|-------------| | `id` | The creative ID | | `name` | Creative name | | `nbVariations` | Number of VAST variations (only present if the tag returns varying responses) | | `latestScanStatus.id` | The scan ID for retrieving test results | | `latestScanStatus.nbIssues` | Number of failing tests | ```json { "data": [ { "id": 56, "name": "Summer Sponsorship 30s", "latestScanStatus": { "id": 42, "processingStatus": "finished", "nbIssues": 7 } }, { "id": 53, "name": "Pre-roll Demo", "nbVariations": 4, "latestScanStatus": { "id": 61, "processingStatus": "finished", "nbIssues": 16 } } ] } ``` What you do next depends on whether the creative has `nbVariations`: - **Has `nbVariations`** -- the VAST tag returns different content on subsequent calls. The top-level scan (`latestScanStatus.id`) contains `nbIssues` as an aggregate count but no `tests` array. The actual test results live on the variations. Go to Step 3a: Variations. - **No `nbVariations`** -- the creative's `latestScanStatus.id` gives you the VAST XML scan with test results. To get video file results, go to Step 3b: Media files. ## Step 3a: Variations When Advalidation detects that a VAST tag returns varying responses, it records each variation separately. > **How variation detection works:** When the VAST variations check is enabled in the ad specification, Advalidation downloads the VAST XML ten times and compares Ad IDs and Creative IDs across each load. The `nbObservations` field on each variation shows how many times it appeared in those ten loads. **1. List variations** using `/creatives/{creativeId}/variations`: ```json { "data": [ { "creativeId": 111, "label": "Variation A", "nbObservations": 4 }, { "creativeId": 222, "label": "Variation B", "nbObservations": 3 } ] } ``` Note the `creativeId` for each variation. **2. Get variation details** using `/creatives/{mainCreativeId}/variations/{variationCreativeId}`. For example, `/creatives/53/variations/111`: ```json { "data": [ { "id": 112, "parentId": 53, "sourceType": "vast-variation", "sourceTypeLabel": "VAST variation", "latestScanStatus": { "id": 118, "nbIssues": 1 } }, { "id": 113, "parentId": 53, "sourceType": "vast-child-video", "sourceTypeLabel": "VAST video media file", "latestScanStatus": { "id": 119, "nbIssues": 0 } }, { "id": 114, "parentId": 53, "sourceType": "vast-child-video", "sourceTypeLabel": "VAST video media file", "latestScanStatus": { "id": 120, "nbIssues": 0 } } ] } ``` The response includes: - Items with `sourceType: "vast-variation"` -- the VAST XML analysis for this variation - Items with `sourceType: "vast-child-video"` -- each video media file referenced in the tag Use `latestScanStatus.id` from each item to fetch the test results. > When using the variations route, media file results are included in the response. There is no need to call the `/media-files` endpoint separately. ## Step 3b: Media files For VAST creatives without variations, each media file referenced in the tag has its own scan. Fetch them using `/creatives/{creativeId}/media-files`: ```json { "data": [ { "id": 444, "parentId": 56, "sourceType": "vast-child-video", "sourceTypeLabel": "VAST video media file", "latestScanStatus": { "id": 4444, "nbIssues": 0 } }, { "id": 555, "parentId": 56, "sourceType": "vast-child-video", "sourceTypeLabel": "VAST video media file", "latestScanStatus": { "id": 5555, "nbIssues": 0 } } ] } ``` Use `latestScanStatus.id` from each media file to fetch test results. You can also fetch results for the VAST XML itself using `latestScanStatus.id` from the parent creative (step 2). ## Step 4: Fetch test results Use `/scans/{scanId}` to get the test results for any scan ID obtained in the previous steps: ```json { "data": { "nbIssues": 2, "tests": [ { "name": "Test_Video_Duration", "value": "15.04", "valueFormatted": "15 seconds", "valuePhrase": "Video track duration is 15 seconds (15.01). Audio track duration is 15 seconds (15.04).", "result": "fail", "attributes": [ { "name": "Video_PlayTime", "description": "Duration of video in milliseconds", "value": 15.04 } ] }, { "name": "Test_Video_Codec", "value": "H264", "valueFormatted": "H264", "valuePhrase": "The video codec is H264.", "result": "pass", "attributes": [ { "name": "Video_Codec", "description": "Video codec", "value": "H264" } ] } ] } } ``` See the test array reference below for all available tests and their fields. ## Retrieving the ad specification Each campaign has an associated ad specification that defines which tests are run and their expected values. You can retrieve it using the `adspecId` from the campaign object. Use `/ad-specifications/{adspecId}`: ```json { "data": { "id": 1111, "name": "VAST", "isDefault": false, "isPublic": false, "tests": [ { "name": "Test_Video_Resolution", "conditionsString": "320x240", "evaluationExpression": "..." }, { "name": "Test_Video_AudioCodec", "conditionsString": "MP3 (MPEG Audio), AAC, WMA", "evaluationExpression": "..." } ] } } ``` The `conditionsString` field shows the human-readable expected value for each test. The `evaluationExpression` field contains the evaluation logic in JsonLogic format -- this is what Advalidation uses internally to determine pass or fail. Some tests (like `Test_Video_VastCreativeCount`) have no configurable parameters and will not include these fields. --- # Creative files upload guide Creatives are uploaded to a campaign using `POST /campaigns/{campaignId}/creatives`. The API supports four upload methods depending on your content type. > **Campaign type must match:** Uploaded files must match their campaign's type (display or video). Uploading a video file to a display campaign, or vice versa, returns a `422` error with `CREATIVE_UPLOAD_NO_CREATIVES_FOUND`. ## Common headers All upload methods require these headers: | Header | Description | |--------|-------------| | `X-API-Key` | Your API key | | `Accept` | `application/json` | | `Content-Type` | Varies by method (see below) | | `X-Filename` | Optional. Sets the display name in the Advalidation UI. Defaults to a timestamp-based name if omitted. | ## JSON payload **Content-Type:** `application/json` **Best for:** URLs, HTML tags, VAST tags, and other text-based content. Send the content in a `payload` field: **curl:** ```bash curl -X POST https://app.advalidation.io/v2/campaigns/CAMPAIGN_ID/creatives \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -H "X-Filename: Interactive.html" \ -d '{"payload": ""}' ``` **JavaScript:** ```js const response = await fetch( `${BASE_URL}/campaigns/${campaignId}/creatives`, { method: "POST", headers: { ...headers, "Content-Type": "application/json", "X-Filename": "Interactive.html", }, body: JSON.stringify({ payload: '', }), } ); const { data } = await response.json(); const creativeId = data[0].id; ``` ## JSON payload with base64 encoding **Content-Type:** `application/json` **Best for:** Binary files (images, videos, ZIP archives) when you need to use JSON. Encode the file as a base64 string and send it in the `payload` field: **curl:** ```bash BASE64=$(base64 < ./creative.mov) curl -X POST https://app.advalidation.io/v2/campaigns/CAMPAIGN_ID/creatives \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -H "X-Filename: creative.mov" \ -d "{\"payload\": \"$BASE64\"}" ``` **JavaScript:** ```js import { readFile } from "node:fs/promises"; const file = await readFile("./creative.mov"); const base64 = file.toString("base64"); const response = await fetch( `${BASE_URL}/campaigns/${campaignId}/creatives`, { method: "POST", headers: { ...headers, "Content-Type": "application/json", "X-Filename": "creative.mov", }, body: JSON.stringify({ payload: base64 }), } ); ``` > Base64 encoding increases file size by approximately 30%. For large files, use the binary upload method instead. ## Plain text upload **Content-Type:** `text/plain` **Best for:** Raw HTML files or plain text content. Send the file content directly as the request body: **curl:** ```bash curl -X POST https://app.advalidation.io/v2/campaigns/CAMPAIGN_ID/creatives \ -H "Accept: application/json" \ -H "Content-Type: text/plain" \ -H "X-API-Key: your-api-key-here" \ -H "X-Filename: creative.html" \ --data-binary @./creative.html ``` **JavaScript:** ```js import { readFile } from "node:fs/promises"; const content = await readFile("./creative.html", "utf8"); const response = await fetch( `${BASE_URL}/campaigns/${campaignId}/creatives`, { method: "POST", headers: { ...headers, "Content-Type": "text/plain", "X-Filename": "creative.html", }, body: content, } ); ``` ## Binary upload **Content-Type:** `application/octet-stream` **Best for:** ZIP archives, images, videos, and other binary files. Most efficient for large files since there is no encoding overhead. Send the raw file bytes as the request body: **curl:** ```bash curl -X POST https://app.advalidation.io/v2/campaigns/CAMPAIGN_ID/creatives \ -H "Accept: application/json" \ -H "Content-Type: application/octet-stream" \ -H "X-API-Key: your-api-key-here" \ -H "X-Filename: creative.zip" \ --data-binary @./creative.zip ``` **JavaScript:** ```js import { readFile } from "node:fs/promises"; const file = await readFile("./creative.zip"); const response = await fetch( `${BASE_URL}/campaigns/${campaignId}/creatives`, { method: "POST", headers: { ...headers, "Content-Type": "application/octet-stream", "X-Filename": "creative.zip", }, body: file, } ); ``` ## Method 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 limits:** All uploads are subject to a maximum payload size of 16 MB. For base64-encoded uploads, the encoded size is approximately 33% larger than the original file. This limit does not apply when submitting a URL -- the file is downloaded by Advalidation directly. --- # Conventions ## Successful responses Successful requests return `200 OK` or `204 No Content`. Responses that include data always have a `data` property. Single-resource endpoints return the object directly: ```json { "data": { "id": 123, "name": "My campaign" } } ``` Collection endpoints return an array in `data`, and may include a `meta` property with pagination information. ## Empty collections vs. not found - **Collection endpoints** return an empty `data` array when no items match -- not a `404`. - **Single-resource endpoints** return `404 Not Found` when the requested ID does not exist. ## Error responses Error responses return an appropriate HTTP status code (`401`, `404`, `422`, etc.) with an `error` property in the response body explaining the issue. ## Content type The API accepts and returns `application/json` for most requests. Binary uploads use `application/octet-stream` or `text/plain` -- see the file upload guide for details. --- # Rate limiting The API enforces IP-based rate limiting to ensure fair usage across all clients. ## Response headers Every response includes headers indicating your current rate limit status: | Header | Description | |--------|-------------| | `X-RateLimit-IPLimit` | Maximum requests allowed in the current window | | `X-RateLimit-IPRemaining` | Requests remaining in the current window | Authenticated requests (those with a valid `X-API-Key` header) receive a higher allowance than unauthenticated requests. ## Exceeding the limit Requests over the allowed quota return `429 Too Many Requests`. When this happens, back off and wait before retrying. Monitor the `X-RateLimit-IPRemaining` header to pace your requests and avoid hitting the limit. --- # The test array When you retrieve scan results, the response includes a `tests` array. Each entry in that array corresponds to one of the tests documented below, with a `name`, `value`, `result`, and `attributes`. ## Display tests This section lists every display test that Advalidation can run against a creative. Each test checks one aspect of the creative and returns `pass`, `fail`, or `warn`. ### Test_Display_AdDimensions Checks if the creative's pixel dimensions match one of the accepted ad unit sizes defined in the ad specification. | Result | Condition | |--------|-----------| | **pass** | Dimensions match an accepted size | | **fail** | Dimensions do not match any accepted size | Example response: ```json { "name": "Test_Display_AdDimensions", "value": "300x250", "valueFormatted": "300x250", "valuePhrase": "300x250 matches one of the accepted dimensions.", "result": "pass", "attributes": [ { "name": "Display_Width", "description": "Pixel width of creative", "value": 300 }, { "name": "Display_Height", "description": "Pixel height of creative", "value": 250 } ] } ``` **Attributes:** `Display_Width`, `Display_Height` --- ### Test_Display_AdVerificationTags Detects ad verification vendor tags (DoubleVerify, IAS, MOAT, etc.) and checks whether they are set to monitor or block delivery. Blocking tags can cause impression discrepancies between your ad server and the advertiser's reporting. | Result | Condition | |--------|-----------| | **pass** | Only monitoring tags detected, or tags match the ad spec policy | Example response: ```json { "name": "Test_Display_AdVerificationTags", "value": "monitoring", "valueFormatted": "Monitoring", "valuePhrase": "cdn.doubleverify.com/dvtp_src.js detected in third party tag. This allows the ad verification vendor to monitor tag performance. Only monitoring tags allowed.", "result": "pass", "attributes": [ { "name": "Display_AdVerificationType", "description": "Display ad verification type (blocking/monitoring)", "value": "monitoring" }, { "name": "Display_AdVerificationVendor", "description": "Display ad verification vendor", "value": "doubleverify" } ] } ``` **Attributes:** `Display_AdVerificationType`, `Display_AdVerificationVendor` --- ### Test_Display_AnimationLength Checks if the ad's animation stops within the allowed duration. Long-running animations distract visitors and violate most publisher specifications. | Result | Condition | |--------|-----------| | **pass** | Animation stops within the threshold (e.g., 15 seconds) | | **fail** | Animation runs longer than allowed | Example response: ```json { "name": "Test_Display_AnimationLength", "value": 0, "valueFormatted": "0 seconds", "valuePhrase": "The ad animates for 0 seconds. Animation must stop within 15 seconds.", "result": "pass", "attributes": [ { "name": "Display_AnimationDuration", "description": "Animation duration in milliseconds", "value": 0 } ] } ``` **Attributes:** `Display_AnimationDuration` --- ### Test_Display_Audio Checks if the creative plays audio before user interaction. Most publisher specs prohibit auto-playing audio. | Result | Condition | |--------|-----------| | **pass** | No audio detected before hover | | **fail** | Audio plays automatically | Example response: ```json { "name": "Test_Display_Audio", "value": false, "valueFormatted": "Not detected", "valuePhrase": "No audio before hover detected. Audio is not allowed before hover.", "result": "pass", "attributes": [ { "name": "Display_AudioBeforeHover", "description": "Audio before hover", "value": false }, { "name": "Display_AudioAfterHover", "description": "Audio after hover", "value": false } ] } ``` **Attributes:** `Display_AudioBeforeHover`, `Display_AudioAfterHover` --- ### Test_Display_Border Checks if the creative has a distinct border that visually separates it from page content. This is a common publisher requirement to prevent ads from blending into editorial content. | Result | Condition | |--------|-----------| | **pass** | Distinct border detected | | **warn** | No distinct border detected | Example response: ```json { "name": "Test_Display_Border", "value": true, "valueFormatted": "Indistinct border", "valuePhrase": "Distinct border not detected. Border required.", "result": "warn", "attributes": [ { "name": "Display_LowContrastBorder", "description": "Low contrast border", "value": true } ] } ``` **Attributes:** `Display_LowContrastBorder` --- ### Test_Display_ClickRedirects Counts the number of redirects between clicking the ad and reaching the landing page. Excessive redirects cause visitors to drop off before arriving. | Result | Condition | |--------|-----------| | **pass** | Redirect count within limit (e.g., 6 max) | | **fail** | Too many redirects | Example response: ```json { "name": "Test_Display_ClickRedirects", "value": 1, "valueFormatted": "1 redirect", "valuePhrase": "Detected 1 redirect. No more than 6 redirects allowed.", "result": "pass", "attributes": [ { "name": "Display_ClickRedirects", "description": "Redirects on click", "value": 1 } ] } ``` **Attributes:** `Display_ClickRedirects` --- ### Test_Display_CodeRules Validates the creative's source code against custom rules defined in the ad specification. These rules can check for prohibited code patterns, required scripts, or other code-level requirements. | Result | Condition | |--------|-----------| | **pass** | Code passes all custom validation rules | | **fail** | Code violates one or more rules | Example response: ```json { "name": "Test_Display_CodeRules", "value": 1, "valueFormatted": "1 issue", "valuePhrase": "This ad fails custom code validation rules set up by the ad spec.", "result": "fail", "attributes": [] } ``` --- ### Test_Display_Connections Checks the creative's external connections against an approved vendor list defined in the ad specification. Connections to unapproved domains are flagged. | Result | Condition | |--------|-----------| | **pass** | All connections match approved vendors | | **fail** | One or more connections violate the rules | Example response: ```json { "name": "Test_Display_Connections", "value": 4, "valueFormatted": "4 violations", "valuePhrase": "This ad violates the ad specification rules for external connections.", "result": "fail", "attributes": [] } ``` --- ### Test_Display_Cookies Counts the number of cookies set by the creative and checks against the allowed limit. | Result | Condition | |--------|-----------| | **pass** | Cookie count within limit | | **fail** | Too many cookies | Example response: ```json { "name": "Test_Display_Cookies", "value": 0, "valueFormatted": 0, "valuePhrase": "Detected no cookies. The maximum number of cookies allowed is 0.", "result": "pass", "attributes": [ { "name": "Display_TotalCookies", "description": "Number of cookies", "value": 0 } ] } ``` **Attributes:** `Display_TotalCookies` --- ### Test_Display_Cpu Measures the creative's CPU usage as average and peak percentages. High CPU usage drains battery life and degrades the browsing experience, especially on mobile devices. | Result | Condition | |--------|-----------| | **pass** | Both average and peak within limits | | **fail** | Average or peak exceeds the threshold | Example response: ```json { "name": "Test_Display_Cpu", "value": 1, "valueFormatted": "1% average, 4% peak", "valuePhrase": "CPU usage within allowed limits. CPU usage during testing was: 1% average, 4% peak. The maximum CPU usage allowed is 10% average, 20% peak.", "result": "pass", "attributes": [ { "name": "Display_CPUAverage", "description": "CPU average", "value": 1 }, { "name": "Display_CPUPeak", "description": "CPU peak", "value": 4 } ] } ``` **Attributes:** `Display_CPUAverage`, `Display_CPUPeak` --- ### Test_Display_ExternalRequests Counts the total number of HTTP requests the creative makes and checks against the allowed limit. Fewer requests means faster load times and better viewability. | Result | Condition | |--------|-----------| | **pass** | Request count within limit | | **fail** | Too many requests | Example response: ```json { "name": "Test_Display_ExternalRequests", "value": 5, "valueFormatted": 5, "valuePhrase": "This ad is making 5 requests. The maximum allowed is 15 requests.", "result": "pass", "attributes": [ { "name": "Display_FileRequestsTotal", "description": "Total requests (post exclusions)", "value": 4 }, { "name": "Display_FileRequestsInitial", "description": "Initial requests (post exclusions)", "value": 4 } ] } ``` **Attributes:** `Display_FileRequestsTotal`, `Display_FileRequestsInitial` --- ### Test_Display_Filesize Checks the total hosted file size of the creative's assets against the allowed limit. Oversized creatives hurt viewability and user experience. | Result | Condition | |--------|-----------| | **pass** | File size within limit | | **fail** | File size exceeds limit | Example response: ```json { "name": "Test_Display_Filesize", "value": "141977", "valueFormatted": "139 kb", "valuePhrase": "The hosted filesize of this ad is 139 kb. The maximum hosted filesize allowed is 100 kb.", "result": "fail", "attributes": [ { "name": "Display_HTML5InflatedSize", "description": "Hosted filesize (sum)", "value": 141977 } ] } ``` **Attributes:** `Display_HTML5InflatedSize` --- ### Test_Display_FlashLso Checks if the creative uses Flash local shared objects (Flash cookies). These persist after clearing browser cookies and are generally prohibited. | Result | Condition | |--------|-----------| | **pass** | No Flash cookies detected | | **fail** | Flash cookies detected | Example response: ```json { "name": "Test_Display_FlashLso", "value": false, "valueFormatted": "Not detected", "valuePhrase": "Detected no flash cookies.", "result": "pass", "attributes": [] } ``` --- ### Test_Display_GdprConsent Checks if the creative's source code contains the GDPR consent macro, which is required for GDPR-compliant ad delivery in applicable regions. | Result | Condition | |--------|-----------| | **pass** | GDPR consent macro detected | | **fail** | Macro not found | Example response: ```json { "name": "Test_Display_GdprConsent", "value": "Detected", "valueFormatted": "Detected", "valuePhrase": "This creative's code contains the GDPR consent macro.", "result": "pass", "attributes": [] } ``` --- ### Test_Display_HeavyAdInterventions Checks if the creative would trigger Chrome's heavy ad intervention, which removes ads that consume excessive CPU or network resources. Chrome considers an ad "heavy" if it uses more than 4 MB of network, more than 15 seconds of CPU in any 30-second window, or more than 60 seconds of CPU total. | Result | Condition | |--------|-----------| | **pass** | Resource usage within Chrome's limits | | **fail** | One or more thresholds exceeded | Example response: ```json { "name": "Test_Display_HeavyAdInterventions", "value": false, "valueFormatted": "Not detected", "valuePhrase": "Chrome heavy ad interventions not detected. Ads triggering heavy ad intervention are not accepted.", "result": "pass", "attributes": [] } ``` --- ### Test_Display_LandingPage Verifies that the ad's landing page URL resolves to a working page. Catches broken URLs, malformed click macros, and redirect failures before launch. | Result | Condition | |--------|-----------| | **pass** | Landing page returns HTTP 200 | | **fail** | Landing page returns an error or does not resolve | Example response: ```json { "name": "Test_Display_LandingPage", "value": true, "valueFormatted": "Working", "valuePhrase": "The landing page works. Response code must be 200.", "result": "pass", "attributes": [ { "name": "Display_LandingPageResponseCode", "description": "Landing page HTTP code", "value": 200 } ] } ``` **Attributes:** `Display_LandingPageResponseCode` --- ### Test_Display_LocalStorage Checks if the creative uses browser local storage. Local storage persists data that cannot be cleared through standard cookie controls, making it a privacy concern. | Result | Condition | |--------|-----------| | **pass** | No local storage usage detected | | **fail** | Local storage objects detected | Example response: ```json { "name": "Test_Display_LocalStorage", "value": false, "valueFormatted": "Not detected", "valuePhrase": "Detected no local storage objects. Local storage objects are not allowed.", "result": "pass", "attributes": [] } ``` --- ### Test_Display_MouseClick Verifies that click tracking works via the click macro. This ensures the publisher can count clicks and attribute conversions. | Result | Condition | |--------|-----------| | **pass** | Click tracking via macro is working | | **fail** | Click tracking is not functioning | Example response: ```json { "name": "Test_Display_MouseClick", "value": true, "valueFormatted": "Trackable", "valuePhrase": "Click tracking works as expected. Click tracking via macro required.", "result": "pass", "attributes": [ { "name": "Display_ClickTracking", "description": "Click is trackable", "value": true } ] } ``` **Attributes:** `Display_ClickTracking` --- ### Test_Display_Ssl Checks that all network requests made by the creative use HTTPS. Non-HTTPS requests can trigger mixed content warnings and be blocked by browsers. | Result | Condition | |--------|-----------| | **pass** | All requests use HTTPS | | **fail** | One or more non-HTTPS requests detected | Example response: ```json { "name": "Test_Display_Ssl", "value": 0, "valueFormatted": "No issues", "valuePhrase": "This ad is not making any non HTTPS requests. Only HTTPS allowed.", "result": "pass", "attributes": [ { "name": "Display_NonHTTPSRequests", "description": "Non-HTTPS assets", "value": 0 } ] } ``` **Attributes:** `Display_NonHTTPSRequests` --- ### Test_Display_TagLoadInitial Measures the total file size of assets loaded during the initial load phase (before the browser's `load` event). Keeping initial load lightweight ensures the ad doesn't compete with page content for bandwidth. | Result | Condition | |--------|-----------| | **pass** | Initial load size within limit | | **fail** | Initial load size exceeds limit | Example response: ```json { "name": "Test_Display_TagLoadInitial", "value": "35091", "valueFormatted": "34 kb", "valuePhrase": "This ad is loading 34 kb within the initial load phase. The maximum allowed is 150 kb.", "result": "pass", "attributes": [ { "name": "Display_RemoteLoadInitial", "description": "Initial load bytes (post exclusions)", "value": 35091 } ] } ``` **Attributes:** `Display_RemoteLoadInitial` --- ### Test_Display_TagLoadSubload Measures the total file size of assets loaded during the subload phase (after the browser's `load` event). Subload has a higher budget than initial load since the page has already rendered. | Result | Condition | |--------|-----------| | **pass** | Subload size within limit | | **fail** | Subload size exceeds limit | Example response: ```json { "name": "Test_Display_TagLoadSubload", "value": "906", "valueFormatted": "1 kb", "valuePhrase": "This ad is loading 1 kb within the subload phase. The maximum allowed is 300 kb.", "result": "pass", "attributes": [ { "name": "Display_RemoteLoadSubload", "description": "Subload bytes (post exclusions)", "value": 906 } ] } ``` **Attributes:** `Display_RemoteLoadSubload` --- ### Test_Display_VisualStart Measures how quickly the creative renders visible content. Slow-loading ads get scrolled past before they are seen, directly impacting viewability metrics and campaign performance. | Result | Condition | |--------|-----------| | **pass** | Renders within the threshold (e.g., 0.8 seconds) | | **fail** | Takes longer than the threshold | Example response: ```json { "name": "Test_Display_VisualStart", "value": "664", "valueFormatted": "0.66 seconds", "valuePhrase": "The median visual start time was 0.66 seconds. Ads must render something visible within 0.8 seconds.", "result": "pass", "attributes": [ { "name": "Display_VisualStartTime", "description": "Time to visual start in milliseconds", "value": 664 } ] } ``` **Attributes:** `Display_VisualStartTime` --- ## Video tests This section lists every video test that Advalidation can run against a creative. Each test checks one aspect of the video or VAST tag and returns `pass` or `fail`. ### Mutually exclusive tests Some video tests come in pairs -- the ad specification determines which variant is active. You will only see one from each pair in a scan response. | 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 | ### Video file tests These tests check the properties of video media files. They appear on hosted video scans and VAST child media file scans. #### Test_Video_AspectRatio Checks if the video's aspect ratio matches one of the accepted ratios defined in the ad specification. | Result | Condition | |--------|-----------| | **pass** | Aspect ratio matches an accepted value | | **fail** | Aspect ratio does not match any accepted value | Example response: ```json { "name": "Test_Video_AspectRatio", "value": "16:9", "valueFormatted": "16:9 (640x360)", "valuePhrase": "The aspect ratio is 16:9. The resolution is 640x360. Supported aspect ratio is: 4:3.", "result": "fail", "attributes": [ { "name": "Video_AspectRatioString", "description": "Aspect ratio", "value": "16:9" } ] } ``` > Mutually exclusive with `Test_Video_Resolution`. The ad specification determines whether dimensions are checked as aspect ratio or exact resolution. **Attributes:** `Video_AspectRatioString` --- #### Test_Video_AudioAverage Checks if the average audio loudness is within the allowed range. Uses the R128/LKFS loudness standard. | Result | Condition | |--------|-----------| | **pass** | Average loudness within the specified range | | **fail** | Average loudness outside the allowed range | Example response: ```json { "name": "Test_Video_AudioAverage", "value": -23, "valueFormatted": "-23.00 LUFS", "valuePhrase": "Average audio volume is within allowed limits: -23.00 LUFS. Audio average must be within -23 +/- 1 LUFS.", "result": "pass", "attributes": [ { "name": "Audio_VolumeAverage", "description": "Audio R128 volume average", "value": -23 } ] } ``` **Attributes:** `Audio_VolumeAverage` --- #### Test_Video_AudioBitrate Checks if the audio track bitrate is within the allowed range. | Result | Condition | |--------|-----------| | **pass** | Audio bitrate within the specified range | | **fail** | Audio bitrate outside the allowed range | Example response: ```json { "name": "Test_Video_AudioBitrate", "value": 128, "valueFormatted": "128 kbps", "valuePhrase": "The bitrate of the audio track is 128 kbps. The audio bitrate must be between 192 kbps and 192 kbps.", "result": "fail", "attributes": [ { "name": "Audio_BitRate", "description": "Audio track bit rate", "value": 128 } ] } ``` > Mutually exclusive with `Test_Video_TotalBitrate`. The ad specification determines whether bitrate is checked as separate audio/video tracks or as a total container bitrate. **Attributes:** `Audio_BitRate` --- #### Test_Video_AudioChannels Checks the number of audio channels in the video file. | Result | Condition | |--------|-----------| | **pass** | Channel count matches an accepted value | | **fail** | Channel count does not match any accepted value | Example response: ```json { "name": "Test_Video_AudioChannels", "value": 2, "valueFormatted": "2 channels", "valuePhrase": "The number of audio channels are 2. Number of audio channels supported are: 0.", "result": "fail", "attributes": [ { "name": "Audio_Channels", "description": "Audio channels", "value": true } ] } ``` > Audio channel requirements are configured in the Advalidation web interface, not through the API. **Attributes:** `Audio_Channels` --- #### Test_Video_AudioCodec Checks if the audio track codec matches one of the accepted codecs. | Result | Condition | |--------|-----------| | **pass** | Audio codec matches an accepted value | | **fail** | Audio codec does not match any accepted value | Example response: ```json { "name": "Test_Video_AudioCodec", "value": "AAC", "valueFormatted": "AAC", "valuePhrase": "The audio codec is AAC.", "result": "pass", "attributes": [ { "name": "Audio_Codec", "description": "Audio track codec", "value": "AAC" } ] } ``` **Attributes:** `Audio_Codec` --- #### Test_Video_AudioPeak Checks if the true peak audio level is within the allowed maximum. | Result | Condition | |--------|-----------| | **pass** | Peak level within the allowed maximum | | **fail** | Peak level exceeds the allowed maximum | Example response: ```json { "name": "Test_Video_AudioPeak", "value": -11.8, "valueFormatted": "-11.80 dBTP", "valuePhrase": "The peak audio volume is -11.80 dBTP. The allowed true-peak maximum is -1 dBTP.", "result": "pass", "attributes": [ { "name": "Audio_VolumePeak", "description": "Audio volume peak", "value": -11.8 } ] } ``` **Attributes:** `Audio_VolumePeak` --- #### Test_Video_AudioSampleRate Checks if the audio sample rate matches one of the accepted values. | Result | Condition | |--------|-----------| | **pass** | Sample rate matches an accepted value | | **fail** | Sample rate does not match any accepted value | Example response: ```json { "name": "Test_Video_AudioSampleRate", "value": 48, "valueFormatted": "48 kHz", "valuePhrase": "The audio sample rate is 48 kHz.", "result": "pass", "attributes": [ { "name": "Audio_SamplingRate", "description": "Audio sampling rate", "value": 48 } ] } ``` **Attributes:** `Audio_SamplingRate` --- #### Test_Video_Boxing Detects letterboxing (black bars top/bottom) or pillarboxing (black bars left/right) in the video. Boxing indicates the video content doesn't fill the declared resolution. | Result | Condition | |--------|-----------| | **pass** | No boxing detected | | **fail** | Letterboxing or pillarboxing detected | Example response: ```json { "name": "Test_Video_Boxing", "value": 0, "valueFormatted": "None detected", "valuePhrase": "None detected. Letterboxing not allowed.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_ChromaSubsampling Checks if the chroma subsampling format matches one of the accepted values. | Result | Condition | |--------|-----------| | **pass** | Chroma subsampling matches an accepted value | | **fail** | Chroma subsampling does not match any accepted value | Example response: ```json { "name": "Test_Video_ChromaSubsampling", "value": "4:2:0", "valueFormatted": "4:2:0", "valuePhrase": "Chroma subsampling is 4:2:0. Supported options are: 4:4:4, 4:2:2, 4:2:0 and 4:1:1.", "result": "pass", "attributes": [ { "name": "Video_ChromaSubsampling", "description": "Video chroma subsampling", "value": "4:2:0" } ] } ``` **Attributes:** `Video_ChromaSubsampling` --- #### Test_Video_Codec Checks if the video codec matches one of the accepted codecs. | Result | Condition | |--------|-----------| | **pass** | Video codec matches an accepted value | | **fail** | Video codec does not match any accepted value | Example response: ```json { "name": "Test_Video_Codec", "value": "H264", "valueFormatted": "H264", "valuePhrase": "The video codec is H264.", "result": "pass", "attributes": [ { "name": "Video_Codec", "description": "Video codec", "value": "H264" } ] } ``` **Attributes:** `Video_Codec` --- #### Test_Video_ContainerFormat Checks if the video container format matches one of the accepted formats. | Result | Condition | |--------|-----------| | **pass** | Container format matches an accepted value | | **fail** | Container format does not match any accepted value | Example response: ```json { "name": "Test_Video_ContainerFormat", "value": "MP4", "valueFormatted": "MP4", "valuePhrase": "The container format is MP4.", "result": "pass", "attributes": [ { "name": "Video_ContainerFormat", "description": "Video container format", "value": "MP4" } ] } ``` **Attributes:** `Video_ContainerFormat` --- #### Test_Video_DuplicateFrames Detects repeated duplicate frames in the video, which can indicate encoding issues or artificially inflated duration. | Result | Condition | |--------|-----------| | **pass** | No repeated duplicate frames detected | | **fail** | Repeated duplicate frames detected | Example response: ```json { "name": "Test_Video_DuplicateFrames", "value": 0, "valueFormatted": "Not detected", "valuePhrase": "Repeated duplicate frames not detected. Repeated duplicated frames not allowed.", "result": "pass", "attributes": [] } ``` > Duplicate frame detection is configured in the Advalidation web interface, not through the API. --- #### Test_Video_Duration Checks if the video duration is within the allowed range. Reports both video track and audio track durations. | Result | Condition | |--------|-----------| | **pass** | Duration within the allowed range | | **fail** | Duration outside the allowed range | Example response: ```json { "name": "Test_Video_Duration", "value": "30.03", "valueFormatted": "30 seconds", "valuePhrase": "Video track duration is 30 seconds (30.03). Audio track duration is 30 seconds (30.03).", "result": "pass", "attributes": [ { "name": "Video_PlayTime", "description": "Duration of video in milliseconds", "value": 30.03 } ] } ``` **Attributes:** `Video_PlayTime` --- #### Test_Video_Filesize Checks if the video file size is within the allowed range. | Result | Condition | |--------|-----------| | **pass** | File size within the allowed range | | **fail** | File size outside the allowed range | Example response: ```json { "name": "Test_Video_Filesize", "value": 1258496, "valueFormatted": "1229 kb", "valuePhrase": "The size of this video file is 1229 kb. The filesize must be above 500 kB.", "result": "pass", "attributes": [ { "name": "File_Length", "description": "Video file size", "value": 1258496 } ] } ``` **Attributes:** `File_Length` --- #### Test_Video_Fps Checks if the video frame rate matches one of the accepted values. | Result | Condition | |--------|-----------| | **pass** | Frame rate matches an accepted value | | **fail** | Frame rate does not match any accepted value | Example response: ```json { "name": "Test_Video_Fps", "value": 23.976, "valueFormatted": "23.976 FPS", "valuePhrase": "The framerate is 23.976 FPS.", "result": "pass", "attributes": [ { "name": "Video_FrameRate", "description": "Frame rate", "value": 23.976 } ] } ``` **Attributes:** `Video_FrameRate` --- #### Test_Video_Interlace Detects interlaced frames in the video by analyzing the interlace frame ratio. Interlaced content causes visual artifacts on progressive displays. | Result | Condition | |--------|-----------| | **pass** | Interlace ratio within allowed limits | | **fail** | Interlace ratio exceeds the threshold | Example response: ```json { "name": "Test_Video_Interlace", "value": 29, "valueFormatted": "29%", "valuePhrase": "The interlace frame ratio is 29%. Video must not be interlaced.", "result": "fail", "attributes": [] } ``` > Interlace detection is configured in the Advalidation web interface, not through the API. --- #### Test_Video_Resolution Checks if the video resolution matches one of the accepted resolutions defined in the ad specification. | Result | Condition | |--------|-----------| | **pass** | Resolution matches an accepted value | | **fail** | Resolution does not match any accepted value | Example response: ```json { "name": "Test_Video_Resolution", "value": "640x360", "valueFormatted": "640x360 (16:9)", "valuePhrase": "The resolution is 640x360. The aspect ratio is 16:9. Supported resolutions are: 640x480, 800x600 and 1920x1080.", "result": "fail", "attributes": [ { "name": "Video_Height", "description": "Video pixel height", "value": 360 }, { "name": "Video_Width", "description": "Video pixel width", "value": 640 } ] } ``` > Mutually exclusive with `Test_Video_AspectRatio`. The ad specification determines whether dimensions are checked as exact resolution or aspect ratio. **Attributes:** `Video_Height`, `Video_Width` --- #### Test_Video_ScanType Checks if the video scan type (progressive or interlaced) matches one of the accepted values. | Result | Condition | |--------|-----------| | **pass** | Scan type matches an accepted value | | **fail** | Scan type does not match any accepted value | Example response: ```json { "name": "Test_Video_ScanType", "value": "progressive", "valueFormatted": "progressive", "valuePhrase": "Scan type of the video is progressive. Supported scan types are: interlaced and progressive.", "result": "pass", "attributes": [ { "name": "Video_ScanType", "description": "Scan type (progressive/interlaced)", "value": "progressive" } ] } ``` **Attributes:** `Video_ScanType` --- #### Test_Video_Timecode Checks if the video contains timecode information and whether it matches accepted formats. | Result | Condition | |--------|-----------| | **pass** | Timecode matches an accepted format, or any format is accepted | | **fail** | Timecode does not match any accepted format | Example response: ```json { "name": "Test_Video_Timecode", "value": null, "valueFormatted": "Missing", "valuePhrase": "This video contains no timecode information. Any timecode combination is supported..", "result": "pass", "attributes": [] } ``` --- #### Test_Video_TotalBitrate Checks if the total container bitrate (audio + video combined) is within the allowed range. | Result | Condition | |--------|-----------| | **pass** | Total bitrate within the allowed range | | **fail** | Total bitrate outside the allowed range | Example response: ```json { "name": "Test_Video_TotalBitrate", "value": 913, "valueFormatted": "913 kbps", "valuePhrase": "The total bitrate is 913 kbps. The total bitrate must be between 100 kbps and 2000 kbps.", "result": "pass", "attributes": [ { "name": "General_BitRate", "description": "Total (container) bit rate", "value": 913 } ] } ``` > Mutually exclusive with `Test_Video_AudioBitrate` and `Test_Video_VideoBitrate`. The ad specification determines whether bitrate is checked as a total container value or as separate audio/video tracks. **Attributes:** `General_BitRate` --- #### Test_Video_VastPropertyDiscrepancy Compares properties declared in the VAST XML (such as codec, dimensions, and bitrate) against the actual properties of the downloaded video file. Discrepancies between declared and actual values are flagged. | Result | Condition | |--------|-----------| | **pass** | No discrepancies between declared and actual properties | | **fail** | One or more discrepancies detected | Example response: ```json { "name": "Test_Video_VastPropertyDiscrepancy", "value": 0, "valueFormatted": "0 discrepancies", "valuePhrase": "Detected 0 discrepancies between the XML declaration and the properties of the actual video file. Must align.", "result": "pass", "attributes": [] } ``` > This test only appears on VAST child media file scans, not on hosted video scans or VAST parent scans. It requires both VAST XML declarations and actual file properties to compare. --- #### Test_Video_VideoBitrate Checks if the video track bitrate is within the allowed range. | Result | Condition | |--------|-----------| | **pass** | Video bitrate within the allowed range | | **fail** | Video bitrate outside the allowed range | Example response: ```json { "name": "Test_Video_VideoBitrate", "value": 800, "valueFormatted": "800 kbps", "valuePhrase": "The bitrate of the video track is 800 kbps. The video bitrate must be between 1000 kbps and 1500 kbps.", "result": "fail", "attributes": [ { "name": "Video_BitRate", "description": "Video track bit rate", "value": 800 } ] } ``` > Mutually exclusive with `Test_Video_TotalBitrate`. The ad specification determines whether bitrate is checked as separate audio/video tracks or as a total container bitrate. **Attributes:** `Video_BitRate` --- ### VAST tag tests These tests validate the 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 checks whether they comply with the ad specification policy. | Result | Condition | |--------|-----------| | **pass** | Tags comply with the policy, or no tags detected when none are allowed | | **fail** | Tags violate the ad specification policy | Example response: ```json { "name": "Test_Video_AdVerificationTags", "value": null, "valueFormatted": "None", "valuePhrase": " Only monitoring tags allowed.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_VastApiProperties Checks for VPAID or other API frameworks in the VAST response and validates against the ad specification's framework policy. | Result | Condition | |--------|-----------| | **pass** | Detected frameworks match the policy, or none detected when none are required | | **fail** | Framework requirements not met | Example response: ```json { "name": "Test_Video_VastApiProperties", "value": 0, "valueFormatted": "VPAID not detected", "valuePhrase": "Detected no frameworks. Supported framework is: VPAID javascript.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_VastConnectionRules Validates the VAST tag's external connections against an approved vendor list defined in the ad specification. All URLs referenced in the VAST XML are checked. | Result | Condition | |--------|-----------| | **pass** | All connections match approved vendors | | **fail** | One or more connections violate the rules | Example response: ```json { "name": "Test_Video_VastConnectionRules", "value": 6, "valueFormatted": "6 violations", "valuePhrase": "This ad violates the ad specification rules for external connections.", "result": "fail", "attributes": [] } ``` --- #### Test_Video_VastCreativeCount Counts the number of `` elements in the VAST XML. Multi-creative VAST responses can cause unexpected ad behavior. | Result | Condition | |--------|-----------| | **pass** | Creative count within the allowed limit | | **fail** | Too many creatives in the VAST response | Example response: ```json { "name": "Test_Video_VastCreativeCount", "value": 1, "valueFormatted": 1, "valuePhrase": "The VAST XML contains 1 creative. Multi creative VAST not allowed.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_VastDurationDiscrepancy Checks whether the duration declared in the VAST XML matches the actual duration of the media files. Discrepancies can cause player issues. | Result | Condition | |--------|-----------| | **pass** | No duration discrepancies detected | | **fail** | Duration discrepancies found between XML and media files | Example response: ```json { "name": "Test_Video_VastDurationDiscrepancy", "value": 1, "valueFormatted": "None", "valuePhrase": "No duration discrepancies detected. Must not vary.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_VastNonHttps Checks if the VAST XML contains any non-HTTPS URLs. Mixed content can be blocked by browsers and cause tracking failures. | Result | Condition | |--------|-----------| | **pass** | All URLs use HTTPS | | **fail** | One or more non-HTTPS URLs detected | Example response: ```json { "name": "Test_Video_VastNonHttps", "value": 0, "valueFormatted": "No issues", "valuePhrase": "This ad is making requests to 0 non HTTPS URLs. Only HTTPS allowed.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_VastRequiredMediaFiles Checks if the VAST XML contains all required media file definitions as specified in the ad specification. Media file definitions match on codec, dimensions, and bitrate. | Result | Condition | |--------|-----------| | **pass** | All required media file definitions matched | | **fail** | One or more required definitions not matched | Example response: ```json { "name": "Test_Video_VastRequiredMediaFiles", "value": 1, "valueFormatted": "1 definition not matched", "valuePhrase": "1 media files definition not matched, all of the definitions required", "result": "fail", "attributes": [] } ``` --- #### Test_Video_VastSkipDetection Checks if the VAST tag includes skip functionality and whether it matches the ad specification's skip policy. | Result | Condition | |--------|-----------| | **pass** | Skip behavior matches the policy | | **fail** | Skip behavior does not match the policy | Example response: ```json { "name": "Test_Video_VastSkipDetection", "value": null, "valueFormatted": "Not skippable", "valuePhrase": "The VAST skippable property is: not skippable. Ad must not be skippable.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_VastValidateXml Validates the VAST XML against the VAST schema. Catches syntax errors, missing required elements, and structural issues. | Result | Condition | |--------|-----------| | **pass** | VAST XML is valid | | **fail** | VAST XML contains validation errors | Example response: ```json { "name": "Test_Video_VastValidateXml", "value": 1, "valueFormatted": "Valid", "valuePhrase": "The XML of this VAST file is valid. The VAST XML syntax must be valid.", "result": "pass", "attributes": [] } ``` --- #### Test_Video_VastVariations Detects whether a VAST URL returns different content on subsequent loads. Variations indicate the tag serves rotating or dynamic content. | Result | Condition | |--------|-----------| | **pass** | Variation count within the allowed limit | | **fail** | Variation count exceeds the allowed limit | Example response: ```json { "name": "Test_Video_VastVariations", "value": 1, "valueFormatted": 1, "valuePhrase": "The VAST URL returns 1 variation. Varying VAST responses not allowed.", "result": "pass", "attributes": [] } ``` > When multiple variations are detected, the creative's `sourceType` becomes `video-url-varying-vast` and the `nbVariations` field appears on the creative object. This field is only present on varying VAST creatives -- it is absent from all other source types. --- #### Test_Video_VastVersion Checks if the VAST version matches one of the accepted versions. | Result | Condition | |--------|-----------| | **pass** | VAST version matches an accepted value | | **fail** | VAST version does not match any accepted value | Example response: ```json { "name": "Test_Video_VastVersion", "value": "2.0", "valueFormatted": "VAST 2.0", "valuePhrase": "The VAST version is: 2.0.", "result": "fail", "attributes": [ { "name": "Video_VASTVersion", "description": "VAST version", "value": "2.0" } ] } ``` **Attributes:** `Video_VASTVersion`