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

# AI

> Group, classify, read and summarise items with one call, and attach an agent step to a keyword.

Nephia's AI passes are on the API, not only in the dashboard. `POST /v1/analyses`
runs one of four passes over items you name; a [keyword](/keywords) with an `aiStep`
runs one of them on every event it catches.

## The four kinds

| `kind` | What it does | Needs |
| - | - | - |
| `group` | Clusters the items by your instruction. | `instruction` |
| `classify` | Sorts each item into buckets you define. | `buckets` |
| `agent` | Applies your instruction and schema to **each** item. | `instruction`, `schema` |
| `summarise` | Answers one question about the whole set. | `question` |

`group`, `classify` and `summarise` are the passes the dashboard runs on a
keyword's results. `agent` is the one you write yourself.

## Credits

| Kind | Rate |
| - | - |
| `group`, `classify`, `summarise` | 1 credit per 50 items |
| `agent` | 1 credit per 25 items |

The agent's batch is half the size for the same credit because its output is
generated text for every field you asked for, not one verdict per item.

`meta.credits_used` on the response is what was actually charged, and it is not
always what the estimate quoted: **a batch the provider could not answer is not
billed**. Price a call first with `GET /v1/analyses/estimate?item_count=…`,
which is free.

## Items

Send them inline:

```bash theme={null}
curl -X POST https://api.nephia.cc/v1/analyses \
  -H "Authorization: Bearer $NEPHIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "summarise",
    "question": "What are people complaining about?",
    "items": [
      { "id": "1", "source": "reddit", "title": "Latency spikes since Tuesday", "body": "…" },
      { "id": "2", "source": "reddit", "title": "Anyone else seeing 502s?", "body": "…" }
    ]
  }'
```

Or point at a keyword you own and let Nephia read its recent events:

```json theme={null}
{
  "kind": "group",
  "instruction": "Group these by the feature they talk about.",
  "keyword_id": "0b0c…",
  "since": "2026-08-17T00:00:00.000Z",
  "limit": 200
}
```

Up to 200 items per call. Send **exactly one** of `items` or `keyword_id`, never both.

## The agent step

An agent step is **one instruction and one flat schema of your own fields**:

```json theme={null}
{
  "kind": "agent",
  "instruction": "Decide whether this person is looking to buy, and how ready they are.",
  "schema": {
    "intent": { "type": "enum", "values": ["buying", "researching", "none"] },
    "readiness": { "type": "number", "description": "0 to 1." },
    "reason": { "type": "string", "maxLength": 160 }
  },
  "items": [ "…" ]
}
```

You get one object per item, with your keys:

```json theme={null}
{
  "meta": { "kind": "agent", "item_count": 2, "batches": 1, "credits_used": 1, "model": "…" },
  "data": {
    "outputs": [
      { "item_id": "1", "output": { "intent": "buying", "readiness": 0.8, "reason": "Asks where to buy it today." } },
      { "item_id": "2", "output": { "intent": "none", "readiness": 0, "reason": null } }
    ]
  }
}
```

### The schema subset

Deliberately narrow, so every schema can be enforced rather than merely
requested:

* **1 to 12 fields**, flat. No nesting, no arrays.
* Each field is `string`, `number`, `boolean` or `enum`.
* `values` (up to 12) belongs to `enum`; `maxLength` (up to 400) to `string`.
* `description` is read by the model — write it as a sentence someone else could
  apply by hand.
* Field names start with a letter and use letters, digits and underscores. They
  are yours: they come back as the keys of `output`, and become `agent:<name>`
  rule fields in the dashboard.

**Every field can answer `null`.** That is a real answer — "this item does not
support one" — not an error. A model forced to always produce a value produces
confident nonsense, which is worse than a gap you can see.

## On a keyword

Give a keyword an `aiStep`, on create or with a patch, and every event it emits carries
the reading in its `ai` block. Delivery waits for it, up to 120 seconds, and the event is
delivered either way. See [Webhooks: Agent step](/webhooks#agent-step).

```json theme={null}
{
  "aiStep": {
    "instruction": "Say whether the author is asking for a recommendation.",
    "schema": {
      "asking": { "type": "boolean" },
      "urgency": { "type": "enum", "values": ["low", "medium", "high"] },
      "why": { "type": "string", "maxLength": 200 }
    }
  }
}
```

The step's credits are charged on top of the keyword's ticks, at the agent
rate above: one credit per 25 events read.

## Sentiment and intent

A keyword with **analysis** turned on (`sentimentEnabled`) has every item it catches read on two more
dimensions, on the same pass and at the same rate — one charge, one call, two
answers:

* **sentiment** — `positive`, `neutral`, `negative`, `question` or `mixed`
* **intent** — `purchase_intent`, `comparison`, `question`, `complaint`,
  `praise` or `other`

They ride the same batch as your `ai` step but stay out of `output`: the keys
you defined are the keys you get back. The reading is delivered on the event's
own [`analysis` key](/webhooks#analysis), and each mention read from
`GET /v1/mentions` or `GET /v1/keywords/{id}/results` carries `sentiment` and `intent`.

`null` on either means nothing has read that item yet. It never means "we
looked and found nothing" — that is what `neutral` and `other` are for.

<h2 id="relevance">
  Relevance
</h2>

The same pass also says how much each mention is about its keyword, at no extra
charge:

* **relevance**: `high` when the mention is about the keyword's subject and you
  would want to see it, `medium` when the subject is there but secondary (one
  entry in a list, a passing quote), `low` when the match is accidental:
  another meaning of the word, a username, a bare link, an unrelated job offer,
  or spam.
* **relevanceReason**: one sentence saying why.

The reading is judged against what the keyword is about: its name and terms,
its `subjectRole`, the `relevanceContext` sentence you give it on
`POST /v1/keywords` or `PATCH /v1/keywords/{id}` (for example "Acme is a cloud
storage company, not the cartoon."), and, for your own brand, the name and
tagline of your brand card. Editing any of them re-bills nothing and applies to
the mentions read afterwards.

Both fields are on every mention of `GET /v1/mentions` and
`GET /v1/keywords/{id}/results`. Filter with `relevance` (repeatable, `unread`
for what nothing has read yet), count with `GET /v1/mentions/stats` and
`group_by=relevance`, and route with a rule condition on the `relevance` field.
A correction made from the dashboard is reported as the reading.

`null` means nothing has read the mention: the reading is off on that keyword,
the account had no credits, or the pass came back late. Mentions caught before
relevance existed stay `null`; nothing is read twice. The webhook is sent when
an item is caught, usually before the reading, so its payload carries no
relevance.

## Failures

An AI pass is an enhancement, never a dependency. Nothing here can lose an
event or stop a poll.

* A batch the provider could not answer is **not charged**, and `meta.batches`
  says how many actually ran.
* If no batch answered, the call returns `503` and charges nothing.
* On a keyword, a step that times out or fails still delivers the event, with
  `ai.output: null` and `ai.status` saying which.
* With no provider configured, `POST /v1/analyses` returns `503` and keywords
  deliver immediately with `ai: null`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.