> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.milliseconds.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.milliseconds.ai/_mcp/server.

# Entities

`POST /v1/decision-machine-1/entities` takes your text and a list of types. It returns every span that matches a type, sorted by start offset. Each entity carries its type, the matched text, a probability, and character offsets. You name the types yourself. There is no fixed taxonomy.

## When to use it

* You need **all** occurrences of something, not one best answer.
* You need **offsets** to redact, highlight, or link spans in the original text.
* You need a **custom type set**: `case_number`, `drug_name`, `ticker`, `policy_id`.
* Use [Extract](/capabilities/extract) instead when you want one typed record with named fields. Use [Answer](/capabilities/answer) instead when you want one span per question.

## Three example decisions

* Find every person, organization, and location in a news paragraph.
* Find every email address, phone number, and account number in a support message, then redact the offsets.
* Find every drug name and dosage in a clinical note, then count them.

## Request

| Field   | Type      | Required                           | Meaning                                       | Limits                                     |
| ------- | --------- | ---------------------------------- | --------------------------------------------- | ------------------------------------------ |
| `text`  | string    | one of `text` / `texts`            | The text to scan                              | 1 to 20,000 characters                     |
| `texts` | string\[] | one of `text` / `texts`            | A batch of texts                              | 1 to 32 items, each 1 to 20,000 characters |
| `types` | string\[] | yes (this form or the object form) | Type names. Each name is its own description. | 1 to 64 items, each at least 1 character   |
| `types` | object    | yes (this form or the array form)  | Map of type name to description               | No count cap                               |

Send `text` or `texts`, never both. The object form gives the model a description per type. Describe any type whose name alone is ambiguous.

#### curl

```bash
curl -s -X POST https://api.milliseconds.ai/v1/decision-machine-1/entities \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer test_sk-...' \
  -d '{
    "text": "John Ternus announced the iPhone Duo in Cupertino on 9 September.",
    "types": ["person", "product", "location"]
  }'
```

#### Python

```python
import requests

response = requests.post(
    "https://api.milliseconds.ai/v1/decision-machine-1/entities",
    headers={"authorization": "Bearer test_sk-..."},
    json={
        "text": "John Ternus announced the iPhone Duo in Cupertino on 9 September.",
        "types": ["person", "product", "location"],
    },
)
print(response.json()["entities"])
```

#### TypeScript

```typescript
const response = await fetch(
  "https://api.milliseconds.ai/v1/decision-machine-1/entities",
  {
    method: "POST",
    headers: { "content-type": "application/json", authorization: "Bearer test_sk-..." },
    body: JSON.stringify({
      text: "John Ternus announced the iPhone Duo in Cupertino on 9 September.",
      types: ["person", "product", "location"],
    }),
  },
);

const { entities } = await response.json();
console.log(entities);
```

> **Note**
>
> Every request needs an API key. See [Authentication](/authentication).

## Response

```json
{
  "entities": [
    {"type": "person", "text": "John Ternus", "probability": 0.999, "start": 0, "end": 11},
    {"type": "product", "text": "iPhone Duo", "probability": 0.973, "start": 26, "end": 36},
    {"type": "location", "text": "Cupertino", "probability": 0.999, "start": 40, "end": 49}
  ]
}
```

* `entities` — every matching span, sorted by `start` ascending.
* `entities[].type` — the type name you requested, copied back.
* `entities[].text` — the matched span.
* `entities[].probability` — the span confidence, 0 to 1, rounded to 3 decimals.
* `entities[].start` and `entities[].end` — character offsets into your text. They are never `null` on this capability.

The slice `text[start:end]` equals `entities[].text`. Use the offsets, not a string search: the same string can occur many times.

An empty result is a normal result. A text with no match for any requested type returns an empty array:

```json
{"entities": []}
```

## Reading the numbers

This request maps each type to a description:

```json
{
  "text": "Contact Maria Alvarez at maria.alvarez@northwind.example or +1 415 555 0132. Her account number is 4471-8890.",
  "types": {
    "email": "an email address",
    "phone": "a telephone number",
    "account_number": "a customer account or card number",
    "person": "the full name of a person"
  }
}
```

It returns:

```json
{
  "entities": [
    {"type": "person", "text": "Maria Alvarez", "probability": 0.999, "start": 8, "end": 21},
    {"type": "email", "text": "maria.alvarez@northwind.example", "probability": 0.994, "start": 25, "end": 56},
    {"type": "phone", "text": "+1 415 555 0132", "probability": 0.998, "start": 60, "end": 75},
    {"type": "account_number", "text": "4471-8890", "probability": 0.991, "start": 99, "end": 108}
  ]
}
```

