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.

1

Get your API key

Generate a key from your dashboard under API Keys.

2

Make a request

POST to /api/v1/classify with your input and desired categories.

3

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.

HTTP Header
Authorization: Bearer cai_live_your_api_key_here
Keep your key secret. Never expose it in client-side JavaScript or commit it to source control. Use environment variables.

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.

Required headers
Authorization: Bearer cai_live_...
Content-Type: application/json
POST /api/v1/classify

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

FieldTypeRequiredDescription
inputstringrequiredThe content to classify. A text string, a public URL (image/audio/video), or a base64 data URI for images.
typestringoptionalInput type: text, image, audio, video. Defaults to text.
categoriesarrayoptionalInline category list. Strings (["finance","hr"]) or category objects. Omit for generic labels. Requires Starter+.
schemaintegeroptionalID of a saved schema. Overrides categories if both are provided. Requires Starter+.
modestringoptionalHow many results to return: single (default), multi, or top_n. See modes.
top_nintegeroptionalNumber of results for top_n mode. Default 3, max 10.
thresholdfloatoptionalMinimum confidence (0.0–1.0). Results below this are excluded. Default 0.0 (return all).
explainbooleanoptionalIf true, include a short explanation for each classification. Default false.
asyncbooleanoptionalIf 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
POST /api/v1/batch

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.

Batch limits by plan: Free — 1 item, Starter — 50 items, Pro — 500 items per request.

Request body

FieldTypeRequiredDescription
itemsarrayrequiredArray of input objects. Each may have id, input, type, and categories.
items[].idstringoptionalYour own ID for this item — echoed back in results. Useful for matching results to your records.
items[].inputstringrequiredContent to classify for this item.
items[].typestringoptionalOverrides the batch-level type for this item.
items[].categoriesarrayoptionalOverrides the batch-level categories for this item.
categoriesarrayoptionalDefault categories for all items. Overridden per-item if item has its own.
schemaintegeroptionalDefault schema for all items.
modestringoptionalSame as /classify. Applied to all items.
thresholdfloatoptionalMinimum confidence filter. Applied to all items.
explainbooleanoptionalInclude 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.

Schemas require a Starter plan or higher.
POST /api/v1/schemas
FieldTypeRequiredDescription
namestringrequiredHuman-readable schema name (e.g. "Support Ticket Topics").
categoriesarrayrequiredArray of strings or category objects.
descriptionstringoptionalInternal note about what this schema is for.
typestringoptionalInput type this schema is designed for: text, image, audio, video, any. Default any.
modestringoptionalDefault 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"
}
GET /api/v1/schemas List all schemas
GET /api/v1/schemas?id=42 Get single schema
DELETE /api/v1/schemas?id=42 Soft-delete a schema. Existing jobs retain their category snapshot.

Jobs

Every call to /classify and /batch creates a job record. Poll for status or retrieve full results via the jobs endpoint.

GET /api/v1/jobs?id=84 Get a single job by ID
GET /api/v1/jobs List recent jobs

Query parameters

ParamTypeDescription
idintegerReturn a single job by ID.
limitintegerNumber of results (default 20, max 100).
offsetintegerPagination offset.
statusstringFilter by pending, processing, completed, failed.
input_typestringFilter 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"
}
GET /api/v1/usage

Returns current usage against your plan limits.

Query parameters

ParamTypeDescription
monthstringMonth 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.

Webhooks require a Pro plan. Maximum 10 webhooks per account.
POST /api/v1/webhooks
FieldTypeRequiredDescription
urlstringrequiredYour HTTPS endpoint. Must be publicly accessible.
eventsarrayoptionalEvents to subscribe to. ["*"] (all), job.completed, job.failed, batch.completed. Defaults to ["*"].
secretstringoptionalHMAC 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"
}
GET /api/v1/webhooks List all webhooks
DELETE /api/v1/webhooks?id=1 Delete a webhook

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.

Simple (strings)
"categories": [
  "billing",
  "technical",
  "account",
  "general"
]
Rich (objects)
"categories": [
  {
    "name": "billing",
    "description": "Payments, refunds, invoices",
    "examples": ["charged twice", "refund not received"]
  },
  ...
]

Category object fields

FieldTypeDescription
namestringrequired. The category label returned in results.
descriptionstringOptional. Prose description of what this category means. Especially valuable when category names alone are ambiguous.
examplesstring[]Optional. Sample inputs that belong to this category. The more specific, the better.

Classification modes

ModeResult shapeUse 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"
  }
}
HTTPCodeMeaning
400invalid_jsonRequest body is not valid JSON or Content-Type is not set.
400invalid_requestA required field is missing or a value is invalid. See message for details.
401unauthorizedAPI key missing, malformed, or revoked.
403subscription_inactivePaid plan subscription has lapsed.
403plan_restrictionThe feature (media, schemas, webhooks) is not available on your current plan.
404schema_not_foundThe requested schema ID does not exist or belongs to another account.
405method_not_allowedWrong HTTP method for this endpoint.
429rate_limit_exceededMonthly request quota exhausted. Includes limit, used, and resets_at fields.
500server_errorUnexpected 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.

PlanRequests / monthBatch size
Free1001 item
Starter10,00050 items
Pro100,000500 items
EnterpriseUnlimited500 items

When you approach your limit, use GET /api/v1/usage programmatically to monitor and alert before you hit the cap.

Plan comparison

FeatureFreeStarterProEnterprise
Requests / month10010,000100,000Unlimited
Text classification
Image classification
Audio classification
Video classification
Custom categories (inline)
Saved schemas10UnlimitedUnlimited
API keys1310Unlimited
Batch requests1 item50 items500 items500 items
Webhooks✓ (10 max)
Job history7 days30 days1 yearCustom
explain: true
Async jobs

View pricing →