IP Geolocation for Streaming Media — Licensing, Anti-Circumvention, CDN Steering, and Ad-Insertion

Why streaming media is its own axis: every minute of premium video is licensed per ISO-2 territory under contracts that explicitly require the operator to demonstrate geo-enforcement on session-init. A residential-proxy hop from US → DE that bypasses HBO Max’s licensing window is a contractual breach to Warner Bros. Discovery, a chargeback liability with Visa, and (in the EU) a compliance hit under AVMSD Art. 13a. Sports rights are even tighter — English Premier League contracts force per-territory blackouts on a 3 km radius around the stadium during kick-off. The IP layer is where licensing windowing, VPN-bypass defence, CDN POP selection, and SSAI ad-insertion all originate. A wrong country resolve on session-init is a licensing-breach within the first frame.

The country an IP resolves to, the ASN it belongs to, and whether it’s a known datacenter, VPN, residential-proxy, or Tor exit are inputs to four separate streaming-media control surfaces:

  1. Geo-licensing enforcement (territorial content windowing) — the same SVOD catalog is fragmented across 30-190 ISO-2 territories; per-title rights are renewed in 1-3 year windows; a wrong country resolve serves a title outside its licence and triggers a studio-side breach claim. Hard-fail-closed on ambiguous resolves.
  2. VPN / proxy / Tor circumvention defence — Netflix, BBC iPlayer, Hulu, HBO Max, Disney+, DAZN, Sky, and Peacock all maintain VPN/residential-proxy block-lists; the dominant 2025-2026 vectors are datacenter ASNs (DigitalOcean, OVH, Hetzner, Vultr) and residential-proxy networks (Bright Data, Oxylabs, Smartproxy, IPRoyal) renting consumer IPs.
  3. CDN POP steering + bitrate-ladder selection — Akamai, Cloudflare, Fastly, BunnyCDN, and Lumen each have ≥ 100 edge POPs; the country + region + ASN input drives which POP serves the manifest, which adaptive-bitrate ladder is offered (1080p in DE, 720p where bandwidth is constrained), and which DRM key-server endpoint signs the playback licence.
  4. SSAI ad-insertion targeting + sports blackout windows — server-side ad insertion (SSAI) regionalises ad-spots by IP-country at the manifest-stitch step; live-sports operators (DAZN, ESPN+, Sky Sport) enforce per-event blackouts within 50-200 km radius of the venue or per-territory exclusion zones.

A single REST call to IP Geo API returns all four signal classes — country/region/city/lat-lon, ASN, threat-flags (VPN/proxy/Tor/hosting/relay), risk score — on every plan, no add-on SKU, ≤40 ms median in EU.

What streaming-media buyers care about (in order)

  1. Session-init latency budget ≤ 40 ms. Premium SVOD operators target a P95 manifest-load < 500 ms (Conviva 2025 — exit-before-video-start rises 6 % per +100 ms manifest delay). The IP-resolve must finish in ≤ 40 ms inside the manifest-stitch path or steering renders late and viewers churn before first frame. IP Geo API runs on EU edges (Hetzner Frankfurt) for ≤ 30-40 ms median across DE/NL/FR/IE/ES/IT/UK, ≤ 60 ms US-EU round-trip.
  2. Studio-grade audit trail for licensing breach claims. Major studios (Disney, WBD, Universal, Sony, Paramount, Netflix-origs licensees) require operators to retain per-session IP-country + ASN + threat-flag screening logs for 24 months minimum, available within 72 hours on contractual audit. IP Geo API ships a deterministic-replay log-format on every paid tier — same country_code + is_vpn + is_proxy + risk_score at lookup-time gets persisted in the response envelope for audit-grade reconstruction.
  3. Threat fields on every plan, not a paid add-on. Most US incumbents (MaxMind, ipinfo.io, ipstack) split datacenter/VPN/proxy classification into a paid Security Module or Privacy add-on. IP Geo API ships is_vpn, is_proxy, is_tor, is_hosting, is_relay, and a numeric risk_score on the free tier — critical for catching the > 80 % of geo-bypass traffic that originates from datacenter or residential-proxy ASNs.
  4. ASN-level granularity for residential-proxy block-listing. Country-only checks pass residential-proxy traffic through; ASN + is_proxy flag catches them. We expose asn, asn_org, and is_proxy as first-class fields so your session-init filters can reject at ASN granularity (Bright Data 212238 / 401116, Oxylabs 396982 / 60068, Smartproxy 62240 / 16276, IPRoyal 35916 / 174, Tier3/Choopa 21859 / 32475) without maintaining a list yourself.
  5. EU residency + GDPR + AVMSD posture. AVMSD (EU Directive 2018/1808) makes VOD operators jointly responsible with the rights-holder for territorial enforcement; failed geo-enforcement is enforced by national media authorities (CSA in BE, CvdM in NL, Ofcom in UK, AGCOM in IT). Viewer IPs cannot be transferred to a US vendor without GDPR §28 DPA + SCCs + TIA. IP Geo API is EU-only data-flow, signed DPA in 24h, no SCCs required, AVMSD-audit-ready.

