Developer Dashboard

URL Font Scanner API

Scan any web page for fonts and get a licensing compliance report. Version 3.

Base URL

https://api.lipi.ai/v3

Postman Collection

Authentication

All requests require an API key passed in the x-api-key header. Same API key as the Font Match API.

curl https://api.lipi.ai/v3/credits \
  -H "x-api-key: lpi_your_api_key_here"

Endpoints

POST/v3/url-scanSubmit URL for font compliance scanning1 credit
GET/v3/url-scan/:idPoll scan status and results
GET/v3/creditsCheck credit balance
GET/v3/usageUsage history (paginated)
How it works

A headless browser navigates to your URL, detects all fonts on the page, and cross-references each against our license database. Processing takes 30-60 seconds. Cached results (7-day TTL) return instantly. Duplicate in-progress scans return the existing job without charging.

POST /v3/url-scan

Submit a URL for font detection and license compliance analysis.

Request

curl -X POST https://api.lipi.ai/v3/url-scan \
  -H "x-api-key: lpi_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://stripe.com"
  }'

Response (201)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "pending",
  "url": "https://stripe.com",
  "credits_charged": 1,
  "credits_remaining": 99,
  "free_credits_remaining": 7,
  "poll_url": "/v3/url-scan/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Response (cached)

If the same URL was scanned in the last 7 days, the cached result is returned immediately. Still costs 1 credit.

{
  "cached": true,
  "url": "https://stripe.com",
  "credits_charged": 1,
  "scanned_at": "2026-04-12T10:30:00Z",
  "page_title": "Stripe | Financial Infrastructure",
  "fonts_detected": [
    { "family": "Inter", "weights": ["400", "500", "700"], "source": "google_fonts" }
  ],
  "license_results": [
    {
      "font_name": "Inter",
      "risk_level": "low",
      "license_type": "SIL Open Font License",
      "commercial_use": true
    }
  ],
  "compliance_summary": {
    "total_fonts": 1,
    "low_risk": 1,
    "medium_risk": 0,
    "high_risk": 0,
    "unknown_risk": 0,
    "overall_score": 100,
    "overall_risk": "low"
  }
}

GET /v3/url-scan/:id

Poll for scan completion. Poll every 3 seconds. Processing typically takes 30-60 seconds.

Response (running)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "running",
  "progress": 60,
  "stage": "checking_licenses",
  "url": "https://stripe.com",
  "created_at": "2026-04-12T10:30:00Z",
  "completed_at": null
}

Response (succeeded)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "succeeded",
  "progress": 100,
  "stage": "completed",
  "url": "https://stripe.com",
  "page_title": "Stripe | Financial Infrastructure",
  "fonts_detected": [
    { "family": "Inter", "weights": ["400", "500", "700"], "source": "google_fonts" },
    { "family": "Söhne", "weights": ["400"], "source": "stylesheet" }
  ],
  "license_results": [
    {
      "font_name": "Inter",
      "risk_level": "low",
      "license_type": "SIL Open Font License",
      "commercial_use": true
    },
    {
      "font_name": "Söhne",
      "risk_level": "low",
      "license_type": "Commercial (Klim Type Foundry)",
      "commercial_use": true
    }
  ],
  "compliance_summary": {
    "total_fonts": 2,
    "low_risk": 2,
    "medium_risk": 0,
    "high_risk": 0,
    "unknown_risk": 0,
    "overall_score": 100,
    "overall_risk": "low"
  },
  "created_at": "2026-04-12T10:30:00Z",
  "completed_at": "2026-04-12T10:30:48Z"
}

Response (failed)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "failed",
  "error": "Scan timed out after 80 seconds",
  "url": "https://stripe.com",
  "created_at": "2026-04-12T10:30:00Z",
  "completed_at": "2026-04-12T10:31:20Z"
}

Credits are automatically refunded for failed scans.

Error Codes

StatusError CodeDescription
400missing_urlNo URL provided in request body
401invalid_api_keyMissing or invalid API key
402insufficient_creditsNo credits remaining
403key_suspendedAPI key has been suspended
404not_foundJob not found or not owned by this key
422invalid_urlInvalid URL, private IP, or non-HTTP scheme
429rate_limitedExceeded rate limit (see retry_after_seconds)
503service_busyHigh demand, retry shortly

Rate Limits

Per Key

60 req/min

Response Header

429 responses include retry_after_seconds

Pricing

Free Tier

10 credits/month

Resets monthly. No card required.

Subscription

$75/month

100 credits. Unused credits roll over. Overage: $75/100 credits.

1 credit = 1 url-scan or 1 font-match request. Credits are shared across both APIs.

Code Examples

Python

import requests
import time

API_KEY = "lpi_your_api_key_here"
BASE = "https://api.lipi.ai/v3"
HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"}

# Submit scan
resp = requests.post(f"{BASE}/url-scan", json={
    "url": "https://stripe.com"
}, headers=HEADERS)
data = resp.json()

# If cached, results are already here
if data.get("cached"):
    print(f"Cached result from {data['scanned_at']}")
    for font in data["fonts_detected"]:
        print(f"  {font['family']}")
    print(f"Compliance score: {data['compliance_summary']['overall_score']}%")
else:
    # Poll for results
    job_id = data["job_id"]
    print(f"Job {job_id} created. Polling...")

    while True:
        time.sleep(3)
        status = requests.get(f"{BASE}/url-scan/{job_id}", headers=HEADERS).json()
        if status["status"] == "succeeded":
            for font in status["fonts_detected"]:
                print(f"  {font['family']} — {font.get('source', 'unknown')}")
            print(f"Compliance: {status['compliance_summary']['overall_risk']}")
            break
        elif status["status"] == "failed":
            print(f"Failed: {status.get('error')}")
            break
        else:
            print(f"  {status['progress']}% — {status.get('stage', '')}")

Node.js

const API_KEY = 'lpi_your_api_key_here';
const BASE = 'https://api.lipi.ai/v3';
const headers = { 'x-api-key': API_KEY, 'Content-Type': 'application/json' };

async function scanUrl(url) {
  const res = await fetch(`${BASE}/url-scan`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ url })
  });
  const data = await res.json();

  // Cached result — return immediately
  if (data.cached) return data;

  // Poll for results
  while (true) {
    await new Promise(r => setTimeout(r, 3000));
    const status = await fetch(`${BASE}/url-scan/${data.job_id}`, { headers }).then(r => r.json());
    if (status.status === 'succeeded') return status;
    if (status.status === 'failed') throw new Error(status.error);
  }
}

scanUrl('https://stripe.com').then(result => {
  console.log(`Fonts: ${result.fonts_detected.length}`);
  console.log(`Risk: ${result.compliance_summary.overall_risk}`);
  console.log(`Score: ${result.compliance_summary.overall_score}%`);
});

Ready to start?

Apply for API access and get 10 free credits.

Get API Key