VAST tag validation in Python with vastlint
Short answer: run pip install vastlint and call vastlint.validate(xml). It validates VAST XML in-process and returns a structured result you can hand straight to an API response or a data pipeline.
The package wraps the same Rust core used by the CLI, Go binding, and web validator through a stable FFI C API. No subprocess to manage, no network hop, a good fit for ad-ops tooling, a FastAPI or Flask service, a Django backend, or an Airflow job that validates creatives and returns structured results.
Why use the Python package
- In-process validation, with no subprocess or external service
- Same core rule coverage as the CLI, web app, and other bindings
- Structured result that serialises to JSON for any API response
- Per-call
rule_overridesand wrapper-depth options - Zero Python dependencies, prebuilt wheels, no Rust toolchain
Install
pip install vastlintWheels ship a platform-matched libvastlint shared library, so there is nothing else to build. Requires Python 3.9 or newer.
Minimal example
import vastlint
result = vastlint.validate(vast_xml)
if result.valid:
print("clean tag")
else:
print(result.summary.errors)
print(result.issues[0].message)
print(result.to_json(indent=2))FastAPI endpoint
from fastapi import FastAPI
from pydantic import BaseModel
import vastlint
app = FastAPI()
class VastPayload(BaseModel):
xml: str
max_wrapper_depth: int = 5
rule_overrides: dict[str, str] | None = None
@app.post("/validate")
def validate(payload: VastPayload):
result = vastlint.validate(
payload.xml,
max_wrapper_depth=payload.max_wrapper_depth,
rule_overrides=payload.rule_overrides,
)
return result.to_dict()The result shape is stable and frontend-friendly: version, an issues array (each with id, severity, message, path, spec_ref, line, and col), and a summary with errors, warnings, and infos counts.
Why validate VAST in-process
A malformed VAST tag is a billed impression that never renders: the player loads, the auction clears, the publisher is charged, and the viewer sees a blank slot. The failure shows up in the player, not your application logs, so a bad tag can sit in rotation until someone traces a revenue dip back to it.
The package calls the same Rust core the rest of the pipeline uses, in-process through FFI. No subprocess to spawn per request, no validation microservice to keep alive, and the XML never leaves your process. Because the result serialises to a stable shape, the same call that gates a creative on the backend also feeds the reviewer's screen.
Where it lands in an ad pipeline
Python tends to run the data, QA, and tooling side of the ad stack, so validation lands where creatives and people meet the system.
- Creative ingestion APIs: validate in the endpoint when a tag is submitted, and reject it with the specific issues before it is ever trafficked.
- Data and QA pipelines: an Airflow or cron job that re-validates every active tag and flags drift after upstream changes.
- Notebooks and analysis: pull a sample of live tags and quantify error and warning rates across partners.
- Demand-partner intake: validate sample
admpayloads from a new SSP or DSP partner during onboarding and report error rates as part of the SLA.
When not to use the Python package
If you just want a quick manual answer, use the web validator. If you only have a live tag URL, use the tester. If the real problem is wrapper depth or redirect chains, jump to the inspector.