The four streaming-media control surfaces, in code

1. Geo-licensing enforcement at session-init (hard-fail-closed)

// /api/playback/session.js — Node 20 / manifest-stitch path
// Called on every HLS/DASH manifest request BEFORE the master.m3u8 / .mpd is signed.
// Fail-closed: on classification error or ambiguous resolve, return HTTP 451 (Unavailable for Legal Reasons).
const fetch = require('undici').fetch;
const sign = require('./drm-signer');

// Per-title territorial windowing — loaded from rights-management DB on title-publish.
const RIGHTS = {
  'WBD:HBO-ORIG-2026:s01e01': {
    licensed: ['US','PR','VI'],
    blackout_radius_km: 0,
    license_window: { from: '2026-03-15', to: '2027-03-14' }
  },
  'PREMIER-LEAGUE:LIV-MUN:2026-05-12': {
    licensed: ['GB','IE','MT','GI'],
    blackout_radius_km: 50,      // 50 km around Anfield (stadium) — UK blackout rule
    blackout_center: { lat: 53.4308, lon: -2.9608 },
    license_window: { from: '2026-05-12T14:00Z', to: '2026-05-12T17:00Z' }
  }
};

async function authorisePlayback(req, res) {
  const titleId = req.params.titleId;
  const ip = req.headers['cf-connecting-ip'] || req.ip;
  const rights = RIGHTS[titleId];
  if (!rights) return res.status(404).end();

  let geo;
  try {
    geo = await (await fetch(`https://ipgeo.10b.app/v1/${ip}?fields=country_code,latitude,longitude,is_vpn,is_proxy,is_tor,asn,risk_score`, {
      headers: { 'Authorization': `Bearer ${process.env.IPGEO_KEY}` },
      signal: AbortSignal.timeout(40)   // ≤ 40 ms hard budget
    })).json();
  } catch { return res.status(451).end(); }  // fail-closed

  // Hard licensing window check
  if (!rights.licensed.includes(geo.country_code)) return res.status(451).end();
  if (geo.is_vpn || geo.is_proxy || geo.is_tor) return res.status(451).end();

  // Per-event blackout-radius check (sports)
  if (rights.blackout_radius_km > 0) {
    const km = haversine(rights.blackout_center, { lat: geo.latitude, lon: geo.longitude });
    if (km <= rights.blackout_radius_km) return res.status(451).end();
  }

  // Persist audit trail for studio / rights-holder claim resolution (24 months).
  await auditLog.append({ titleId, ip, geo, ts: Date.now() });
  return res.json({ manifest: sign(titleId, geo) });
}

2. VPN / proxy / Tor circumvention defence (signup + session-init)

# api/abuse/circumvention.py — FastAPI session-init filter
# Catches the > 80 % of geo-bypass attempts from datacenter + residential-proxy ASNs.
# Bright Data 212238/401116, Oxylabs 396982/60068, Smartproxy 62240/16276, IPRoyal 35916/174.
from fastapi import HTTPException
import httpx

RESIDENTIAL_PROXY_ASN = {
    212238, 401116,   # Bright Data (Luminati Networks)
    396982, 60068,    # Oxylabs (Tesonet)
    62240,  16276,    # Smartproxy (OVH-resold)
    35916,  174,      # IPRoyal (Cogent-resold)
    21859,  32475,    # Tier3 / Choopa (used by Smartproxy ranges)
}
DATACENTER_ASN_HOSTING = {14061, 16509, 14618, 16276, 24940, 20473, 8075, 13335, 396982}

