Docs
View as Markdown

Themes

themes=true groups the rows a call returned by what they talk about and gives each group a name, a size, the ids of its rows and a sample. Ninety-seven replies to a post become a handful of topics you can read in a minute.

When to use it#

  • You have a long comment or reply list and want to see the main topics before reading individual rows.
  • You want counts per topic, with the row ids to jump back to the originals.

It works on the same 31 list endpoints as labels, and on multi-platform search.

The request#

curl "https://api.onekey.dev/v1/bluesky/post_replies?uri=at://did:plc:…/app.bsky.feed.post/…&themes=true" \
  -H "x-api-key: YOUR_API_KEY"
ParameterValuesWhat it does
themestrueCluster the rows the call returns and name each cluster.
stricttrue (default) or falseAs on Labels. Only matters for the naming step.

Combine it with relevance=filter to cluster only the rows that survived the filter.

How it works#

  1. Clustering is deterministic and involves no model. The rows' text is compared with a standard word-frequency similarity (TF-IDF cosine) and grouped. The same rows always give the same clusters, and there are at most 12.
  2. Only the names come from the provider. One call names all the clusters at once. If enrichment is off or the naming fails, the clusters are still returned, named Theme 1, Theme 2 and so on, with named: false.

The response#

The themes block sits at the top level next to data. Recorded on the pre-release test provider (list shortened):

json
{
  "success": true,
  "data": [ "…97 replies…" ],
  "themes": {
    "status": "complete",
    "rows": 97,
    "clustered": 92,
    "skipped": 5,
    "named": true,
    "extra_credits": 7.942,
    "methodology_version": "fake/1",
    "clustering": "cluster/1",
    "themes": [
      { "theme": "Bsky Bluesky Social", "size": 41, "row_ids": ["at://did:plc:…/3mw2eruoksc2x", "…"], "sample": "…", "terms": ["bsky", "bluesky", "…"] }
    ]
  },
  "metadata": { "credits_charged": 12.942, "extra_credits": 7.942, "…": "…" },
  "error": null
}
FieldMeaning
rows, clustered, skippedRows returned, rows placed in a theme, and rows with no usable text (which are in no theme).
namedtrue when a provider named the clusters; false when they carry the placeholder names.
themes[].themeThe cluster's name.
themes[].sizeRows in the cluster.
themes[].row_idsThe rows' ids (the record's own id, or row-<index>). On multi-platform search they are platform:id.
themes[].sampleA representative text from the cluster.
themes[].termsThe words that define the cluster. These come from the clustering, not from a model.
clusteringThe clustering method, cluster/1.
methodology_version, status, extra_creditsAs on Labels.

How to read it honestly#

  • Themes are a reading aid, not a taxonomy. Grouping is by shared words. It works best on short comments; it can merge long texts that share vocabulary, and it can split paraphrases that share no words.
  • A name is a summary of a sample, not a finding. Check terms and sample before you quote a theme's name.
  • Every row is accounted for. skipped rows are in no theme, so clustered plus skipped is rows.
  • Not deduplicated across platforms. On multi-platform search, the same URL posted on two platforms is two rows, and clustering runs once over the kept rows of every platform that accepts enrichment.

On /v1/search/multi, data.themes is computed once over the kept rows of every opted-in platform (after relevance=filter, if used), and a stream carries it in an enrich frame. See Streaming.

What it costs#

One separate charge for the naming call, on top of the endpoint's price. A call with the provider off returns the placeholder-named clusters at no extra charge. See AI pricing.

Errors it can return#

StatusTypeWhen
422INVALID_PARAMSthemes on an endpoint that does not accept enrichment. Free.
429AI_BUDGET_EXCEEDEDToday's allowance is used up and strict=true.
503AI_UNAVAILABLEEnrichment is off or unavailable and strict=true.
402INSUFFICIENT_CREDITS, KEY_BUDGET_EXCEEDEDThe reservation cannot be covered.

See Errors for the full list.