IP Geolocation for Online Education — Cross-Border Licensure, PPP Tuition Tiering, Exam-Proctoring Geo-Anchor, Student-Data Residency

Why online education is its own axis: every enrolment-page paint on an LMS / MOOC / online-degree platform is a four-jurisdiction decision in <40 ms — student-residency-state distance-learning licensure (US SARA 49-state reciprocity + non-SARA CA/MA partial + EU ENIC-NARIC degree-recognition + UK QAA + AU TEQSA + IN AICTE), PPP-adjusted tuition tier (legitimate price-discrimination per UNESCO IIEP + World Bank IBRD-tier guidance), exam-proctoring geo-anchor (IELTS/TOEFL/GMAT/GRE/CFA/CPA digital-proctoring rules require IP+ID-doc country-match at session-init), and FERPA + GDPR Art. 9 student-data residency for special-category educational records. IP-layer signals are upstream of the enrolment-CTA render, the proctored-exam admit-card flow, the LMS-shard routing call, and the post-enrolment GDPR / FERPA / state-AG audit eval. A wrong country resolve at enrolment-paint either breaches state licensure (CA-resident enrolled in a non-SARA program from a non-licensed institution) or breaches sanctions (US-Cuba OFAC 31 CFR §515.565 academic General License gate).

The country an IP resolves to, the state/region it belongs to, the ASN it sits on, and whether it’s a known datacenter, VPN, residential-proxy, or Tor exit are inputs to four separate online-education control surfaces:

  1. Cross-border distance-learning licensure + accreditation gating at enrolment-init — every US-based online-degree program faces 50-state licensure: SARA (49 states + DC + PR + USVI, Council of State Authorization Reciprocity Agreements) provides reciprocity for distance-ed across member states; CA + MA are non-SARA → require direct authorization or hard-stop. EU programs face ENIC-NARIC (47 national centres) per-country degree-recognition + Bologna Process ECTS validation; UK programs face QAA + OfS registration + HESA reporting; AU programs face TEQSA + CRICOS for international students; IN programs face AICTE + UGC distance-ed approval. Hard-fail-closed: enrolment from a state/country where the program is not licensed → HTTP 451 + redirect to licensure-status page. Wrong call = US state-AG enforcement action (CA Bureau of Private Postsecondary Education has issued ~$2M in penalties 2023-24) + loss of Title IV federal financial aid eligibility ($800M+/yr sector).
  2. PPP-adjusted tuition tiering at enrolment-checkout (anti-arbitrage) — every MOOC, online-degree, and bootcamp uses regional pricing — Coursera Plus is $59/mo in US but ~$10/mo in IN/PK/NG; edX MicroMasters is $1,500 in US but ~$400 in LATAM; Duolingo Super is $14/mo in US but $4/mo in TR. VPN/proxy arbitrage (US-resident routing through an IN residential proxy to hit emerging-market tier) is contract-violation per platform T&Cs and a ~$50-200 unit-margin leak per learner per year. Hard-fail-closed: VPN/proxy/Tor at enrolment → fall back to T1_USD/EUR tier (anti-arbitrage), residential-proxy ASN → block + manual review at checkout. Pin enrolment-tier to BIN-billing-country at payment-time (defence in depth — IP geo is render-time, BIN is settlement-time).
  3. Exam-proctoring geo-anchor + ID-doc residency check at session-init — high-stakes proctored exams (IELTS Online, TOEFL iBT Home Edition, GMAT Online, GRE at Home, CFA, CPA, USMLE Step-1, Pearson PTE Home) require IP-country to match ID-doc-issuing-country at admit-card-issue + session-init. Hard-fail-closed: IP-country ≠ ID-country → exam void (no refund). Residential-proxy ASN at session-init → exam void + ETS/GMAC/CFA-Institute fraud-flag (lifetime ban). Datacenter IP at session-init → instant-reject (proctoring software requires home-network egress for proper environment scan). VPN at proctoring → void + fraud-flag (ETS 2024: ~14K exams voided/yr for VPN detection, $200 avg fee = ~$2.8M sector exposure).
  4. FERPA + GDPR Art. 9 + Schrems II student-data residency at LMS shard-routing — student educational records are FERPA-protected (20 USC §1232g) in US, GDPR special-category Art. 9 in EU (mental-health accommodations + disability accommodations are health-data), state-AG protected (CA SOPIPA + NY SHIELD Act + IL SOPPA + CO HB 23-1130). LMS shard-routing: route EU-student LMS-session to EU-FRA shard (Hetzner / OVH), US-student to US-IAD, CA-student to CA-YYZ (BC/AB/QC residency requirements), AU-student to AU-SYD (Privacy Act 1988 APP 8). VPN/proxy → fall back to EU-FRA highest-protection shard. Schrems II SCC-required-flag for any US-shard routing of EU-student data. Wrong call = €20M GDPR fine or $50K-per-violation FERPA penalty (FERPA-PPRA Joint Guidance 2024).

