# Pulse > The sponsorship arm of GameOn, a grassroots youth football software ecosystem. > One brand at a time sponsors the GameOn apps outright. Pulse is also the > measurement stack behind that inventory, and the home of the GameOn Sales > Agent, which transacts the inventory over the Ad Context Protocol. Site: https://pulse.gameon.futbol (public: `/`, `/how-it-works`, `/docs`). Publisher domain: https://gameon.futbol Inventory: sponsor lockup, banner and MREC placements inside the Sub Manager apps for iOS and Android. Sole occupancy for the booked window: no auction, no rotation, no competitor beside you. Sold as time on screen, not as impressions. Ads are served first-party. There are no ad SDKs, no third-party tags or pixels, and no user, device or event data leaves GameOn. Two audiences read this file, and the sections below are split accordingly. A **buying agent** wants the AdCP endpoint. A **GameOn product** embedding the pixel or the ad units wants the tracking and delivery endpoints. ## Buying agents (AdCP) The GameOn Sales Agent implements the Ad Context Protocol over MCP with the streamable-HTTP transport. Methods: `initialize`, `ping`, `tools/list`, `tools/call`. - Endpoint: https://assets.gameon.futbol/functions/v1/adcp (infrastructure alias: https://etrspsphgbkapczqxboy.supabase.co/functions/v1/adcp) - Publisher authorization: https://gameon.futbol/.well-known/adagents.json - `adcp_version`: `3.2`. `supported_versions`: `3.1`, `3.2`. - Tools: `get_adcp_capabilities`, `list_products`, `get_products`, `list_creative_formats`, `get_availability`, `create_media_buy`, `update_media_buy`, `get_media_buys`, `get_media_buy_delivery`, `sync_creatives`, `get_task_status`. - Flexible-window availability: `list_products` accepts `criteria.offer_filters.availability_horizon` and answers with time-dimensioned forecast points carrying `availability_status`, computed from every booking eligibility constraint, not competing holds alone. Declared as the `media_buy.availability_horizon` capability. - Human review: `create_media_buy` returns a **pending proposal**, never a committed buy. Every booking and every creative is reviewed by a person inside a published 24-hour window. An unanswered proposal auto-releases; we never auto-approve. - Reporting: aggregate counts only. Viewable time is measured first-party against the MRC threshold (at least 50% of the lockup's own pixels in view for at least one continuous second). Pulse is not MRC accredited, and says so. Where a placement is not measurable, the figure is reported as null, never as zero. - Declined at review: gambling, alcohol, vaping and tobacco. The audience includes junior-football families. ## Privacy contract - No PII collected or stored. - No IP address retention (country derived at edge from CF header when present, otherwise from a client-supplied region hint — never from IP lookup). - No User-Agent retention (used only for bot filtering at request time). - No cookies, no fingerprints, no cross-site identifiers. - Raw events retained 7 days, then aggregated and purged. - Apple privacy disclosure not required (no tracking, no personal data). ## What we store per event - `source` (host, e.g. `gameon.futbol`) - `path` (URL path, query/hash stripped, max 500 chars) - `event` (e.g. `pageview`, `ad_click`) - `ref_category` (`direct` | `internal` | `search` | `social` | `other`) - `country` (ISO-3166 alpha-2, derived at edge) - `meta` (optional, strict allow-list: `surface`, `sponsor_id`, `artifact`) ## Endpoints Base: https://etrspsphgbkapczqxboy.supabase.co/functions/v1 (Custom domain alias: https://assets.gameon.futbol/functions/v1) ### GET /logo-transparent 1x1 transparent PNG beacon. Logs a heartbeat. Always returns the pixel, even if logging fails. No request body. CORS open. Use as: `` ### POST /pulse-event Typed event ingestion. JSON body: ``` { "source": "gameon.futbol", // required, host "path": "/pricing", // optional, default "/" "event": "pageview", // optional, default "pageview" "ref": "https://google.com/..." // optional } ``` Response: `{ "ok": true }`. Bots filtered server-side. ### Share events Share buttons across our products post a `share_intent` event so we can see untracked reach without recording what was shared or to whom. ``` POST /pulse-event { "source": "gameon.futbol", "event": "share_intent", "meta": { "surface": "lineup", // required slug: lineup | stats | app_invite | sponsor_card | ... "sponsor_id": "uuid-or-omit", // optional, when the artifact carries a sponsor mark "artifact": "png" // optional: png | link | text } } ``` Use `share_completed` only when the host platform reports a confirmed completion (rare on iOS). `meta` is hard-capped server-side to the three keys above — anything else is dropped. ### GET /get-ads?formats=banner,large Returns published ads for mobile/web rendering. Cache 30 days client-side. Two ways to call: **Simple (GET):** - `formats` — CSV of formats the caller wants (`banner`, `large`). The server picks the best native asset per format and NEVER cross-serves (no 300x250 in a banner slot). Single `format=banner` still works. **Slot manifest (POST, preferred for mobile):** declare every slot you'll render in one call. The server returns one ad per slot, pre-paired by brand where possible. ``` POST /get-ads Content-Type: application/json { "slots": [ { "id": "feed_top", "format": "large", "width": 600, "height": 500, "dpr": 2, "placement": "feed" }, { "id": "feed_inline1", "format": "banner", "width": 640, "height": 100, "dpr": 2, "placement": "feed" }, { "id": "feed_inline2", "format": "banner", "width": 640, "height": 100, "dpr": 2, "placement": "feed" } ], "audience": { "team_tier": "competitive", "coach_tier": "developing", "match_result": "win", // win | loss | draw | none, optional "country": "AU", // ISO-3166 alpha-2, optional "sponsor_id": "..." // optional anchor for companion pairing } } ``` Rules: - `slots` required, 1–8 entries. `id` is caller-defined and echoed back. - `width`/`height` optional. If both provided, the server validates the aspect against the format (banner 6.4:1, large 1.2:1, ±10%) and returns `error: "aspect_mismatch"` for that slot rather than serving the wrong asset. - `dpr` optional 1–3 (default 2). Recorded for future hi-res variants. - `placement` optional, alphanum+`_-`, max 64 chars. Logged on impression. - `audience.match_result`/`country` currently logged only; future targeting. - Same ad will not be returned twice in one response. Response shape (both modes): ``` { "served_at": "...", "slots": { "": { "format": "...", "ad": { ... } | null, "error"?: "..." } }, "by_format": { "banner": [ ... ], "large": [ ... ] }, "ads": [ ... ] // flat list, kept for old clients } ``` When multiple formats are requested the server pairs companion ads automatically so a brand can fill both slots in one round-trip without the client passing `sponsor_id` back. ### GET /ad-click/:adId?to= 302 redirects to `to` and logs `ad_click`. Validates URL is http(s). ### GET /get-sponsors Returns pitch sponsors (logo_url normalised to absolute https URL). ## Implementation principles - Failsafe: tracking endpoints never block or error the host page. - Lightweight: pixel beacon is ~70 bytes; event endpoint is a single fetch. - Privacy-by-design: no client-side state, no SDK to install. - Server-side aggregation (daily cron, 2am UTC) to bypass row limits.