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

# Reference

> Reference information for the Fetch API

## Endpoint

| Method | Path        | Operation           |
| ------ | ----------- | ------------------- |
| `POST` | `/v1/fetch` | Fetch up to 20 URLs |

Over REST, authenticate with the `x-api-key` header against
`https://api.seltz.ai`. The SDKs speak gRPC to `grpc.seltz.ai`; pass your API
key to the client constructor and it is sent on every call.

## SDK methods

| Python  | TypeScript | Returns                           |
| ------- | ---------- | --------------------------------- |
| `fetch` | `fetch`    | `results` — one per requested URL |

Both return the response message whole rather than the list inside it.

## Request

| Field        | Type            | Default             |
| ------------ | --------------- | ------------------- |
| `urls`       | list of strings | required            |
| `formats`    | list of strings | `["markdown"]`      |
| `tier`       | string          | `"pro"`             |
| `timeout_ms` | integer         | the service maximum |

`formats` names the representations to return. `"markdown"` returns the page's
main content with navigation, footers and ads removed. An unrecognized name is
rejected rather than dropped. Sending an empty list is the same as leaving the
field out.

`tier` selects the price. `"pro"` is the only value, and an omitted `tier`
resolves to it. An unrecognized value is rejected rather than defaulted.

`timeout_ms` is a wall-clock budget for **one URL**, not for the batch. URLs are
fetched concurrently, so a batch takes roughly as long as its slowest entry. A
URL that runs out gets `error.code = "timeout"`; the others are unaffected.
Values above the maximum are clamped, not rejected.

## Result

| Field              | Set when                                                                        |
| ------------------ | ------------------------------------------------------------------------------- |
| `requested_url`    | Always. Echoed byte for byte from the request.                                  |
| `status`           | Always. `ok` or `error`.                                                        |
| `error`            | `status` is `error`. Carries `code` and `message`.                              |
| `markdown`         | The page produced Markdown and it was requested.                                |
| `fetched_at`       | The content was retrieved. RFC 3339.                                            |
| `final_url`        | Always on a successful fetch. Equal to `requested_url` when nothing redirected. |
| `http_status_code` | A network response was observed for the main document.                          |
| `content_type`     | The origin declared a media type. Parameters are stripped.                      |

## Limits

| Limit            | Value                               |
| ---------------- | ----------------------------------- |
| URLs per request | 1 minimum, 20 maximum               |
| URL length       | 2048 bytes                          |
| Per-URL timeout  | 75 seconds maximum, and the default |
| Request body     | 65536 bytes                         |

Duplicate URLs in one request are rejected: `requested_url` is the correlation
key, and a repeated key is ambiguous.

## Errors

### The request was rejected

A rejection is a non-200 with this body. Nothing was fetched and nothing was
billed. The `code` is a stable name and is the field to branch on; the
`message` is prose and can change.

```json theme={null}
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "urls accepts at most 20 entries, got 21"
  }
}
```

| Status | Code                   | When                                                                                                                            |
| ------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `INVALID_REQUEST`      | A malformed body, an empty or over-long `urls` list, a duplicate or over-long URL, or an unavailable `formats` or `tier` value. |
| 401    | `UNAUTHENTICATED`      | Invalid or missing API key.                                                                                                     |
| 402    | `INSUFFICIENT_CREDITS` | The balance cannot cover the request.                                                                                           |
| 429    | `RATE_LIMITED`         | Too many requests. Retry later.                                                                                                 |
| 500    | `INTERNAL`             | A fault on our side.                                                                                                            |

Unrecognized fields in the request body are ignored.

<Note>
  Over gRPC the same failures arrive as status codes rather than as this body.
</Note>

### One URL failed

A page that could not be fetched is **not** a rejection. It arrives inside the
`200` as that result's `status = "error"` and an `error.code`.

| Code                       | Meaning                                                                                       |
| -------------------------- | --------------------------------------------------------------------------------------------- |
| `invalid_url`              | Not a parseable absolute URL.                                                                 |
| `unsupported_scheme`       | Parseable, but not `http` or `https`.                                                         |
| `url_not_accessible`       | DNS, connection, TLS or navigation failure reaching the origin.                               |
| `timeout`                  | The fetch did not finish within `timeout_ms`.                                                 |
| `unsupported_content_type` | The document is not an HTML page. PDFs, images, archives and other binaries are out of scope. |
| `extraction_failed`        | The page was fetched, but no requested format could be produced from it.                      |
| `upstream_error`           | The fetch path itself failed. Ours, not the URL's.                                            |

The set grows, so treat an unrecognized code as a generic failure rather than
rejecting the result.

## Enum spelling

`status` is spelled three ways depending on where you read it.

| Where          | Spelling                                                        |
| -------------- | --------------------------------------------------------------- |
| REST / JSON    | `"ok"`, `"error"` — lowercase                                   |
| TypeScript SDK | `FetchStatus.OK`, `FetchStatus.ERROR`                           |
| Python SDK     | `FetchStatus.FETCH_STATUS_OK`, `FetchStatus.FETCH_STATUS_ERROR` |