async def gate_session_init(ip: str) -> None:
    async with httpx.AsyncClient(timeout=0.04) as cx:
        r = await cx.get(
            f"https://ipgeo.10b.app/v1/{ip}",
            params={"fields": "country_code,asn,is_vpn,is_proxy,is_tor,is_hosting,is_relay,risk_score"},
            headers={"Authorization": f"Bearer {IPGEO_KEY}"}
        )
    g = r.json()

    # Hard block — any flagged circumvention signal
    if g["is_vpn"] or g["is_proxy"] or g["is_tor"] or g["is_relay"]:
        raise HTTPException(451, "VPN/proxy/Tor not permitted for licensed content")

    # ASN-level block — residential-proxy and unattested hosting
    if g["asn"] in RESIDENTIAL_PROXY_ASN:
        raise HTTPException(451, "Residential-proxy ASN blocked")

    # Soft warn for datacenter ASNs (hosting customers, not consumer ISPs)
    if g["is_hosting"] and g["asn"] in DATACENTER_ASN_HOSTING and g["risk_score"] >= 60:
        raise HTTPException(451, "Datacenter origin not permitted")

3. CDN POP steering + adaptive-bitrate ladder selection

// /api/manifest/stitch.js — Node 20 / origin-shield
// On the manifest-stitch path, pick CDN POP + bitrate ladder by country + ASN.
const POPS = {
  AMS: { regions: ['NL','BE','LU','DE','DK','SE','NO','FI'] },
  FRA: { regions: ['DE','AT','CH','CZ','SK','HU','PL'] },
  LON: { regions: ['GB','IE','JE','GG','IM','GI'] },
  CDG: { regions: ['FR','MC','BE','LU','ES','PT'] },
  MIL: { regions: ['IT','SM','VA','MT'] },
  IAD: { regions: ['US','CA','MX','PR'] },
  GRU: { regions: ['BR','AR','CL','UY','PY','CO','PE'] }
};
const BITRATE_LADDERS = {
  premium: ['4K-HEVC','1080p-AVC','720p-AVC','480p-AVC','240p-AVC'],   // unlimited-bandwidth markets
  standard:['1080p-AVC','720p-AVC','480p-AVC','240p-AVC'],
  mobile_first:['720p-AVC','480p-AVC','240p-AVC','144p-AVC']           // bandwidth-constrained markets
};
const LADDER_BY_REGION = {
  'DE':'premium','NL':'premium','GB':'premium','US':'premium','SE':'premium','FR':'premium',
  'PL':'standard','PT':'standard','TR':'standard','MX':'standard','BR':'standard',
  'IN':'mobile_first','ID':'mobile_first','PH':'mobile_first','NG':'mobile_first','KE':'mobile_first'
};

function steer(geo) {
  const pop = Object.entries(POPS).find(([_, v]) => v.regions.includes(geo.country_code))?.[0] ?? 'IAD';
  const ladder = LADDER_BY_REGION[geo.country_code] ?? 'standard';
  return { pop, ladder: BITRATE_LADDERS[ladder] };
}

4. SSAI ad-insertion targeting + sports blackout windows

# ssai/decision.py — Server-Side Ad Insertion decision-engine
# Picks the ad-spot pool for the manifest-stitcher based on IP-country + region + risk_score.
# Sports operators add blackout-radius check around stadium GPS during kick-off window.

from datetime import datetime, timezone
import httpx

AD_POOLS_BY_COUNTRY = {
    "DE": "pool-de-de",     # German-language ads, EUR CPM €18-€26
    "AT": "pool-de-at",
    "FR": "pool-fr-fr",
    "GB": "pool-en-gb",
    "US": "pool-en-us",     # $14-22 CPM, MRC-counted
    "BR": "pool-pt-br",
    "JP": "pool-ja-jp",
}

EVENT_BLACKOUTS = [
    {
      "event": "PREMIER-LEAGUE:LIV-MUN:2026-05-12",
      "venue": {"lat": 53.4308, "lon": -2.9608},
      "radius_km": 50,
      "from": "2026-05-12T14:00Z", "to": "2026-05-12T17:00Z",
      "blackout_countries": ["GB"]
    }
]

