Lipi.ai API Documentation
Identify fonts from images programmatically. Version 3.
Base URL
https://api.lipi.ai/v3
Authentication
All requests require an API key passed in the x-api-key header.
curl https://api.lipi.ai/v3/credits \ -H "x-api-key: lpi_your_api_key_here"
Never expose your API key in client-side code or public repositories. Use environment variables.
Endpoints
/v3/font-matchSubmit image for font identification1 credit/v3/font-match/:idPoll job status and results/v3/creditsCheck credit balance/v3/usageUsage history (paginated)POST /v3/font-match
Submit an image containing text for AI-powered font identification. The image is analyzed asynchronously — poll the returned job_id for results.
Images larger than 1536px on either dimension are automatically resized (aspect ratio preserved). Max payload: 10MB.
Request
curl -X POST https://api.lipi.ai/v3/font-match \
-H "x-api-key: lpi_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"image": "data:image/png;base64,iVBORw0KGgo..."
}'Response (201)
{
"job_id": "6c301b7d-e91b-4d8c-bbb0-7e67bc6c5548",
"status": "pending",
"credits_charged": 1,
"credits_remaining": 99,
"free_credits_remaining": 7,
"poll_url": "/v3/font-match/6c301b7d-e91b-4d8c-bbb0-7e67bc6c5548"
}GET /v3/font-match/:id
Poll for job completion. Typical processing time is 10-20 seconds. Poll every 2 seconds.
Response (running)
{
"job_id": "6c301b7d-e91b-4d8c-bbb0-7e67bc6c5548",
"status": "running",
"progress": 20,
"stage": "analyzing",
"created_at": "2026-04-12T10:30:00Z",
"completed_at": null
}Response (succeeded)
{
"job_id": "6c301b7d-e91b-4d8c-bbb0-7e67bc6c5548",
"status": "succeeded",
"progress": 100,
"stage": "completed",
"results": [
{
"text": "Hello World",
"reasoning": "The letterforms show geometric sans-serif characteristics with uniform stroke width and circular bowl shapes...",
"font_match": {
"most_likely_commercial": "Futura Bold",
"close_commercial_alternatives": [
"Century Gothic Bold",
"Avenir Heavy"
],
"closest_free_alternatives": [
"Jost Bold",
"Nunito Sans Bold"
]
}
}
],
"created_at": "2026-04-12T10:30:00Z",
"completed_at": "2026-04-12T10:30:18Z"
}Response (failed)
{
"job_id": "6c301b7d-e91b-4d8c-bbb0-7e67bc6c5548",
"status": "failed",
"progress": 0,
"stage": "error",
"error": "Font matching failed",
"created_at": "2026-04-12T10:30:00Z",
"completed_at": "2026-04-12T10:30:05Z"
}Credits are automatically refunded for failed jobs.
GET /v3/credits
{
"free_credits_remaining": 7,
"free_credits_reset_date": "2026-05-01",
"paid_credits_remaining": 93,
"subscription_status": "active",
"total_requests": 10,
"rate_limit_rpm": 60
}GET /v3/usage
Query params: limit (max 100),cursor,from (ISO date),to (ISO date).
{
"usage": [
{
"timestamp": "2026-04-12T10:30:00Z",
"endpoint": "font-match",
"credits_charged": 1,
"credit_source": "free",
"status_code": 201,
"job_id": "6c301b7d-e91b-4d8c-bbb0-7e67bc6c5548"
}
],
"pagination": {
"next_cursor": "eyJ0aW1lc3Rh...",
"has_more": true
},
"summary": {
"total_requests_shown": 1,
"total_credits_shown": 1,
"period": {
"from": null,
"to": null
}
}
}Error Codes
| Status | Error Code | Description |
|---|---|---|
| 401 | invalid_api_key | Missing or invalid API key |
| 402 | insufficient_credits | No credits remaining |
| 403 | key_suspended | API key has been suspended |
| 404 | not_found | Job not found or not owned by this key |
| 413 | payload_too_large | Image exceeds 10MB |
| 422 | invalid_image | Not a valid PNG, JPEG, or WebP image |
| 429 | rate_limited | Exceeded rate limit (see retry_after_seconds) |
| 503 | service_busy | High 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 font-match request.
Code Examples
Python
import requests
import base64
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"}
# Read image and convert to base64
with open("sample.png", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
# Submit job
resp = requests.post(f"{BASE}/font-match", json={
"image": f"data:image/png;base64,{b64}"
}, headers=HEADERS)
job = resp.json()
print(f"Job {job['job_id']} created. Credits remaining: {job['credits_remaining']}")
# Poll for results
while True:
time.sleep(2)
status = requests.get(f"{BASE}/font-match/{job['job_id']}", headers=HEADERS).json()
if status["status"] == "succeeded":
for result in status["results"]:
print(f"Text: {result['text']}")
print(f"Match: {result['font_match']['most_likely_commercial']}")
break
elif status["status"] == "failed":
print(f"Failed: {status.get('error')}")
breakNode.js
const fs = require('fs');
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 matchFont(imagePath) {
const b64 = fs.readFileSync(imagePath, 'base64');
// Submit job
const res = await fetch(`${BASE}/font-match`, {
method: 'POST',
headers,
body: JSON.stringify({ image: `data:image/png;base64,${b64}` })
});
const job = await res.json();
console.log(`Job ${job.job_id} created`);
// Poll
while (true) {
await new Promise(r => setTimeout(r, 2000));
const status = await fetch(`${BASE}/font-match/${job.job_id}`, { headers }).then(r => r.json());
if (status.status === 'succeeded') return status.results;
if (status.status === 'failed') throw new Error(status.error);
}
}
matchFont('sample.png').then(results => {
results.forEach(r => {
console.log(`"${r.text}" → ${r.font_match.most_likely_commercial}`);
});
});