> ## Documentation Index
> Fetch the complete documentation index at: https://www.outx.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Intent Labels, Enrichment & Default Feed Filters

> What intent labels are, how posts get classified against them during enrichment, what default: true/false means, and how to read or change labels via the API.

Intent labels are the classification taxonomy of a keyword or Reddit watchlist. Keywords decide **which posts are collected**; intent labels decide **what each collected post means** (for example "buying intent" vs "competitor mention" vs "hiring"). This page is the canonical reference for how labels work across the API.

Intent labels exist only on keyword (LinkedIn) and Reddit watchlists. People and company watchlists do not have them.

## The label object

Labels are stored as an array on the watchlist. Each label has three fields:

```json theme={null}
{
  "key": "buying-intent",
  "description": "Posts where the author is actively evaluating or asking for recommendations for a CRM tool",
  "default": true
}
```

| Field         | Meaning                                                                                                                                                                      |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`         | The label's identifier. This exact string appears in each post's `tags` array after classification.                                                                          |
| `description` | What the AI classifier matches a post against. **Only the description is read by the classifier**, so a specific, concrete description matters far more than the key.        |
| `default`     | Whether this label is one of the watchlist's default feed filters (see [Default labels](#default-labels-the-pre-applied-feed-filter) below). At most 2 labels can be `true`. |

<Info>
  **Naming across the API.** On **input** (POST/PUT `labels`), use `name` for the label identifier; `key` is accepted as an alias so a label object returned by GET can be sent back unchanged. On **output** (GET watchlist), labels always use `key`. On **posts** (`GET /api-posts`), the assigned label appears as `tags` (the key) plus `tagDescriptions` (key and description pairs). "Label" on the watchlist and "tag" on a post are the same concept.
</Info>

## Lifecycle: how a post gets its label

1. **Watchlist creation.** Labels come from one of two places:
   * **You provide them** in the `labels` array of the create call.
   * **OutX generates them.** In prompt mode, or when you create with bare `keywords` and no usable `labels`, OutX generates a label taxonomy (plus an `objective`) in the background. The create response returns immediately; labels appear on the watchlist a few seconds later (fetch them with GET).
2. **Collection.** On each fetch cycle (`fetchFreqInHours`), new posts matching the keywords are collected. A freshly collected post has **no label yet** (`tags` is empty).
3. **Enrichment (background).** Each new post is processed once:
   * It is scored for relevance against the watchlist's objective embedding, producing `relevance_score` (1 to 10).
   * For relevant posts, an AI call picks the **single** label whose `description` best fits the post. That label's key is written to the post's `tags`.
   * A post with no confident match (or too low relevance) gets the literal tag `"na"`.
4. **Reading.** `GET /api-posts` returns each post's `tags` and `tagDescriptions`. Filter the feed by label with the `labels` query parameter.

<Warning>
  **Classification is forward-only.** A post is classified exactly once, when first enriched. Changing `labels` via PUT affects how **future** posts are classified; already-enriched posts keep their old tags. Re-classifying historical posts requires an explicit re-enrichment by OutX (contact support), a label update alone does not trigger it.
</Warning>

<Warning>
  **Empty `tags` means "not classified yet", not "no match."** Enrichment is asynchronous. An agent polling `/api-posts` right after creating a watchlist should expect posts with empty `tags` for a while. `"na"` (not empty) is the "no match" signal.
</Warning>

## Default labels: the pre-applied feed filter

`default: true` marks a label as one of the watchlist's **default feed filters**. In the OutX app, the feed opens with the default labels pre-applied, so users see the highest-intent bucket first instead of the whole firehose. The rules:

* **At most 2 labels** can have `default: true`. If you send more, the extras are silently capped to `false` (first two win, in array order).
* **Never zero.** If you send a `labels` array where at least one label carries an explicit `default` boolean but none is `true`, the first label is automatically promoted to `default: true`.
* If you omit `default` from every label in the array, no default flags are written (legacy behavior) and the app falls back to showing all posts.

<Info>
  **The API feed does not auto-apply defaults.** `GET /api-posts` returns all posts unless you pass the `labels` parameter. `default` controls the product UI's initial filter and tells you which buckets the watchlist owner considers primary. To reproduce the default feed via API, read the default labels and pass them explicitly (recipe below).
</Info>

### Reading which labels are default

`GET /api-<type>-watchlist?id=...` returns the labels with their flags:

```bash theme={null}
curl "https://api.outx.ai/api-keyword-watchlist?id=550e8400-e29b-41d4-a716-446655440000" \
  -H "x-api-key: YOUR_API_KEY"
