PixelFox Docs
Reference

REST ingest API

The public endpoints the SDK uses - and that your backend can use too.

Base URL: your PixelFox ingest API (e.g. https://api.pixelfox.app). Authenticate with the X-PixelFox-Key header, or ?key= where headers aren't possible. CORS is open; origin allow-listing is enforced per key (dashboard → Settings).

POST /v1/ingest

Send up to 50 events per request (body cap 256 KiB):

curl -X POST https://api.pixelfox.app/v1/ingest \
  -H "Content-Type: application/json" \
  -H "X-PixelFox-Key: pfx_live_…" \
  -d '{
    "events": [{
      "event": "purchase",
      "uuid": "3f1c…-…",
      "distinct_id": "user-123",
      "timestamp": "2026-08-05T12:00:00Z",
      "properties": {
        "order_id": "1001",
        "total": 129.5,
        "currency": "USD",
        "email": "[email protected]"
      }
    }]
  }'

Response: {"accepted": 1} - returned as soon as the batch is accepted; destination fan-out happens asynchronously. When the account's monthly event allowance (plus a 20% grace margin) is exhausted, the excess is dropped and reported as {"accepted": n, "dropped": m}; bot traffic and events matching the site's block rules answer {"accepted": 0} without an error.

FieldRequiredNotes
event✅Event name (see conventions)
uuid✅Unique id per event - the browser↔server dedup key. Omit only if no browser pixel will ever fire this event
distinct_id-Visitor/user id; defaults to an anonymous id derived server-side
timestamp-ISO 8601; defaults to arrival time
properties-Event properties. email/phone here trigger identity stitching on identify-type events

Errors: 401 missing key · 403 invalid key or origin not allowed · 413 too many events / body too large · 429 rate limited (200 req/10s per IP, 1000 req/10s per key).

GET /v1/config

What the SDK fetches on load - public per-site config, never secrets:

{
  "mode": "online",
  "business_category": "ecommerce_retail",
  "clarity": { "project_id": "…" } | null,
  "facebook": { "pixel_id": "…", "route_pixels": [{ "pixel_id": "…", "url_pattern": "/eu/*" }] } | null,
  "tiktok": { "pixel_code": "…" } | null,
  "ga4": { "measurement_id": "…" } | null,
  "gtm": { "container_id": "GTM-…" } | null,
  "custom_loader": false,
  "events": [ { "id": "…", "name": "hero_cta_clicked", "trigger": "click",
                "selector": ".hero .btn", "url_pattern": "/", "text_contains": "",
                "properties": {}, "conversion_event": "lead",
                "destinations": { "tiktok": false },
                "value_selector": "", "field_selectors": { "email": "input[name=email]" },
                "enabled": true } ]
}

mode is online or offline (offline sites are anonymous-only and the edge strips identifiers). ga4 is null when the destination routes server-side, so the tag does not load gtag. custom_loader is true when a first-party domain is configured and the tag should source vendor scripts through it. Per-definition destinations flags (absent = send) and the value_selector / field_selectors read at fire time are described under event definitions.

business_category is the site's Meta-style business category (ecommerce_retail, entertainment_media, education, real_estate, technology, financial_services, automotive, travel, other), set in the dashboard's site settings. It is informational - integrations use it to surface the conversion events recommended for that kind of business; capture behaves identically for every category.

POST /v1/inventory

Used by the SDK's auto-discovery; body { page, url, title, elements: [{ type, selector, text, href?, event_hint? }] } with at most 300 elements. Deduplicated server-side per (key, page) content hash. Response: {"accepted": n}.

When a site is past the plan's site limit

A plan includes a number of sites. If an account has more than that, its oldest sites keep collecting and the rest answer 200 with {"accepted": 0, "reason": "site_over_plan_limit"} until the account upgrades or removes a site. It is a 200, not an error, so a tag does not retry; no data already collected is removed.

GET /v1/health

{"ok": true, "ratelimit": "redis" | "memory", "queue": "rabbitmq" | "inline"} - for uptime checks. ratelimit says whether rate-limit and quota counters are shared across replicas; queue says whether background work is going through the durable queue or being done inline because no broker is configured or reachable. Neither value affects whether events are accepted.

Forwarded hits

/g/collect normally identifies the site by the hostname it was called on. A server-side GTM container forwarding events instead authenticates itself, and says who the visitor was:

HeaderValue
X-PixelFox-Forward-SecretThe container's forwarding secret (pff_…). Generated in the dashboard, shown once, stored only as a hash.
X-PixelFox-Visitor-IPThe visitor's IP address.
X-PixelFox-Visitor-UAThe visitor's user agent.

The two visitor headers are honoured only alongside a valid forwarding secret. They are ignored on every other request, including one carrying an API key: an API key is public, so it cannot be allowed to vouch for an IP address.

A forwarded hit differs from a direct one in four ways: geo, block rules and bot filtering use the stated visitor rather than the connection; the rate limit is per visitor IP and per container; no first-party cookies are set on the response; and it is never sent on to GA4 (and to no destination at all if the container's forwarding fan-out is off). An invalid secret is 403.

A request may carry several events: shared parameters in the query string and one event per line in a POST body (GA4's own batch format), up to 20.

Other public endpoints

RoutePurpose
GET /pixelfox.min.js, /pixelfox.mjs, /pixelfox-engage.min.js (also under /v1/)The tag itself, with ETag and a 5-minute cache
GET /v1/script-statusWhether the tag is built, its size and ETag
GET /v1/tls-check?domain=Certificate gate for first-party domains (used by the edge proxy)
GET/POST /g/collectGA4 wire-format intake for server-side GTM
POST /v1/engage, /v1/surveys/respond, /v1/promos/track, /v1/formSurveys, forms and promos
/sg.js, /sx.js, /sp.js, /tt.js, /a/c, /s/p, /t/eFirst-party custom loader proxies, served only on a claimed domain

On this page