A single REST call to IP Geo API returns all four signal classes — country/region/state/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 online-education buyers care about (in order)

  1. Enrolment-paint latency budget ≤ 40 ms. Enrolment landing page must paint in ≤ 1 s on 4G mobile (50% of global online-learning traffic is mobile-first per UNESCO 2024); IP-resolve sits inside the first-paint path for tuition-tier + currency-display + licensure-pre-gate. 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 — well under the enrolment-paint budget and ahead of any LMS-shard cold-start (300-800 ms for new-session provisioning on Canvas/Blackboard/D2L/Moodle).
  2. FERPA + GDPR + state-AG audit-trail bundle. FERPA (20 USC §1232g) requires educational-record access-logs retained for the lifetime of the record + 5 years post-graduation; GDPR Art. 30 RoPA requires per-transaction third-country-transfer evidence (Schrems II Recommendations 01/2020); SARA + state-AG requires per-enrolment student-residency-state evidence. IP Geo API ships a deterministic-replay log-format on every paid tier — same country_code + region + is_vpn + is_proxy + risk_score at lookup-time gets persisted in the response envelope for audit-grade reconstruction by your registrar + DPO + state-AG-compliance teams.
  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 blocking residential-proxy proctoring fraud (ETS 2024: ~78% of voided exams trace to residential-proxy ASN egress) and for tuition-tier arbitrage detection on regional-pricing pages.
  4. State + region granularity for SARA + CA + MA non-SARA + EU-state licensure routing. Country-only checks miss the SARA-vs-non-SARA distinction critical to US distance-ed compliance — CA-resident enrolling in a non-SARA program requires direct authorization (or hard-stop), MA-resident faces partial-reciprocity. ASN + state-level resolution lets the enrolment engine fork policies at SARA-member-state granularity. We expose subdivision_iso_code (ISO 3166-2: e.g. US-CA, US-MA, US-TX) on every paid tier — exactly what the SARA Manual 23.0 (2024) §2.5© requires for enrolment-time student-residency determination.
  5. EU residency + GDPR + FERPA + ePrivacy posture. Student IPs cannot be transferred to a US vendor without GDPR §28 DPA + SCCs + TIA (Schrems II); FERPA requires written-consent for non-US-vendor disclosure of educational records (34 CFR §99.31 directory-information exception). Cookieless IP-resolve preferred over cookie-based tracking under ePrivacy Directive 2002/58/EC + EU CJEU Planet49 (no consent required for IP-resolve as legitimate-interest §6(1)(f) on licensure-compliance + fraud-prevention basis). The entire chain (IP-resolve, licensure-gate, PPP-tier-calc, proctoring-anchor, LMS-shard-route) must be EU-data-residency end-to-end for EU-student enrolments. IP Geo API is EU-only data-flow, signed DPA in 24h, no SCCs required.

The four online-education control surfaces, in code

1. Cross-border distance-learning licensure + accreditation gating (hard-fail-closed)

// /api/enrolment/gate.js — Node 20 / enrolment-page paint, ≤40 ms hard budget
// Resolves IP → student-residency-state → SARA / non-SARA / EU-ENIC-NARIC / UK-QAA / AU-TEQSA gate.
const fetch = require('undici').fetch;

// SARA member-state map (49 of 50 US states + DC + PR + USVI as of Manual 23.0, 2024)
const SARA_MEMBERS = new Set([
  'AL','AK','AZ','AR','CO','CT','DE','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME',
  'MD','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA',
  'RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY','DC','PR','VI'
]);

// CA + MA are non-SARA → direct authorization required or hard-stop
const NON_SARA = new Set(['CA','MA']);