```

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "AI CRM Watchlist",
  "type": "keyword",
  "labels": [
    { "key": "buying-intent", "description": "Actively evaluating or asking for CRM recommendations", "default": true },
    { "key": "competitor-mention", "description": "Mentions Salesforce, HubSpot, or Pipedrive by name", "default": true },
    { "key": "industry-news", "description": "General CRM market news and funding announcements", "default": false }
  ]
}
```

Labels on watchlists created before the `default` flag existed may omit the field entirely; treat a missing `default` as `false`.

### Recipe: read the feed filtered to default labels only

```bash theme={null}
# 1. Get the watchlist and collect keys where default is true
curl "https://api.outx.ai/api-keyword-watchlist?id=WATCHLIST_ID" \
  -H "x-api-key: YOUR_API_KEY"
# -> default labels: buying-intent, competitor-mention

# 2. Pass those keys as the labels filter (comma-separated)
curl "https://api.outx.ai/api-posts?watchlist_id=WATCHLIST_ID&labels=buying-intent,competitor-mention" \
  -H "x-api-key: YOUR_API_KEY"
```

To fetch unclassified-bucket posts, filter with `labels=no labels` (the API maps it to the stored `"na"` tag).

### Changing which labels are default

Send the full `labels` array on PUT with the `default` booleans you want. **PUT replaces the whole label set**, so always start from the current GET output and modify it; sending a partial array drops the labels you omit.

```bash theme={null}
# Make industry-news a default and demote competitor-mention
curl -X PUT "https://api.outx.ai/api-keyword-watchlist" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "labels": [
      { "key": "buying-intent", "description": "Actively evaluating or asking for CRM recommendations", "default": true },
      { "key": "competitor-mention", "description": "Mentions Salesforce, HubSpot, or Pipedrive by name", "default": false },
      { "key": "industry-news", "description": "General CRM market news and funding announcements", "default": true }
    ]
  }'
```

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "updated": true,
  "message": "Watchlist updated successfully",
  "labels": [
    { "key": "buying-intent", "description": "Actively evaluating or asking for CRM recommendations", "default": true },
    { "key": "competitor-mention", "description": "Mentions Salesforce, HubSpot, or Pipedrive by name", "default": false },
    { "key": "industry-news", "description": "General CRM market news and funding announcements", "default": true }
  ]
}
```

The same `labels` array (including `default`) is accepted on create (`POST`) for both `/api-keyword-watchlist` and `/api-reddit-watchlist`.

<Note>
  Keeping the same `key` and `description` strings when toggling `default` means already-classified posts keep matching their tags. Renaming a key orphans the old tag on historical posts (they keep the old string in `tags`).
</Note>

## Timing summary

Two delays stack before labeled posts appear:

1. **Collection cadence:** new posts arrive on the `fetchFreqInHours` cycle (LinkedIn keyword watchlists also backfill recent matching posts automatically at creation).
2. **Enrichment queue:** each collected post is scored and classified in the background.

Poll `GET /api-posts` and treat empty `tags` as pending, `"na"` as classified-no-match.

## No sentiment field

OutX classifies posts into **your** intent labels, not into positive/negative sentiment, and `relevance_score` measures topical relevance, not tone. If you need sentiment, model it as an intent label (for example a label described as "frustrated with or complaining about a competitor") or derive it from post `content` yourself.

## Related pages

<CardGroup cols={2}>
  <Card title="Create Keyword Watchlist" icon="magnifying-glass" href="/docs/api-reference/watchlist/keyword/create">
    Set labels at creation time
  </Card>

  <Card title="Update Keyword Watchlist" icon="pen" href="/docs/api-reference/watchlist/keyword/update">
    Replace labels or toggle defaults
  </Card>

  <Card title="Get Posts" icon="newspaper" href="/docs/api-reference/engagement/posts/get">
    Filter the feed by labels
  </Card>

  <Card title="Choose a Watchlist Type" icon="list-check" href="/docs/api-reference/watchlist/overview">
    LinkedIn vs Reddit watchlists
  </Card>
</CardGroup>