Read `account_number` at `0.991`. The probability is the raw span confidence from the extractor. It is not normalized across types, so the numbers do not sum to 1. Each entity stands alone.

Pick one threshold per action:

| Action                    | Threshold       | Why                                                       |
| ------------------------- | --------------- | --------------------------------------------------------- |
| Redact before storage     | Keep everything | A missed span leaks data. A wrong redaction costs little. |
| Highlight in a UI         | 0.5 and above   | The reader corrects the rest.                             |
| Write to a database field | 0.9 and above   | Route the rest to a person.                               |

Raise the bar with the blast radius. [Thresholds and confidence routing](/concepts/thresholds) covers the bands.

This capability returns no `confidence` field. `confidence` exists on [Classify](/capabilities/classify) and [Rate](/capabilities/rate), where the scores compete over one label set.

## Batching

Send `texts` instead of `text`. The response is `{"results": [...]}`, one entry per text, in input order.

```bash
curl -s -X POST https://api.milliseconds.ai/v1/decision-machine-1/entities \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer test_sk-...' \
  -d '{
    "texts": [
      "Acme Corp signed with Globex in Berlin.",
      "Jane Foster joined Initech in Austin."
    ],
    "types": ["organization", "location"]
  }'
```

```json
{
  "results": [
    {
      "entities": [
        {"type": "organization", "text": "Acme Corp", "probability": 0.984, "start": 0, "end": 9},
        {"type": "organization", "text": "Globex", "probability": 0.979, "start": 22, "end": 28},
        {"type": "location", "text": "Berlin", "probability": 0.999, "start": 32, "end": 38}
      ]
    },
    {
      "entities": [
        {"type": "organization", "text": "Initech", "probability": 0.993, "start": 19, "end": 26},
        {"type": "location", "text": "Austin", "probability": 0.999, "start": 30, "end": 36}
      ]
    }
  ]
}
```

The second text names `Jane Foster`. You did not request a `person` type, so no person span comes back. The model returns only the types you name.

Offsets are per text. Index result `n` against `texts[n]`. Each text is a separate inference call, so a batch of 32 fans out to 32 calls. Statements and questions follow different rules, covered in [Batching](/capabilities/batching).

## Limits and gotchas

| Limit               | Value                              |
| ------------------- | ---------------------------------- |
| `text` length       | 20,000 characters                  |
| `texts` items       | 32                                 |
| `types` array items | 1 to 64                            |
| `types` object keys | No count cap                       |
| Typical latency     | 0.42 s for 3 types on a short text |

* An empty `types` array fails: `{"error":{"code":"invalid_request","message":"types: Too small: expected array to have >=1 items"}}`.
* Sending both `text` and `texts` fails with `body: provide text, texts or image`. Sending neither returns the same message.
* **The common mistake:** vague type names. `number` matches dates, quantities and ids. Name the type for the case, or move to the object form and describe it. [Writing good statements and labels](/concepts/writing-good-questions) has the rules.
* Duplicate mentions each return their own entity. Deduplicate on `text` in your own code when you want a set.
* Over 2,000 characters the service scans overlapping windows and remaps the offsets, so the offsets stay correct against your original string. Cost is linear in characters. [Long text and chunking](/concepts/long-text) covers the windows.
* The extractor is multilingual. Type descriptions in English work on non-English text. [Languages](/concepts/languages) carries the measurements.
* A `502 runner_error` or a `529 overloaded` means retry with backoff. [Errors](/reference/errors) lists every code.

## Images

Send `image` instead of `text` to run this capability over a photograph or a scanned page. `image` is
a `data:image/jpeg;base64,` URL or bare base64 of a JPEG, PNG or WebP, at most 5 MB decoded. `detail`
picks the resolution: `low` is 512 px, `medium` is 768 px and is the default, `high` is 1024 px.

```bash
curl -X POST https://api.milliseconds.ai/v1/decision-machine-1/entities \
  -H "content-type: application/json" \
  -H "authorization: Bearer test_sk-..." \
  -d '{
    "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
    "detail": "low",
    "types": {
      "merchant": "The shop or company name",
      "amount": "A money amount"
    }
  }'
```

The response keeps the same label-keyed items. An image has no character string to index into, so
`start`, `end` and `probability` are `null` on every item. Code that redacts by offset has nothing
to index on an image.

`image` works with `text`, never with `texts`. The image bills a fixed number of tokens:
2,000 for `low`, 4,000 for `medium`, 8,000 for `high`. The rest of the body bills as today. See [Images](/concepts/images).

## Next

* [PII detection](/recipes/pii-detection) — described PII types and offset redaction, end to end.
* [Choosing a capability](/concepts/choosing-a-capability) — entities against extract, and the other near misses.
* [Extract](/capabilities/extract) — one typed record instead of a span list.