// EU ENIC-NARIC + Bologna ECTS reciprocity
const EU_ENIC_NARIC = new Set([
  'DE','NL','FR','BE','LU','AT','DK','SE','FI','IE','ES','IT','PT','GR','PL','CZ','HU','SK',
  'RO','BG','HR','SI','EE','LV','LT','CY','MT','IS','NO','LI','CH'
]);

const SANCTIONS_HARDSTOP = new Set(['IR','KP','SY','CU','BY','RU','MM','VE','AF','SO']);

async function gateEnrolment(req, programId) {
  const ip = req.headers['cf-connecting-ip'] || req.ip;
  let geo;
  try {
    geo = await (await fetch(`https://ipgeo.10b.app/v1/${ip}?fields=country_code,subdivision_iso_code,is_vpn,is_proxy,is_tor,asn,risk_score`, {
      headers: { 'Authorization': `Bearer ${process.env.IPGEO_KEY}` },
      signal: AbortSignal.timeout(40)
    })).json();
  } catch {
    // Fail-conservative: on timeout, present the manual-residency-attestation form (no auto-enrolment)
    return { gate: 'manual_attestation_required', reason: 'geo_timeout_no_auto_enrolment' };
  }

  // Hard sanctions stop — refuse to render enrolment CTA
  if (SANCTIONS_HARDSTOP.has(geo.country_code)) return { gate: 'sanctions_hardstop', status: 451 };

  // VPN / proxy / Tor → manual-attestation + suspend auto-enrolment (anti-licensure-circumvention)
  if (geo.is_vpn || geo.is_proxy || geo.is_tor) {
    return { gate: 'manual_attestation_required', reason: 'anonymised_origin_residency_unverifiable' };
  }

  // US: SARA / non-SARA branch
  if (geo.country_code === 'US') {
    const stateCode = geo.subdivision_iso_code?.replace('US-', '');
    if (NON_SARA.has(stateCode)) {
      // Check programId's direct-authorization status in this state
      return await checkDirectAuthorization(programId, stateCode);
    }
    if (SARA_MEMBERS.has(stateCode)) return { gate: 'sara_reciprocity_open', state: stateCode };
    return { gate: 'state_not_recognized', status: 451 };
  }

  // EU ENIC-NARIC / Bologna ECTS recognition
  if (EU_ENIC_NARIC.has(geo.country_code)) return { gate: 'enic_naric_open', country: geo.country_code };

  // UK QAA / OfS gate
  if (geo.country_code === 'GB') return { gate: 'qaa_ofs_open' };

  // AU TEQSA / CRICOS gate
  if (geo.country_code === 'AU') return { gate: 'teqsa_cricos_open' };

  // Fall-through: present international-student-application flow with manual review
  return { gate: 'international_manual_review', country: geo.country_code };
}

2. PPP-adjusted tuition tiering at enrolment-checkout (anti-arbitrage)

# /api/enrolment/tuition-tier.py — FastAPI / regional-pricing render, ≤40 ms
# IP → World Bank IBRD-tier → tuition-multiplier; VPN/proxy → fall back to T1.
from fastapi import FastAPI, Request, HTTPException
import httpx, os

IPGEO_KEY = os.environ["IPGEO_KEY"]

# 8-tier pricebook (multiplier on USD-base tuition)
TIER_MULTIPLIERS = {
    "T1_HIGH_INCOME":   {"mult": 1.00, "countries": {"US","CA","GB","DE","NL","FR","BE","LU","AT","DK","SE","FI","IE","IS","NO","CH","AU","NZ","JP","SG","HK","KR","IL","AE","QA","KW"}},
    "T2_UPPER_MID":     {"mult": 0.65, "countries": {"ES","IT","PT","GR","PL","CZ","HU","SK","SI","EE","LV","LT","CY","MT","CL","UY","CR","PA","RO","HR","BG"}},
    "T3_LOWER_MID":     {"mult": 0.35, "countries": {"BR","MX","AR","CO","PE","EC","DO","VE","TH","MY","TR","ZA","RS","BA","UA","GE","AM","TN","MA","EG","JO","LB"}},
    "T4_EMERGING":      {"mult": 0.22, "countries": {"ID","PH","VN","LK","BD","PK","UZ","KG","MD","KE","NG","GH","CI","SN","ET","RW","TZ","UG","ZM","ZW"}},
    "T5_LOW_INCOME":    {"mult": 0.15, "countries": {"IN","NP","KH","LA","MM","MW","MZ","MG","BJ","BF","ML","NE","SL","LR","TG","SO","SS","CF","CD"}},
}

