Labels
include=labels and label= add small, structured judgments to every row a list endpoint returns: is this comment a question, is it a complaint, how positive is it, is the person asking to buy. You keep the endpoint's normal answer and get the judgments next to each row, in computed.labels.
Labels are signals, not verdicts. Each one is a level or a probability with a score, and you pick your own cut-off. Nothing else in https://api.onekey.dev is produced by a language model; this feature, relevance and themes are the only places one is involved, and the response says which method produced them.
When to use it#
- You pull the comments or replies under a post and want to find the questions, the complaints and the buying intent without reading them all.
- You want a sentiment number per row to sort or chart, and you are happy to choose the threshold yourself.
- You can live with a judgment being missing on a row: a row that could not be judged says so with
nulland is never guessed.
Labels are extra work on top of the endpoint, so they are metered separately. See AI pricing.
The request#
Add include=labels to any endpoint that accepts it. This judges every comment under a Hacker News story with that endpoint's default presets:
curl "https://api.onekey.dev/v1/hackernews/item_comments?id=8863&include=labels" \
-H "x-api-key: YOUR_API_KEY"To choose the presets yourself, name them with label=. It implies include=labels, and fewer presets cost less:
curl "https://api.onekey.dev/v1/hackernews/item_comments?id=8863&label=question,complaint" \
-H "x-api-key: YOUR_API_KEY"| Parameter | Values | What it does |
|---|---|---|
include=labels | flag | Judge every row with the endpoint's default presets (below). |
label=sentiment,question | comma list, at most 5 | Judge exactly these presets. Implies include=labels. An unknown name is a free 422 INVALID_LABEL. |
strict=true | true (default) or false | What happens when nothing at all could be judged. See When nothing could be judged. |
These combine with the other enrichment parameters on the same call: relevance= and themes=.
The presets#
| Preset | Value per row | Reads as |
|---|---|---|
sentiment | {level, score_0_1, confidence} | level is 1 (very negative) to 5 (very positive); score_0_1 is the same on a 0 to 1 scale. |
question | {p} | Probability the text is asking a question. |
purchase_intent | {p} | Probability the person is looking to buy or asking where to buy. |
complaint | {p} | Probability the text is a complaint. |
issue | {topic, confidence} | The main problem raised. topic is one of shipping, pricing, billing, quality, support, usability, performance, content, account, other or none. |
p and confidence are numbers from 0 to 1. There is never a bare is_complaint: true: you decide where the line is.
Defaults by row kind#
Without label=, the presets depend on what a row is:
| Rows are | Default presets |
|---|---|
| Comments | sentiment, question, complaint, issue |
| Posts | sentiment, question, purchase_intent |
| Reviews | sentiment, complaint, issue, purchase_intent |
| Search results | sentiment, question |
Which endpoints accept it#
31 list endpoints on 11 platforms accept enrichment today, plus multi-platform search (per platform) and batch items, which take the same parameters as a single call. The table is generated from each endpoint's own declaration, so it is always current; features.ai in GET /v1/platforms and x-ai in /openapi.json mark the same endpoints. The judged text is the field named in the third column: for a search result that is the title, for a post the title and body together.
| Endpoint | Rows are | Judged text | Default presets | relevance |
|---|---|---|---|---|
bluesky.post_replies | Comments | content.text | sentiment, question, complaint, issue | no |
bluesky.posts | Posts | content.text | sentiment, question, purchase_intent | no |
bluesky.search | Search results | content.text | sentiment, question | yes |
devto.articles_by_tag | Posts | content.title + content.text | sentiment, question, purchase_intent | no |
devto.comments | Comments | content.text | sentiment, question, complaint, issue | no |
devto.search | Search results | content.title + content.text | sentiment, question | yes |
github.issue_comments | Comments | content.text | sentiment, question, complaint, issue | no |
github.issues | Posts | content.title + content.text | sentiment, question, purchase_intent | no |
github.search_issues | Posts | content.title + content.text | sentiment, question, purchase_intent | no |
gitlab.issues | Posts | content.title + content.text | sentiment, question, purchase_intent | no |
gitlab.search | Search results | content.title + content.text | sentiment, question | yes |
hackernews.author_comments | Comments | content.text | sentiment, question, complaint, issue | no |
hackernews.item_comments | Comments | content.text | sentiment, question, complaint, issue | no |
hackernews.search | Search results | content.title | sentiment, question | yes |
hackernews.search_comments | Comments | content.text | sentiment, question, complaint, issue | no |
hackernews.story_comments | Comments | content.text | sentiment, question, complaint, issue | no |
lemmy.comments | Comments | content.text | sentiment, question, complaint, issue | no |
lemmy.search_comments | Comments | content.text | sentiment, question, complaint, issue | no |
lemmy.search_posts | Posts | content.title + content.text | sentiment, question, purchase_intent | yes |
lobsters.story_comments | Comments | content.text | sentiment, question, complaint, issue | no |
mastodon.status_context_replies | Comments | content.text | sentiment, question, complaint, issue | no |
mastodon.timeline_tag | Posts | content.text | sentiment, question, purchase_intent | no |
reddit.post_comments | Comments | content.text | sentiment, question, complaint, issue | no |
reddit.search_posts | Posts | content.title + content.text | sentiment, question, purchase_intent | yes |
reddit.user_comments | Comments | content.text | sentiment, question, complaint, issue | no |
stackexchange.answers | Posts | content.text | sentiment, question, purchase_intent | no |
stackexchange.comments | Comments | content.text | sentiment, question, complaint, issue | no |
stackexchange.questions | Posts | content.title + content.text | sentiment, question, purchase_intent | no |
stackexchange.search | Search results | content.title | sentiment, question | yes |
youtube.comments | Comments | content.text | sentiment, question, complaint, issue | no |
youtube.video_comments | Comments | content.text | sentiment, question, complaint, issue | no |
Any enrichment parameter on another endpoint is a free 422 INVALID_PARAMS, and so is one on /v1/creator/card. The last column says whether the endpoint is a search, where relevance= is also allowed.
The response#
Each row gets computed.labels, and one labels summary block sits at the top level next to data. This is a recorded call on the pre-release test provider (rows shortened; methodology_version carries the test provider's method id, which marks that provider, see below):
{
"success": true,
"data": [
{
"content": { "id": "8869", "text": "This has great potential! Only suggestion I would have is go slower on the demo…" },
"author": { "username": "…" },
"computed": {
"labels": {
"complaint": { "p": 0.05 },
"issue": { "topic": "usability", "confidence": 0.5 },
"question": { "p": 0.05 },
"sentiment": { "level": 4, "score_0_1": 0.7, "confidence": 0.95 }
}
}
}
],
"labels": {
"presets": ["complaint", "issue", "question", "sentiment"],
"mode": "sync",
"status": "complete",
"rows": 32,
"labelled": 32,
"cached": 0,
"skipped": 0,
"unjudged": 0,
"pending": 0,
"dropped": 0,
"extra_credits": 60.225,
"methodology_version": "fake/1"
},
"metadata": { "platform": "hackernews", "endpoint": "item_comments", "cached": false, "credits_charged": 65.225, "extra_credits": 60.225, "…": "…" },
"error": null
}| Field | Meaning |
|---|---|
presets | The presets that were judged, in the order returned. |
mode | Always sync today: the call waits for the judgments. |
status | complete (every row that could be judged was), partial (some were not) or unavailable (none were). |
rows | How many rows the endpoint returned. |
labelled | Rows that have a judgment, whether it was fresh or reused. |
cached | Of those, judgments reused from an earlier identical text. They cost nothing. |
skipped | Rows with nothing to judge: no text, not text, or shorter than 3 characters. Their computed.labels is null. |
unjudged | Rows that had text but were not judged, for the reason given. Their presets are null. |
pending | Always 0. Reserved for a future asynchronous mode. |
dropped | Always 0 for labels. Only relevance filtering drops rows. |
extra_credits | What this step cost, on top of the endpoint's price. See AI pricing. |
methodology_version | Which method produced the judgments. |
reason | Only when status is not complete. One of request_cap, account_cap, global_cap, kill_switch, provider_off, provider_unavailable, invalid_output. |
metadata.extra_credits is the sum of every enrichment step in the call, and metadata.credits_charged already includes it.
How to read it honestly#
- Signals, not verdicts. A
question.pof 0.7 is a probability from a model, not a fact about the comment. Choose the cut-off that fits your use, and spot-check rows near it. nullmeans not judged, never "no". A skipped row hascomputed.labels: null. A row that had text but was not judged has{preset: null}for each preset. Treat both as unknown, not as a negative. Nothing is guessed to fill a gap.- Count what was judged.
rows,labelled,skippedandunjudgedalways add up the story. Iflabelledis smaller thanrows, say so in whatever you build on top. - Same text, same judgment. Identical texts (ignoring case and extra whitespace) are judged once and share the result. Texts longer than 2,000 characters are clipped before judging.
methodology_versionnames the method. It is on every enrichment block. The value in the examples on these pages is the test provider's method id, the pre-release test provider: a deterministic keyword heuristic that exists so the feature can be built and tested end to end. It is not a measure of quality, and it is wrong on sarcasm, negation and non-English text. The value will change when a production method is in use, and judgments made under one version are never served as another.- Limits per request. At most 200 rows of one request are judged; the rest stay
nullwithreason: request_capandstatus: partial, and you are charged only for the rows judged. Per-account daily allowances also apply; see AI pricing.
When nothing could be judged#
strict decides what happens when not a single row could be judged, because enrichment is off for the account, the provider is unavailable or a daily allowance is used up.
strict=true(the default): the call fails, so you never mistake unjudged rows for judged ones. It isAI_UNAVAILABLE(503) orAI_BUDGET_EXCEEDED(429). When enrichment is not enabled at all, this is refused before the data is fetched, so it costs nothing.strict=false: you get the rows back,computed.labelsisnullon each,labels.statusisunavailablewith areason, and there is no extra charge. Use it when the rows matter more than the judgments.
A partial result is never an error, whichever you choose: you get what was judged, status: partial and the reason.
Batch, multi-platform search and streams#
- Batch. Put the parameters in an item's
params. The summary blocks sit on that item's row, not at the top of the response. - Multi-platform search.
include=labelsandlabel=on/v1/search/multiapply to every platform whose search endpoint accepts them. Each platform's row carries its ownlabelsblock andextra_credits, anddata.labelsadds them up (legs, the counters, andunsupported: the platforms whose search has no enrichment, whose rows come back untouched).data.labels.statusiscompleteonly when every platform is. - Streams. With
Accept: text/event-streamthe platform frames arrive first, without the enrichment lines in theircredits_charged, thenfused, then oneenrichframe per step, thendonewith the full total. See Streaming. - Idempotency. A replay with the same
Idempotency-Keyreturns the stored body, labels included, at 0 credits. Changing any enrichment parameter makes it a different request (IDEMPOTENCY_KEY_REUSED).
What it costs#
The endpoint's own price, plus a separate charge for each step that judged something. It depends on how many rows are judged and how many presets you ask for, and it is reserved before judging and settled to what was actually used. Cached judgments, skipped rows and unjudged rows are free. Details and the measured examples are on AI pricing.
Errors it can return#
| Status | Type | When |
|---|---|---|
| 422 | INVALID_LABEL | label= named a preset that does not exist. Free. |
| 422 | INVALID_PARAMS | An enrichment parameter on an endpoint that does not accept it, or a malformed include. Free. |
| 429 | AI_BUDGET_EXCEEDED | Nothing could be judged because today's allowance is used up and strict=true. Wait Retry-After. |
| 503 | AI_UNAVAILABLE | Nothing could be judged because enrichment is off or unavailable and strict=true. |
| 402 | INSUFFICIENT_CREDITS, KEY_BUDGET_EXCEEDED | The reservation for the judgments cannot be covered. |
See Errors for the full list.