Skip to main content

Text Classification API

The classify endpoint sorts a piece of text into labels you define in the request — no training data, no per-customer model. Send the text together with 2–20 label names (optionally with a short description of each), and the endpoint returns the best-matching label, a score for every label, and a one-sentence rationale pointing at the evidence in the text.

Two modes are supported:

  • Single-label (the default): exactly one label applies. The scores form a probability distribution over your labels (they sum to ~1) and label is the top one.
  • Multi-label (multi_label: true): zero, one or several labels may apply at once. Each label gets an independent 0–1 score, and labels lists every label at or above a threshold you control.

It is built for support-ticket intent routing, topic tagging, lead qualification, content categorization and feedback bucketing. For fixed category sets, see the sentiment and tone endpoints.

Try it out

Sample Code

curl -X POST https://api.sapling.ai/api/v1/classify \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "text":"Hi, I was charged twice for my March invoice. Can you refund the duplicate payment?", "labels": ["billing", "technical issue", "shipping", "other"], "context": "Support tickets for a SaaS billing product"}'

Sample Response

{
"label": "billing",
"labels": ["billing"],
"scores": [
{"label": "billing", "score": 0.86},
{"label": "technical issue", "score": 0.07},
{"label": "shipping", "score": 0.04},
{"label": "other", "score": 0.03}
],
"rationale": "The customer reports being charged twice on one invoice.",
"multi_label": false
}

With "multi_label": true and the text "I was charged twice and the app crashes on launch", several labels can apply at once and the scores no longer need to sum to 1:

{
"label": "billing",
"labels": ["billing", "technical issue"],
"scores": [
{"label": "billing", "score": 0.92},
{"label": "technical issue", "score": 0.81},
{"label": "shipping", "score": 0.03},
{"label": "other", "score": 0.02}
],
"rationale": "Mentions a duplicate charge and an app crash.",
"multi_label": true
}

Batch Requests

To classify many short texts — a queue of support tickets, a page of reviews — in one request, send texts (a list of 1–10 strings) instead of text. The same labels, multi_label, threshold and context apply to every item, and the combined length of all items may be up to 10,000 characters (the same cap as a single text). Exactly one of text and texts must be provided.

curl -X POST https://api.sapling.ai/api/v1/classify \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "texts": ["I was charged twice this month.", "Where is my package?"], "labels": ["billing", "shipping", "other"]}'

The batch response is {"results": [...]} with one entry per input, in input order; each entry has exactly the single-response shape above:

{
"results": [
{
"label": "billing",
"labels": ["billing"],
"scores": [
{"label": "billing", "score": 0.91},
{"label": "shipping", "score": 0.05},
{"label": "other", "score": 0.04}
],
"rationale": "Reports a duplicate charge.",
"multi_label": false
},
{
"label": "shipping",
"labels": ["shipping"],
"scores": [
{"label": "shipping", "score": 0.93},
{"label": "other", "score": 0.04},
{"label": "billing", "score": 0.03}
],
"rationale": "Asks about a package's whereabouts.",
"multi_label": false
}
]
}

Items are billed individually (a batch of N costs the same as N single requests), and each item is cached the same way as a single request — batches and single calls share the cache. If any item fails to classify, the whole request returns a 502 and nothing is billed; the items that did succeed are already cached, so a retry only re-runs the failures.

Request Parameters

POST to https://api.sapling.ai/api/v1/classify

key: String
32-character API key. Can also be supplied via the Authorization header as a bearer token; if both are provided, the key parameter takes precedence.

text: String
The text to classify, up to 10,000 characters (measured on the raw input, before any HTML is stripped). Plain text or HTML — tags are stripped server-side. Text that is empty after stripping returns a 400. The text is treated as untrusted data: instructions inside it are ignored and it is classified on its content alone. Provide exactly one of text and texts.

texts: List[String]
Batch form (see Batch Requests): 1–10 texts to classify in one request, each treated exactly like text above, with a combined length of up to 10,000 characters. An item that is empty after stripping returns a 400 naming its index. The response becomes {"results": [...]}, one entry per item in input order.

labels: List[String | Object]
The set of labels to choose from: between 2 and 20 entries. Each entry is either a string (the label name) or an object {"name": "...", "description": "..."} where description is optional. Names are trimmed, internal whitespace is collapsed, and they must be at most 50 characters and case-insensitively unique; descriptions are at most 200 characters. Names are returned in this normalized form in the response, so match your application logic on the normalized names. A short description per label sharpens borderline decisions — see Tips.

multi_label: Boolean, optional, defaults to false
false: exactly one label applies — scores are a probability distribution over the labels and labels is [label]. true: zero, one or several labels may apply — each label gets an independent 0–1 score and labels contains every label whose score is at least threshold. Non-boolean values return a 400.

threshold: Number, optional, defaults to 0.5
Multi-label cut-off between 0 and 1: a label is included in labels when score >= threshold. Ignored in single-label mode. Values outside 0–1 (or non-numbers) return a 400.

context: String, optional
Up to 500 characters describing what the texts are and how to decide between labels, for example "Support tickets for a SaaS billing product; pick billing for anything about charges or refunds". Guidance only — it steers the decision but can never add a label outside labels.

Response Parameters

label: String
The best-matching label. Always one of the submitted label names (as normalized: trimmed, internal whitespace collapsed).

labels: List[String]
Single-label mode: [label]. Multi-label mode: every label whose score is at least threshold, in descending score order — this may be empty, which means none of the labels apply.

scores: List[Object]
Exactly one entry per submitted label, sorted by descending score (ties keep the model's pick first, then input order). Each has:

  • label: the label name.
  • score: 0–1, rounded to 4 decimals. In single-label mode the scores sum to ~1 (a probability distribution over the labels); in multi-label mode each is an independent applicability probability and they need not sum to 1.

rationale: String
One sentence (at most 300 characters) naming the evidence in the text that drove the decision. May be an empty string.

multi_label: Boolean
Echo of the mode used for this request.

Errors

StatusMeaning
400Validation error — missing or oversized text, text empty after stripping, both or neither of text/texts provided, an invalid or over-length texts batch, or invalid labels (e.g. {"msg": "Invalid labels: Needs between 2 and 20 labels."}), multi_label, threshold or context. The body is {"msg": "..."}.
401 / 403Missing or invalid API key.
429Rate limit exceeded or key over capacity.
502{"msg": "Unexpected error classifying text."} — the classification model failed; safe to retry. Not billed and not cached.

Tips

  • Describe your labels. Plain names work, but a one-line description per label (what belongs in it, and what does not) noticeably improves borderline cases, especially when labels overlap (billing vs account) or are domain-specific jargon.
  • Add an "other" label. In single-label mode the endpoint always picks one of your labels, so include a catch-all (other, unrelated, none of the above) unless every text is guaranteed to fit one of your categories.
  • Use context to say what the texts are and how to decide — e.g. "Inbound sales emails; pick qualified only when a budget or timeline is mentioned". This is usually the cheapest way to fix systematic mistakes.
  • Multi-label for tagging, single-label for routing. Use multi_label: true when a text can legitimately belong to several buckets (topic tags, feature areas) and false when you need exactly one decision (queue assignment, intent).
  • Tune threshold from the scores. Start at the default 0.5; lower it to recall more tags, raise it for precision. Results are cached on Sapling's side for about three days keyed on the text, labels, mode and context — the threshold is applied on top, so re-running with a different threshold returns the same underlying scores.
  • Scores are calibrated guidance, not guarantees — for high-stakes automation, act on high-confidence results and route low-margin ones (top two scores close together) to a human.