async def pick_ad_pool(ip: str, event_id: str | None = None) -> str | None:
    async with httpx.AsyncClient(timeout=0.04) as cx:
        r = await cx.get(f"https://ipgeo.10b.app/v1/{ip}",
            params={"fields": "country_code,latitude,longitude,risk_score,is_vpn,is_proxy"},
            headers={"Authorization": f"Bearer {IPGEO_KEY}"})
    g = r.json()
    if g["is_vpn"] or g["is_proxy"]: return None              # no ads on circumvention traffic
    if g["risk_score"] >= 70:        return None              # no ads on fraud-risk sessions

    # Blackout enforcement for live sports
    for bl in EVENT_BLACKOUTS:
        if event_id != bl["event"]: continue
        now = datetime.now(timezone.utc)
        t_from = datetime.fromisoformat(bl["from"].replace("Z","+00:00"))
        t_to   = datetime.fromisoformat(bl["to"].replace("Z","+00:00"))
        if not (t_from <= now <= t_to): continue
        if g["country_code"] in bl["blackout_countries"]:
            return "pool-blackout-fallback"                   # serve non-event ad pool
        # radius check
        from math import radians, sin, cos, asin, sqrt
        lat1, lon1 = radians(bl["venue"]["lat"]), radians(bl["venue"]["lon"])
        lat2, lon2 = radians(g["latitude"]), radians(g["longitude"])
        d = 2 * 6371 * asin(sqrt(sin((lat2-lat1)/2)**2 + cos(lat1)*cos(lat2)*sin((lon2-lon1)/2)**2))
        if d <= bl["radius_km"]:
            return "pool-blackout-fallback"

    return AD_POOLS_BY_COUNTRY.get(g["country_code"], "pool-en-us-default")

Pricing math — when does IP Geo API pay for itself for a streaming-media operator?

Plan Lookups / month Median session-init latency Threat fields Equivalent licensing-breach risk reduction
Free (1K/day) 30 K ≤ 40 ms bundled Sandbox / staging — covers QA region matrices
Starter €29 ~1 M ≤ 40 ms bundled Single-region SVOD ≤ 100 K monthly active sessions
Business €99 ~15 M ≤ 40 ms bundled, SLA Multi-region SVOD ≤ 5 M monthly active sessions, audit-grade
Enterprise (POA) ≥ 50 M dedicated edges full + custom Tier-1 OTT operator (Netflix-class), per-event sports blackout

At Business €99/mo: a single blocked residential-proxy session that would have streamed a UEFA Champions League final outside its licensed territory (Sky DE → BR via proxy) saves ~€60-€220 in studio-side per-breach claim cost. Most streaming-media operators recoup the €99/mo Business tier on < 1 blocked breach-class session per month.

Honest limits — when IP geolocation is not the right signal

  1. Carrier-grade NAT (CG-NAT) regions — in MENA, parts of LATAM, and rural India, CG-NAT collapses thousands of subscribers behind a single egress IP-block. Geo-resolve is country-accurate but city-resolve degrades. We expose city_confidence so the manifest-stitcher can fall back to country-only DRM scoping in CG-NAT regions.
  2. iCloud Private Relay + Proxy-on-by-default OS-modes — Apple’s iCloud Private Relay shifts the egress IP to a single Cloudflare/Akamai relay-block; we classify these as is_relay=true so the streaming app can decide whether to opt-in route (preferred for premium tiers) or fall back to a full-IP unmask challenge.
  3. Mobile-data IP rotation — 4G/5G subscribers can hop between cell-tower egress IPs in a 4-7 minute window; do not invalidate active playback sessions on IP-change alone. Pin the playback session to the at-session-init country + ASN snapshot for ≤ 6 h.
  4. MaxMind is_anonymous_proxy deprecation — MaxMind sunset is_anonymous_proxy in 2024 Q3; if your existing studio-audit logs reference it, migrate to is_proxy + is_vpn + is_relay + is_hosting composite. We expose all four for forward-compatibility on the audit-trail.
  5. Sports blackout 3 km radius edge-case — the EPL “3 km radius around stadium” rule actually applies to the entire local Football Association zone for the venue; map blackout polygons to ISO-3166-2 sub-regions (or HASC codes) rather than a fixed lat-lon radius for studio-grade conformance.

Use-cases that compose with streaming media

Every streaming-media stack composes 2-4 IP-layer use-cases. The relevant primary deep-dives:

Compare IP Geo API to the providers streaming-media teams evaluate

If you’re shortlisting vendors for a session-init refactor, a manifest-stitcher rebuild, or a rights-management audit, these head-to-heads cover the providers most often shortlisted in the IP-geolocation market:

Read also — narrative deep-dives

Seven 2026-dated comparison articles with code-level migration sketches and latency / pricing math at 100K / 1M / 10M req/mo:

Migration walkthroughs — drop-in code-level guides

Already on an incumbent? These step-by-step migration guides ship with field-by-field maps, code diffs, shadow-mode validation, and rollback notes:

Industry deep-dives

Other vertical-specific surfaces using the same IP Geo API primitives:


Get started — streaming-media-friendly procurement

Sign up at https://ipgeo.10b.app/pricing and start with a sandbox key today.


Get early access — 50% off for 12 months

First 100 signups lock in 50% off any paid plan for the first year. No credit card required — we’ll email you at launch.