# Residential-proxy ASNs — instant block on enrolment checkout
RES_PROXY_ASNS = {212238, 401116, 396982, 60068, 62240, 16276, 35916, 174, 21859, 32475}

SANCTIONS_HARDSTOP = {"IR","KP","SY","CU","BY","RU","MM","VE","AF","SO"}

async def tuition_tier(request: Request, base_tuition_usd: float) -> dict:
    ip = request.headers.get("cf-connecting-ip", request.client.host)
    async with httpx.AsyncClient(timeout=0.04) as cx:
        try:
            r = await cx.get(
                f"https://ipgeo.10b.app/v1/{ip}",
                params={"fields": "country_code,is_vpn,is_proxy,is_tor,asn,risk_score"},
                headers={"Authorization": f"Bearer {IPGEO_KEY}"},
            )
            geo = r.json()
        except Exception:
            # Fail-conservative: T1 (no arbitrage exposure)
            return {"tier": "T1_HIGH_INCOME", "mult": 1.00, "tuition_usd": base_tuition_usd, "reason": "geo_timeout_default_t1"}

    if geo["country_code"] in SANCTIONS_HARDSTOP:
        raise HTTPException(status_code=451, detail="sanctions_jurisdiction_blocked")

    if geo.get("asn") in RES_PROXY_ASNS:
        raise HTTPException(status_code=403, detail="residential_proxy_asn_blocked")

    # VPN / proxy / Tor → fall back to T1 (anti-arbitrage on regional-pricing)
    if geo.get("is_vpn") or geo.get("is_proxy") or geo.get("is_tor"):
        return {"tier": "T1_HIGH_INCOME", "mult": 1.00, "tuition_usd": base_tuition_usd, "reason": "anonymised_origin_anti_arbitrage"}

    for tier_name, tier in TIER_MULTIPLIERS.items():
        if geo["country_code"] in tier["countries"]:
            return {"tier": tier_name, "mult": tier["mult"], "tuition_usd": round(base_tuition_usd * tier["mult"], 2), "origin": geo["country_code"]}
    # Default: T2_UPPER_MID for unmapped countries (mid-protection on margin)
    return {"tier": "T2_UPPER_MID", "mult": 0.65, "tuition_usd": round(base_tuition_usd * 0.65, 2), "origin": geo["country_code"], "reason": "country_not_in_pricebook_default_t2"}

3. Exam-proctoring geo-anchor + ID-doc residency check (hard-fail-closed)

# /api/proctoring/session-init.py — FastAPI / proctored-exam admit, ≤40 ms
# At session-init: IP-country MUST match ID-doc-issuing-country; residential-proxy/datacenter/VPN = void.
from fastapi import FastAPI, HTTPException, Request
import httpx, os

IPGEO_KEY = os.environ["IPGEO_KEY"]

# Residential-proxy ASN block-list (driving ~78% of voided exams per ETS 2024)
RES_PROXY_ASNS = {212238, 401116, 396982, 60068, 62240, 16276, 35916, 174, 21859, 32475}

# Datacenter ASN block-list — proctoring requires home-network egress
DATACENTER_ASNS = {16509, 14618, 32934, 15169, 8075, 13335, 16276, 14061}  # AWS, GCP, Meta, Cloudflare, OVH, DO

EXAM_SANCTIONS_HARDSTOP = {"IR","KP","SY","CU","BY","RU","MM","VE","AF","SO"}

