Pixellint
Pixellint is a spec-first validator for pixels, postbacks, conversion API payloads, and other measurement artifacts. It runs in a terminal, in CI, and in agents, and every finding carries a stable id, a severity, an evidence level, and the document it came from.
Broken pixels cost attribution, QA time, and campaign money. The tooling that exists is fragmented: vendor-specific helpers, enterprise tag auditors, and schema linters that know nothing about ad tech. Pixellint is the open validation layer underneath all of that.
$ pixellint validate url 'https://www.facebook.com/tr?ev=Purchse&em=buyer@example.com'
rulepack: core
ok
rulepack: vendor/meta (vendor: meta)
error vendor.meta.param.id.missing `id` is required on Meta Pixel requests but is not present. It is the numeric Pixel ID from Events Manager.
fix: Set `id` to the numeric Pixel ID shown in Events Manager.
docs: https://developers.facebook.com/docs/meta-pixel/get-started
warning vendor.meta.param.ev.invalid `ev` is `Purchse`, which is not one of the documented values: PageView, AddPaymentInfo, ...
fix: Use a standard event name, or confirm the custom event is registered in Events Manager.
docs: https://developers.facebook.com/docs/meta-pixel/reference
error vendor.meta.pii.unhashed_email A parameter carries what looks like a raw email address. Meta requires customer information to be normalized and SHA-256 hashed before it is sent.
fix: Normalize the value, hash it with SHA-256, and send the hex digest instead of the plain address.
docs: https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching
2 error(s), 1 warning(s), 0 info message(s) across 2 rulepack(s).
Server-side events are checked in the body, one event at a time:
$ pixellint validate json '{"data":[{"event_name":"Purchase","event_time":1770000000000,
"action_source":"website","user_data":{"em":"buyer@example.com"}}]}'
rulepack: vendor/meta-conversions-api (vendor: meta)
error vendor.meta-conversions-api.body.event_time.invalid `event_time` must be at most 10 digits, but `1770000000000` has 13. Meta documents it as a Unix timestamp in seconds...
fix: Send seconds, not milliseconds: divide a JavaScript `Date.now()` by 1000 and floor it.
error vendor.meta-conversions-api.body.purchase_requires_value_and_currency A `Purchase` event is missing `custom_data.value` or `custom_data.currency`.
error vendor.meta-conversions-api.body.website_requires_source_url The event is marked `action_source: website` but carries no `event_source_url`.
Install
cargo install pixellint # CLI
cargo install pixellint-mcp # MCP server
npm install pixellint # library, WASM-backed
Or paste a URL into the playground at pixellint.org, which runs the same engine in your browser. Artifacts you test may be stored; see privacy. Vendor contracts are at pixellint.org/packs/. Guides for pixels, conversion APIs, and consent are at pixellint.org/docs/.
Start with the contracts people actually hit:
- Conversion API validator
- Pixel not firing
- Conversions API not working
- What is a conversion API
- Reddit CAPI
- DART Floodlight tags
- Facebook Pixel Helper vs Network
- Pinterest standard events
Use it
QA
Validate an inline artifact, a file with @path, or stdin with -:
pixellint validate url 'https://px.ads.linkedin.com/collect?pid=123456&fmt=gif'
pixellint validate postback @conversion-endpoint.txt --json
printf '%s\n' 'https://example.com/tracking?cb=[CACHEBUSTING]' | pixellint validate vast - --state template
Callers that already extracted many URLs (Vastlint, an HTML adapter) pass them as a document. Identical values validate once:
pixellint validate-many @extracted.json --json
Callers that know which artifacts belong to each ad session can supply explicit groups with original zero-based row indexes:
pixellint validate-sessions @sessions.json --json
The initial IAS UVP profile checks external-ID consistency within each group and uniqueness across groups. Its report preserves ordinary document findings and adds relationship coverage, original spans and provenance. See docs/SESSION_SCHEMA.md for grouping, typed skips and local resource limits.
extracted.json is the wrapper in
docs/MULTI_ARTIFACT_SCHEMA.md, or a JSON array
of URL strings. Pixellint does not parse VAST, HTML, or GTM.
vast means one tracking URL extracted from VAST, not a VAST XML document.
It enables the IAB VAST macro-name checks on that URL.
Use request for a complete HTTP capture. It links endpoint, method, headers,
query fields and body so the destination's transport and payload rules run
together:
pixellint validate request @captured-request.json --json
The capture has string url and method, a headers object or list of
{ "name": "...", "value": "..." } entries. Use an optional raw string body
for text, or body_base64 for captured binary bytes. The shared engine decodes
identity and gzip entities within explicit local limits.
See docs/HTTP_REQUEST_SCHEMA.md for examples and
validation limits. A bare URL passed as request keeps its existing behavior.
If the artifact is still a template with unexpanded macros, say so, and macro rules adjust:
pixellint validate url 'https://example.com/pixel?cb=[CACHEBUSTING]' --state template
CI
pixellint validate exits 0 when the artifact is clean or only produced
warnings, 1 when any error-severity finding is present, and 2 on a usage or
input problem.
- uses: aleksUIX/pixellint@v0.31.12
with:
path: fixtures/conversion-pixel.txt
kind: url
version selects the CLI release (auto follows the action's own v* tag).
Linux and macOS runners, x86_64 and aarch64. The Action downloads a prebuilt
tarball from GitHub Releases.
Or install the CLI:
- name: Validate tracking artifacts
run: |
cargo install pixellint
pixellint validate url @fixtures/conversion-pixel.txt
Agents
cargo install pixellint-mcp
pixellint-mcp speaks MCP over stdio and exposes three tools. It does not
send artifacts; there is no hosted MCP on pixellint.org. Playground artifacts
on pixellint.org may be stored; see pixellint.org/privacy.
-
MCP Registry name:
mcp-name: io.github.aleksUIX/pixellint -
list_rulepacks -
list_vendors, optionally filtered bycategoryor attributing a singlehost -
validate_artifact, takingartifact_kind,artifact, and optionalclaimed_vendor,expansion_state,rulepacks,except_rulepacks
Responses include the structured findings, the detected vendors, and a severity summary, so an agent can act on the result without parsing prose.
Node
The npm wrapper requires Node.js 18 or newer. Its ESM entry uses node:module
to load the Node-target WASM build. For a browser integration, build
pixellint-wasm for a browser target and supply a browser loader, as the
playground does.
import { validate, isOk } from "pixellint";
const summary = validate("https://www.facebook.com/tr?ev=Purchase");
isOk(summary); // false, the pixel id is missing
Library
use pixellint_core::{ArtifactKind, Engine, ExpansionState, ValidationOptions, ValidationRequest};
let engine = Engine::default();
let request = ValidationRequest {
artifact_kind: ArtifactKind::Url,
artifact: "https://www.facebook.com/tr?id=1234567890123456&ev=PageView".to_string(),
claimed_vendor: None,
expansion_state: ExpansionState::Unknown,
};
let summary = engine.validate(&request, &ValidationOptions::default())?;
assert!(summary.is_ok());
Rulepacks
core runs on every URL-like artifact. Vendor packs run only when the artifact
targets their endpoints, so you get vendor checks without asking for them, and
nothing fires on an endpoint it does not understand. A conversion API body has
no endpoint to go by, so those packs claim it by the shape of the payload.
The table shows selected built-in packs. Run pixellint list-rulepacks --json
for the complete inventory in your installed build.
| Rulepack | Covers | Evidence |
|---|---|---|
core | URL validity, transport, credentials, fragments, ad-tech macro handling, IAB consent signals | normative |
vendor/meta | facebook.com/tr pixel requests | official vendor |
vendor/meta-conversions-api | Graph API events edge, URL and JSON event payload | official vendor |
vendor/google-analytics | GA4 Measurement Protocol, URL and JSON event payload | official vendor |
vendor/google-analytics-collect | The /g/collect transport the Google tag uses in the browser | ecosystem reference |
vendor/google-tag-manager | gtm.js, gtag/js, and ns.html loader requests | official vendor |
vendor/google-ads-conversion | Google Ads conversion and view-through pixels | official vendor |
vendor/google-ads-click-conversions | Google Ads UploadClickConversions JSON | official vendor |
vendor/floodlight | Campaign Manager Floodlight activity tags | official vendor |
vendor/cm360-tracking-ad | CM360 tracking ads, dc_trk_aid and dc_trk_cid | official vendor |
vendor/cm360-vast-event | CM360 VAST event pixels, dc_oe on googlesyndication | ecosystem reference |
vendor/google-ad-manager | Ad Manager /gampad/ads, iu sz output env gdfp_req correlator | official vendor |
vendor/adobe-analytics | Adobe Analytics data collection beacons | official vendor |
vendor/pinterest | Pinterest tag requests and the noscript fallback | official vendor |
vendor/pinterest-conversions-api |
Vendor directory
There are 166 vendor rulepacks across 87 vendors. The vendor directory covers additional hosts by attribution: 134 vendor rows and 326 hosts, so an unrecognized pixel still gets a name. Full inventory: docs/STANDARDS.md.
The per-pack depth audit records reviewed sources, implemented checks and remaining requirements. Pack counts and passing fixtures do not certify complete vendor specification coverage. Private protocols, unsupported request representations, account settings and request history remain explicit coverage limits.
$ pixellint validate url 'https://trc.taboola.com/actions?a=1'
rulepack: directory (vendor: taboola)
info directory.no_rulepack_coverage This endpoint belongs to Taboola (native). No Pixellint rulepack covers it, so only the core checks ran. The `vendor/taboola` rulepack covers other Taboola endpoints, not this one.
Directory entries make one claim, that a host belongs to a vendor. They carry
no parameter contracts, the finding is always info, and attribution never
changes an exit code. See docs/VENDOR_DIRECTORY.md.
pixellint list-vendors
pixellint list-vendors --json
Select packs per run:
pixellint validate url "$ARTIFACT" --rulepack core
pixellint validate url "$ARTIFACT" --except vendor/meta
pixellint list-rulepacks --json
Full rule inventory with citations: docs/STANDARDS.md.
Evidence levels
Findings say where their authority comes from, and you can hold packs to different standards accordingly:
normative: a formal standard such as the WHATWG URL Standard or an RFCofficial_vendor: a parameter contract the vendor publishes, with the URLofficial_template: vendor-published templates or SDK behaviorecosystem_reference: consistent real-world behavior the vendor generates in its own UI but does not documentheuristic: Pixellint's judgment, labeled as such
The manifest loader refuses to compile a pack that claims official_vendor
without citing documentation.
Custom rulepacks
Rulepacks are data, not code. First-party vendor packs are written in the same JSON format you can write for an internal endpoint:
{
"id": "custom/acme",
"display_name": "Acme internal pixel",
"description": "Contract for the internal conversion endpoint.",
"vendor": "acme",
"source_level": "heuristic",
"match": { "hosts": ["px.acme.example"], "path_prefixes": ["/collect"] },
"params": [
{ "name": "aid", "requirement": "required", "format": { "kind": "integer" } },
{ "name": "ev", "requirement": "required", "format": { "kind": "enum", "values": ["view", "purchase"] } }
]
}
pixellint validate url 'https://px.acme.example/collect?ev=purchase' --rulepack-file acme.json
The format is documented in docs/RULEPACK_SCHEMA.md.
Private pixels that only need a name, not a parameter contract, belong in a directory overlay:
pixellint validate url 'https://px.acme.example/collect' --directory-file extra.json
The overlay uses the same entry shape as the built-in directory. New hosts only; it cannot steal a first-party host. Contribute an upstream host through a PR: CONTRIBUTING.md.
What Pixellint does not do
- It does not extract artifacts from documents.
vastaccepts a tracking URL, not XML.html,js, andgtmare not validation kinds; the CLI exits 2 if you pass them. Callers such as Vastlint parse VAST, HTML, or GTM containers and hand Pixellint the URLs they found.pixellint validate-manywraps those extracted URLs. The document result model is in docs/MULTI_ARTIFACT_SCHEMA.md. - It does not fire requests or check whether an endpoint responds.
- It does not auto-fix. Findings carry fix hints; applying them is on you.
Evidence
Every rule is backed by tests: unit tests in crates/pixellint-core/src/, a
golden corpus in fixtures/ with one directory per rulepack, and integration
tests that drive the real CLI binary and the real MCP stdio transport.
cargo test --workspace
Benchmark
In-process engine speed over the golden corpus, D1-shaped synthetic pixels, and large validate-many batches:
cargo run -p pixellint-bench --release -- --quick
cargo run -p pixellint-bench --release
cargo run -p pixellint-bench --release -- --mode load --heavy
See bench/README.md.
Docs
- Standards and rule inventory
- Vendor directory
- npm package
- Rulepack manifest schema
- Architecture
- Roadmap
- Multi-artifact output model
- Explicit ad-session input and output
License
Apache-2.0. Pixellint is not affiliated with or endorsed by any vendor named in its rulepacks; see NOTICE.
Local HAR 1.2 request capture import and offline replay are available through
pixellint import-har, pixellint validate-har, Rust, npm and WASM. See
HAR request replay and capture availability for
header policies, omitted-body handling and recorded-clock validation.