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

# SDKs

> Python and TypeScript clients, generated from the API contract.

export const apiBase = "https://api.vainona.ai/v1";

Both SDKs are thin. They are generated from the same OpenAPI document as the API reference, with no caching and no validation beyond types. They add two things of their own: they [retry](#errors-and-retries) failed requests that are safe to repeat, and they [import](#importing-documents) large sets of documents in batches. Any request the SDK makes, you can make with `curl` against <code>{apiBase}</code>.

|         | Python                | TypeScript                               |
| ------- | --------------------- | ---------------------------------------- |
| Install | `pip install vainona` | `npm install vainona`                    |
| Runtime | Python 3.9+           | Node 20.19+, or any runtime with `fetch` |
| Version | 1.x, for API `/v1`    | 1.x, for API `/v1`                       |

## Methods

Methods mirror the routes. TypeScript uses the same names in camelCase.

```python theme={null}
db = Client(api_key=...)
ns = db.namespace("acme/prod/tenant_123")

ns.judgments.create(name=..., type=..., question=..., context=..., engine=..., thresholds=..., activate=True)
ns.write(upsert=[...], patch=[...], append=[...], delete=[...], wait_for=[...])
ns.query(filters=..., rank_by=..., top_k=..., include=..., answers="fresh_only")
ns.get("t_123", include_history=True)
ns.judgments.update("needs_escalation", freshness={"policy": "on_change"}, confirm=True)
ns.judgments.activate("needs_escalation", version=4)
ns.judgments.backfill("needs_escalation", confirm=False)
ns.outcomes.append([...])
db.jobs.get(job_id); db.jobs.pause(job_id); db.jobs.resume(job_id); db.jobs.cancel(job_id); db.jobs.confirm(job_id)
ns.import_documents(records, concurrency=4)
```

The rest of the API:

| Route                                                        | Python                                                           | TypeScript                                                           |
| ------------------------------------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------- |
| `GET /namespaces`                                            | `db.namespaces.list(prefix=...)`                                 | `db.namespaces.list({ prefix })`                                     |
| `GET /namespaces/{ns}`                                       | `ns.metadata()`                                                  | `ns.metadata()`                                                      |
| `PATCH /namespaces/{ns}`                                     | `ns.update(budget=...)`                                          | `ns.update({ budget })`                                              |
| `POST /namespaces/{ns}/warm`                                 | `ns.warm()`                                                      | `ns.warm()`                                                          |
| `DELETE /namespaces/{ns}`                                    | `ns.delete()`                                                    | `ns.delete()`                                                        |
| `GET /namespaces/{ns}/judgments`                             | `ns.judgments.list()`                                            | `ns.judgments.list()`                                                |
| `GET /namespaces/{ns}/judgments/{name}`                      | `ns.judgments.get(name)`                                         | `ns.judgments.get(name)`                                             |
| `DELETE /namespaces/{ns}/judgments/{name}`                   | `ns.judgments.delete(name)`                                      | `ns.judgments.delete(name)`                                          |
| `PATCH /namespaces/{ns}/judgments/{name}` with thresholds    | `ns.judgments.update(name, thresholds=...)`                      | `ns.judgments.update(name, { thresholds })`                          |
| `GET /namespaces/{ns}/judgments/{name}/calibration`          | `ns.judgments.calibration(name)`                                 | `ns.judgments.calibration(name)`                                     |
| `GET /namespaces/{ns}/judgments/{name}/thresholds/recommend` | `ns.judgments.recommend_threshold(name, target="precision:0.9")` | `ns.judgments.recommendThreshold(name, { target: "precision:0.9" })` |
| `POST /namespaces/{ns}/judgments/{name}/detach`              | `ns.judgments.detach(name)`                                      | `ns.judgments.detach(name)`                                          |
| `POST /namespaces/{ns}/judgments/{name}/suggest_parts`       | `ns.judgments.suggest_parts(name, count=5)`                      | `ns.judgments.suggestParts(name, { count: 5 })`                      |
| `GET /namespaces/{ns}/evaluations/{id}`                      | `ns.evaluation(id)`                                              | `ns.evaluation(id)`                                                  |
| `GET /templates`                                             | `db.templates.list(prefix=...)`                                  | `db.templates.list({ prefix })`                                      |
| `GET /engines`                                               | `db.engines.list()`                                              | `db.engines.list()`                                                  |

Namespace names and document ids may contain `/`. The SDKs send it as `%2F`, as the API requires. A [template](/guides/templates) is a namespace handle on a prefix ending in `/*`, such as `db.namespace("acme/prod/*")`, and its `*` is sent as it is.

## Requests and responses are the API's JSON

Request and response bodies use the API's own field names (`wait_for`, `top_k`, `rank_by`) in both languages. Any JSON body in these docs, or one copied from the dashboard's query builder, can be passed to the SDK unchanged. Options that are not part of a JSON body follow each language's conventions, such as `include_history=True` in Python and `{ includeHistory: true }` in TypeScript.

The types are strict:

* **Answers and definitions** are discriminated unions on `type`. In TypeScript, `answer.p` type-checks only after `answer.type === "bool"`.
* **Filters** are typed tuples: `[field, op, value]`, `["And" | "Or", [...]]` or `["Not", filter]`. An unknown operator is a type error.

In Python, filters are tuples. mypy cannot check a nested tuple literal, so use `And`, `Or` and `Not`:

```python theme={null}
from vainona import And, Not

ns.query(filters=And(
    ("attributes.plan", "Eq", "pro"),
    Not(("answers.needs_escalation.freshness", "Eq", "stale")),
))
```

Python responses are plain dicts, typed with `TypedDict`s in `vainona.types`. Nothing is validated at runtime, so fields and enum values the API adds later never break your client.

## Errors and retries

Every error response becomes an `ApiError` with the HTTP `status`, the API's `code` (such as `budget_exceeded` or `too_large`), its `message` and `details`. An error response that is not the API's JSON, such as a proxy's error page, or a success whose body is not JSON, gets the code `unknown`, a generic message and, when the response has one, `details.request_id`. Network errors, and timeouts in Python, are raised as they are.

Each call is retried at most twice by default. Set `max_retries` in Python or `maxRetries` in TypeScript on the client to change it.

* **`429` or `503` with `Retry-After`.** Retried after waiting that long, for any call: the API refused it without acting on it, or it was a write, which is always safe to repeat. A `Retry-After` over 60 seconds (`MAX_RETRY_AFTER_SECONDS`) is not waited out, and the error is returned at once. A daily allowance that is used up sends no `Retry-After` and says when it renews in `details.resets_at`.
* **Network errors, timeouts, `500`, `502`, `503` and `504`.** Retried only when the call is safe to repeat: a read (every `GET`, and `query`), a `write`, which is idempotent by construction, or any call with an idempotency key. The wait starts at 0.5 seconds and doubles on each retry up to 8 seconds, with random jitter, unless the response gives a `Retry-After`.
* **Anything else** is returned at once, including every other `4xx`, and a failed call that is not safe to repeat, such as creating a judgment without a key.

### Idempotency keys

Every call that changes something takes an idempotency key, sent as the `Idempotency-Key` header: `idempotency_key="..."` in Python and `{ idempotencyKey: "..." }` as the last argument in TypeScript. While the API still has the original response, a request with the same key gets that response back instead of running again. Use one key per logical request, and reuse it only for the same request. A call with a key is retried like a write.

<CodeGroup>
  ```python Python theme={null}
  ns.judgments.create(
      name="needs_escalation",
      type="bool",
      question="Does this ticket need escalation?",
      idempotency_key="create-needs-escalation-v1",
  )
  ```

  ```ts TypeScript theme={null}
  await ns.judgments.create(
    { name: "needs_escalation", type: "bool", question: "Does this ticket need escalation?" },
    { idempotencyKey: "create-needs-escalation-v1" },
  );
  ```
</CodeGroup>

Writes need no key to be retried safely; a key only saves the second write when the first one landed.

## Importing documents

`ns.import_documents(documents)` in Python and `ns.importDocuments(documents)` in TypeScript upsert a large set of documents. They read `documents` as they go, from any iterable (and in TypeScript any async iterable), so an import never has to fit in memory.

* **Batches.** Each write holds up to 1,000 documents or 64 MB, the most the API takes.
* **Concurrency.** Up to `concurrency` writes at once, 4 by default. Python runs them on a thread pool.
* **Retries.** Each batch has its own idempotency key, reused on its retries, which follow the client's rules above.
* **Repeated ids.** No id is in two writes at once. A document whose id is still being written waits for that write, so when your input repeats an id, the later document is the one that stays.
* **Failures.** A batch that still fails after its retries is recorded, and the import carries on. `stop_on_failure=True` (`stopOnFailure: true`) stops starting new batches after the first failure.
* **Progress.** `on_progress` (`onProgress`) is called after each batch with the totals so far.

It returns a summary: `documents` and `batches` written, `failures`, each with the batch's `ids` and its `error`, and `stopped`. A write commits all of its documents or none, and writing them again is safe, so once you have fixed the cause, write a failed batch's ids again. See [import existing data](/guides/import-existing-data).