async def proctor_session_init(request: Request, candidate_id: str, exam_id: str, id_doc_country: str) -> dict:
    ip = request.headers.get("cf-connecting-ip", request.client.host)
    async with httpx.AsyncClient(timeout=0.04) as cx:
        try:
            r = await cx.get(
                f"https://ipgeo.10b.app/v1/{ip}",
                params={"fields": "country_code,is_vpn,is_proxy,is_tor,is_hosting,is_relay,asn,risk_score"},
                headers={"Authorization": f"Bearer {IPGEO_KEY}"},
            )
            geo = r.json()
        except Exception:
            # Fail-closed: void exam on geo-timeout (cannot verify residency)
            await void_exam(candidate_id, exam_id, reason="geo_timeout_residency_unverifiable")
            raise HTTPException(status_code=451, detail="residency_verification_required")

    # Sanctions hard-stop — void exam + fraud-flag
    if geo["country_code"] in EXAM_SANCTIONS_HARDSTOP:
        await void_exam(candidate_id, exam_id, reason=f"sanctions_jurisdiction_{geo['country_code']}")
        raise HTTPException(status_code=451, detail="sanctions_jurisdiction_blocked")

    # Residential-proxy ASN → void + lifetime fraud-flag (ETS/GMAC/CFA-Institute policy)
    if geo.get("asn") in RES_PROXY_ASNS:
        await void_exam(candidate_id, exam_id, reason=f"residential_proxy_asn_{geo['asn']}")
        await fraud_flag(candidate_id, severity="lifetime_ban")
        raise HTTPException(status_code=403, detail="residential_proxy_detected_exam_voided")

    # Datacenter ASN → instant reject (proctoring requires home-network)
    if geo.get("asn") in DATACENTER_ASNS or geo.get("is_hosting"):
        await void_exam(candidate_id, exam_id, reason=f"datacenter_origin_asn_{geo['asn']}")
        raise HTTPException(status_code=403, detail="datacenter_origin_not_permitted")

    # VPN / proxy / Tor / relay → void exam (no remediation)
    if geo.get("is_vpn") or geo.get("is_proxy") or geo.get("is_tor") or geo.get("is_relay"):
        await void_exam(candidate_id, exam_id, reason="vpn_proxy_tor_at_session_init")
        await fraud_flag(candidate_id, severity="standard")
        raise HTTPException(status_code=403, detail="anonymised_origin_exam_voided")

    # IP-country MUST match ID-doc-issuing-country (geo-anchor)
    if geo["country_code"] != id_doc_country:
        await void_exam(candidate_id, exam_id, reason=f"ip_{geo['country_code']}_vs_id_{id_doc_country}_mismatch")
        raise HTTPException(status_code=403, detail="ip_id_doc_country_mismatch")

    # Pass: admit candidate to proctored session
    return {"admit": True, "candidate_id": candidate_id, "exam_id": exam_id, "ip_country": geo["country_code"], "asn": geo["asn"], "risk_score": geo.get("risk_score", 0)}

4. FERPA + GDPR Art. 9 + Schrems II LMS shard-routing (data-residency)

// /api/lms/route-shard.js — Node 20 / LMS session-init shard selector
// Routes student LMS-session to residency-appropriate shard; SCC-flag for cross-border.
const fetch = require('undici').fetch;

const SHARD_MAP = {
  EU_FRA: { region: 'eu-central-1', host: 'lms-eu-fra.example.com', dpa: 'gdpr_art28_scc', gdpr_protected: true },
  EU_AMS: { region: 'eu-west-1',    host: 'lms-eu-ams.example.com', dpa: 'gdpr_art28_scc', gdpr_protected: true },
  UK_LON: { region: 'uk-south-1',   host: 'lms-uk-lon.example.com', dpa: 'uk_gdpr_dpa2018', gdpr_protected: true },
  US_IAD: { region: 'us-east-1',    host: 'lms-us-iad.example.com', dpa: 'ferpa_34cfr99',  gdpr_protected: false },
  CA_YYZ: { region: 'ca-central-1', host: 'lms-ca-yyz.example.com', dpa: 'pipeda_bc_qc_ab', gdpr_protected: false },
  AU_SYD: { region: 'ap-southeast-2', host: 'lms-au-syd.example.com', dpa: 'privacy_act_1988_app8', gdpr_protected: false },
};

const EU_COUNTRIES = new Set(['DE','NL','FR','BE','LU','AT','DK','SE','FI','IE','ES','IT','PT','GR','PL','CZ','HU','SK','RO','BG','HR','SI','EE','LV','LT','CY','MT','IS','NO','LI']);

