API Reference
ClassifAIly exposes a simple REST API that classifies any content — text, images, audio, or video — into your categories or generic labels. All requests require an API key and return JSON.
Make a request
POST to /api/v1/classify with your input and desired categories.
Get structured results
Receive a label, confidence score, and optional explanation in the response.
Authentication
All API requests must include your API key in the Authorization header as a Bearer token.
Authorization: Bearer cai_live_your_api_key_here
You can generate, name, and revoke keys at any time from the API Keys section of your dashboard. Revoking a key is immediate — any requests using it will return 401.
Base URL
https://classifaily.com/api/v1
All endpoints below are relative to this base. For example, POST /classify means POST https://classifaily.com/api/v1/classify.
Request format
Send all request bodies as JSON with the Content-Type: application/json header. Responses are always JSON.
Authorization: Bearer cai_live_...
Content-Type: application/json
Classify a single input. The core of the API. Supports text, image URL, audio URL, and video URL as inputs. Returns structured classification results with confidence scores.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
input | string | required | The content to classify. A text string, a public URL (image/audio/video), or a base64 data URI for images. |
type | string | optional | Input type: text, image, audio, video. Defaults to text. |
categories | array | optional | Inline category list. Strings (["finance","hr"]) or category objects. Omit for generic labels. Requires Starter+. |
schema | integer | optional | ID of a saved schema. Overrides categories if both are provided. Requires Starter+. |
mode | string | optional | How many results to return: single (default), multi, or top_n. See modes. |
top_n | integer | optional | Number of results for top_n mode. Default 3, max 10. |
threshold | float | optional | Minimum confidence (0.0–1.0). Results below this are excluded. Default 0.0 (return all). |
explain | boolean | optional | If true, include a short explanation for each classification. Default false. |
async | boolean | optional | If true, return a job_id immediately without waiting for results. Useful for large media files. Default false. |
Response
{
"id": "84",
"status": "completed",
"type": "text",
"result": {
"label": "finance",
"confidence": 0.94,
"explanation": "The text discusses quarterly server costs..." // only if explain: true
},
"meta": {
"model": "classifai-v1",
"latency_ms": 218,
"tokens": 124,
"mode": "single"
},
"created_at": "2025-03-15T10:30:00Z"
}
{
"id": "85",
"status": "completed",
"type": "text",
"result": {
"labels": [
{ "label": "finance", "confidence": 0.94 },
{ "label": "engineering", "confidence": 0.61 },
{ "label": "marketing", "confidence": 0.22 }
]
},
"meta": { "mode": "multi", "latency_ms": 231 }
}
// HTTP 202 Accepted
{
"id": "86",
"status": "pending"
}
// Poll GET /api/v1/jobs?id=86 or use a webhook to receive the result.
Examples
# Basic — inline categories
curl -X POST https://classifaily.com/api/v1/classify \
-H "Authorization: Bearer cai_live_..." \
-H "Content-Type: application/json" \
-d '{
"input": "Our server costs doubled again this quarter.",
"categories": ["finance", "engineering", "marketing", "support"]
}'
# With a saved schema + explanations
curl -X POST https://classifaily.com/api/v1/classify \
-H "Authorization: Bearer cai_live_..." \
-H "Content-Type: application/json" \
-d '{
"input": "Refund was never processed after 2 weeks.",
"schema": 42,
"explain": true
}'
# Generic — no categories
curl -X POST https://classifaily.com/api/v1/classify \
-H "Authorization: Bearer cai_live_..." \
-H "Content-Type: application/json" \
-d '{ "input": "The sunset over the lake was breathtaking." }'
# Image by URL
curl -X POST https://classifaily.com/api/v1/classify \
-H "Authorization: Bearer cai_live_..." \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/product-photo.jpg",
"type": "image",
"categories": ["apparel", "electronics", "furniture", "food"]
}'
import requests
API_KEY = "cai_live_..."
BASE = "https://classifaily.com/api/v1"
def classify(input_text, categories=None, schema=None, **kwargs):
payload = {"input": input_text, **kwargs}
if categories: payload["categories"] = categories
if schema: payload["schema"] = schema
resp = requests.post(
f"{BASE}/classify",
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload
)
resp.raise_for_status()
return resp.json()
# Simple classification
result = classify(
"Our server costs doubled this quarter.",
categories=["finance", "engineering", "marketing"]
)
print(result["result"]["label"]) # "finance"
print(result["result"]["confidence"]) # 0.94
# Top 3 with explanations
result = classify(
"Refund never processed after 2 weeks.",
categories=["billing", "technical", "account", "general"],
mode="top_n", top_n=3, explain=True
)
for label in result["result"]["labels"]:
print(label["label"], label["confidence"])
const API_KEY = 'cai_live_...';
const BASE = 'https://classifaily.com/api/v1';
async function classify(payload) {
const res = await fetch(`${BASE}/classify`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) throw await res.json();
return res.json();
}
// Simple
const { result } = await classify({
input: 'Our server costs doubled this quarter.',
categories: ['finance', 'engineering', 'marketing'],
});
console.log(result.label, result.confidence);
// Multi-label, threshold 0.5
const data = await classify({
input: 'Refund was never processed after 2 weeks.',
schema: 42,
mode: 'multi',
threshold: 0.5,
});
data.result.labels.forEach(l => console.log(l.label, l.confidence));
function classifai_classify(array $payload): array {
$ch = curl_init('https://classifaily.com/api/v1/classify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer cai_live_...',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
$result = classifai_classify([
'input' => 'Our server costs doubled this quarter.',
'categories' => ['finance', 'engineering', 'marketing'],
]);
echo $result['result']['label']; // finance
echo $result['result']['confidence']; // 0.94
Classify multiple inputs in a single API call. Each item can have its own input, type, and categories, or inherit shared settings from the batch-level fields. Each item in the batch counts as one request against your monthly limit.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
items | array | required | Array of input objects. Each may have id, input, type, and categories. |
items[].id | string | optional | Your own ID for this item — echoed back in results. Useful for matching results to your records. |
items[].input | string | required | Content to classify for this item. |
items[].type | string | optional | Overrides the batch-level type for this item. |
items[].categories | array | optional | Overrides the batch-level categories for this item. |
categories | array | optional | Default categories for all items. Overridden per-item if item has its own. |
schema | integer | optional | Default schema for all items. |
mode | string | optional | Same as /classify. Applied to all items. |
threshold | float | optional | Minimum confidence filter. Applied to all items. |
explain | boolean | optional | Include explanations for all items. |
Example
# Batch classify 3 support tickets at once
curl -X POST https://classifaily.com/api/v1/batch \
-H "Authorization: Bearer cai_live_..." \
-H "Content-Type: application/json" \
-d '{
"categories": ["billing", "technical", "account", "general"],
"mode": "single",
"items": [
{ "id": "ticket_1001", "input": "I was charged twice for my subscription." },
{ "id": "ticket_1002", "input": "The API keeps returning a 500 error." },
{ "id": "ticket_1003", "input": "How do I change my password?" }
]
}'
Response
{
"id": "batch_6612abc",
"status": "completed",
"results": [
{ "id": "ticket_1001", "status": "completed", "result": { "label": "billing", "confidence": 0.97 } },
{ "id": "ticket_1002", "status": "completed", "result": { "label": "technical", "confidence": 0.93 } },
{ "id": "ticket_1003", "status": "completed", "result": { "label": "account", "confidence": 0.88 } }
],
"meta": { "total": 3, "completed": 3, "failed": 0, "latency_ms": 412 },
"created_at": "2025-03-15T10:30:00Z"
}
Schemas
Schemas let you save a named category set and reuse it across requests with a single schema ID. When your classification taxonomy changes, update the schema once instead of updating every request.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | Human-readable schema name (e.g. "Support Ticket Topics"). |
categories | array | required | Array of strings or category objects. |
description | string | optional | Internal note about what this schema is for. |
type | string | optional | Input type this schema is designed for: text, image, audio, video, any. Default any. |
mode | string | optional | Default classification mode for this schema: single or multi. Default single. |
Example: rich category objects
curl -X POST https://classifaily.com/api/v1/schemas \
-H "Authorization: Bearer cai_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Support Ticket Topics",
"description": "Routes incoming support tickets to the right team",
"type": "text",
"mode": "single",
"categories": [
{
"name": "billing",
"description": "Payments, refunds, invoices, subscription charges",
"examples": ["I was charged twice", "my refund hasnt arrived", "cancel my subscription"]
},
{
"name": "technical",
"description": "Bugs, errors, API issues, performance problems",
"examples": ["getting a 500 error", "the API is slow", "login is broken"]
},
{
"name": "account",
"description": "Profile, password, permissions, 2FA",
"examples": ["forgot my password", "change my email", "enable two-factor"]
},
{
"name": "general",
"description": "Everything else"
}
]
}'
Response
{
"id": 42,
"name": "Support Ticket Topics",
"categories": [ ... ],
"type": "text",
"mode": "single",
"total_uses": 0,
"created_at": "2025-03-15T10:30:00Z"
}
Jobs
Every call to /classify and /batch creates a job record. Poll for status or retrieve full results via the jobs endpoint.
Query parameters
| Param | Type | Description |
|---|---|---|
id | integer | Return a single job by ID. |
limit | integer | Number of results (default 20, max 100). |
offset | integer | Pagination offset. |
status | string | Filter by pending, processing, completed, failed. |
input_type | string | Filter by text, image, audio, video. |
Job object
{
"id": "84",
"status": "completed", // pending | processing | completed | failed
"input_type": "text",
"input_preview": "Our server costs doubled...",
"result": { "label": "finance", "confidence": 0.94 },
"categories": [ "finance", "engineering", "marketing" ],
"schema_id": null,
"model_used": "classifai-v1",
"tokens_used": 124,
"latency_ms": 218,
"created_at": "2025-03-15T10:30:00Z",
"completed_at": "2025-03-15T10:30:00Z"
}
Returns current usage against your plan limits.
Query parameters
| Param | Type | Description |
|---|---|---|
month | string | Month in YYYY-MM format. Defaults to current month. |
curl https://classifaily.com/api/v1/usage \
-H "Authorization: Bearer cai_live_..."
// Response
{
"period": "2025-03",
"plan": "starter",
"limits": {
"requests": 10000,
"resets_at": "2025-04-01T00:00:00Z",
"media": true,
"schemas": 10,
"batch_size": 50
},
"usage": {
"requests": 3421,
"remaining": 6579,
"by_type": { "text": 3200, "image": 221, "audio": 0, "video": 0 },
"tokens": 891234
}
}
Webhooks Pro
Register HTTPS endpoints to receive job results in real time instead of polling. ClassifAIly sends a POST with a JSON payload when a job event fires.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | required | Your HTTPS endpoint. Must be publicly accessible. |
events | array | optional | Events to subscribe to. ["*"] (all), job.completed, job.failed, batch.completed. Defaults to ["*"]. |
secret | string | optional | HMAC signing secret. If omitted, one is generated. Store it — it won't be shown again. |
Verifying signatures
Every delivery includes an X-ClassifAIly-Signature header. Verify it to ensure the request came from ClassifAIly and was not tampered with.
# PHP
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CLASSIFAI_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $YOUR_WEBHOOK_SECRET);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// handle $event['event'] and $event['result']
# Python
import hmac, hashlib
expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers['X-ClassifAIly-Signature']):
abort(401)
Webhook payload
{
"event": "job.completed",
"id": "84",
"result": { "label": "finance", "confidence": 0.94 },
"type": "text",
"schema": 42,
"timestamp": "2025-03-15T10:30:00Z"
}
Category objects
Categories can be plain strings or rich objects. Rich objects give the model more context and significantly improve accuracy, especially for similar or ambiguous categories.
"categories": [
"billing",
"technical",
"account",
"general"
]
"categories": [
{
"name": "billing",
"description": "Payments, refunds, invoices",
"examples": ["charged twice", "refund not received"]
},
...
]
Category object fields
| Field | Type | Description |
|---|---|---|
name | string | required. The category label returned in results. |
description | string | Optional. Prose description of what this category means. Especially valuable when category names alone are ambiguous. |
examples | string[] | Optional. Sample inputs that belong to this category. The more specific, the better. |
Classification modes
| Mode | Result shape | Use when |
|---|---|---|
single |
{ "label": "finance", "confidence": 0.94 } |
You want exactly one answer. Best for routing, tagging, and bucketing. Default. |
multi |
{ "labels": [ {label, confidence}, ... ] } |
An input can belong to multiple categories (e.g. content tagging). Returns all categories above your threshold. |
top_n |
{ "labels": [ {label, confidence}, ... ] } |
You want the top N results ranked by confidence. Set top_n to control how many (default 3, max 10). |
Errors
All errors return a JSON body with an error object. HTTP status codes follow standard conventions.
{
"error": {
"code": "rate_limit_exceeded",
"message": "Monthly limit of 100 requests reached.",
"docs": "https://classifaily.com/docs.php#errors"
}
}
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_json | Request body is not valid JSON or Content-Type is not set. |
| 400 | invalid_request | A required field is missing or a value is invalid. See message for details. |
| 401 | unauthorized | API key missing, malformed, or revoked. |
| 403 | subscription_inactive | Paid plan subscription has lapsed. |
| 403 | plan_restriction | The feature (media, schemas, webhooks) is not available on your current plan. |
| 404 | schema_not_found | The requested schema ID does not exist or belongs to another account. |
| 405 | method_not_allowed | Wrong HTTP method for this endpoint. |
| 429 | rate_limit_exceeded | Monthly request quota exhausted. Includes limit, used, and resets_at fields. |
| 500 | server_error | Unexpected internal error. Retry with exponential backoff. |
Handling errors in code
# Python
resp = requests.post(url, headers=headers, json=payload)
if not resp.ok:
err = resp.json()["error"]
if err["code"] == "rate_limit_exceeded":
print(f"Quota hit. Resets at {err['resets_at']}")
elif err["code"] == "plan_restriction":
print("Upgrade your plan to use this feature.")
else:
raise Exception(err["message"])
# Node.js
if (!res.ok) {
const { error } = await res.json();
if (error.code === 'rate_limit_exceeded') throw new Error(`Quota hit, resets ${error.resets_at}`);
throw new Error(error.message);
}
Rate limits
Limits reset on the 1st of each calendar month at midnight UTC. Each item in a batch request counts as one request against your limit.
| Plan | Requests / month | Batch size |
|---|---|---|
| Free | 100 | 1 item |
| Starter | 10,000 | 50 items |
| Pro | 100,000 | 500 items |
| Enterprise | Unlimited | 500 items |
When you approach your limit, use GET /api/v1/usage programmatically to monitor and alert before you hit the cap.
Plan comparison
| Feature | Free | Starter | Pro | Enterprise |
|---|---|---|---|---|
| Requests / month | 100 | 10,000 | 100,000 | Unlimited |
| Text classification | ✓ | ✓ | ✓ | ✓ |
| Image classification | — | ✓ | ✓ | ✓ |
| Audio classification | — | — | ✓ | ✓ |
| Video classification | — | — | ✓ | ✓ |
| Custom categories (inline) | — | ✓ | ✓ | ✓ |
| Saved schemas | — | 10 | Unlimited | Unlimited |
| API keys | 1 | 3 | 10 | Unlimited |
| Batch requests | 1 item | 50 items | 500 items | 500 items |
| Webhooks | — | — | ✓ (10 max) | ✓ |
| Job history | 7 days | 30 days | 1 year | Custom |
| explain: true | ✓ | ✓ | ✓ | ✓ |
| Async jobs | ✓ | ✓ | ✓ | ✓ |