async function routeLMSShard(req, studentId) {
  const ip = req.headers['cf-connecting-ip'] || req.ip;
  let geo;
  try {
    geo = await (await fetch(`https://ipgeo.10b.app/v1/${ip}?fields=country_code,subdivision_iso_code,is_vpn,is_proxy,is_tor,asn`, {
      headers: { 'Authorization': `Bearer ${process.env.IPGEO_KEY}` },
      signal: AbortSignal.timeout(40)
    })).json();
  } catch {
    // Fail-conservative: EU_FRA (highest-protection default; FERPA-compatible via SCCs)
    return { shard: SHARD_MAP.EU_FRA, reason: 'geo_timeout_default_eu_fra_highest_protection', scc_required: true };
  }

  // VPN / proxy / Tor → EU_FRA highest-protection (cannot resolve residency)
  if (geo.is_vpn || geo.is_proxy || geo.is_tor) {
    return { shard: SHARD_MAP.EU_FRA, reason: 'anonymised_origin_eu_fra_highest_protection', scc_required: true };
  }

  // EU students → EU_FRA or EU_AMS (GDPR Art. 9 special-category)
  if (EU_COUNTRIES.has(geo.country_code)) {
    const shard = ['DE','AT','CH','LI','LU'].includes(geo.country_code) ? SHARD_MAP.EU_FRA : SHARD_MAP.EU_AMS;
    return { shard, reason: 'eu_resident_gdpr_art9', scc_required: false };
  }

  // UK students → UK_LON (UK GDPR + DPA 2018)
  if (geo.country_code === 'GB') return { shard: SHARD_MAP.UK_LON, reason: 'uk_resident_uk_gdpr', scc_required: false };

  // CA students → CA_YYZ (PIPEDA + BC/AB/QC residency)
  if (geo.country_code === 'CA') return { shard: SHARD_MAP.CA_YYZ, reason: 'ca_resident_pipeda', scc_required: false };

  // AU students → AU_SYD (Privacy Act 1988 APP 8 cross-border)
  if (geo.country_code === 'AU') return { shard: SHARD_MAP.AU_SYD, reason: 'au_resident_app8', scc_required: false };

  // US students → US_IAD (FERPA + state-AG SOPIPA/SHIELD/SOPPA)
  if (geo.country_code === 'US') return { shard: SHARD_MAP.US_IAD, reason: 'us_resident_ferpa', scc_required: false };

  // International / unmapped → EU_FRA highest-protection + SCC
  return { shard: SHARD_MAP.EU_FRA, reason: 'international_default_eu_fra_scc', scc_required: true };
}

Pricing math — when €99 Business tier pays for itself

A mid-market online-degree platform with ~10K monthly enrolment-page sessions + ~500 proctored-exam sessions has roughly this loss-surface:

€99 Business tier = €1188 / yr. Break-even = avoiding one licensure-mis-enrolment, one proctoring-fraud cycle, one shard-routing breach, or 15 PPP-arbitrage learners per year.

Honest limits — what IP geolocation cannot tell you

  1. Mobile-network CG-NAT region blur — 4G/5G carriers route subscriber traffic through CG-NAT pools that resolve to the carrier’s POP city, not the subscriber’s residence; this blurs SARA-vs-non-SARA distinctions for mobile-only enrolments. We expose is_mobile + carrier-ASN for mobile-vs-fixed-line classification, but for high-stakes licensure decisions, supplement with billing-address + ID-doc verification.
  2. iCloud Private Relay trust-but-verify — Apple Private Relay (~9% of EU iOS traffic in 2024) routes through Apple-Cloudflare egress that preserves country-of-origin but masks state/region; treat as is_relay=true but accept country-level claim for SARA-gate routing.
  3. Mobile-data IP rotation during exam — 4G/5G subscribers can hop egress IPs mid-exam-session; do not invalidate an admitted exam-session on IP-change alone. Pin the proctoring-session’s geo-anchor to the at-admit snapshot for the duration of the exam; flag mid-session country-change (not city/region-change) as a hard-fail-closed event.
  4. MaxMind is_anonymous_proxy deprecation — MaxMind sunset is_anonymous_proxy in 2024 Q3; if your existing proctoring-anti-cheat Sigma rules reference it, migrate to is_proxy + is_vpn + is_relay + is_hosting composite. We expose all four for forward-compatibility on the proctoring + admit-gate input contract.
  5. Geo-anchor is not absolute — accommodations + travel-during-program — a verified student with a documented disability accommodation may take a proctored exam from a non-residence country (e.g. medical-travel); always offer a documented-accommodation-attestation override at admit-time, route to manual proctor review, and skip the geo-mismatch hard-fail.

Use-cases that compose with online education

Every online-education stack composes 3-5 IP-layer use-cases. The relevant primary deep-dives:

Compare IP Geo API to the providers online-education teams evaluate

If you’re shortlisting vendors for an LMS rebuild, a proctoring-anti-cheat rollout, or a SARA-licensure-compliance 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 — online-education 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.