# VERIS API Integration Guide

> One endpoint per AI capability. Multi-provider routing, observability, and cost controls included.

This guide is for developers integrating VERIS into an application. It covers the live `/api/v1` API surface, authentication, request/response shapes, error handling, and copy-paste code samples.

## Base URL

Production:

```text
https://veris.digital/api/v1
```

Health, OpenAPI, and CORS preflight are available without authentication. All other endpoints require a bearer token.

## Authentication

Every request (except `/health`) must include an `Authorization` header with a VERIS API key:

```text
Authorization: Bearer <VERIS_API_KEY>
```

API keys use the `veris_` prefix. Legacy `aios_` keys are still accepted for backwards compatibility.

### Creating a key

1. Open VERIS and sign in.
2. Go to **Applications** and select (or create) an application.
3. Open **Developer → API keys**.
4. Reveal the key once and copy it into your application's environment as `VERIS_API_KEY`.

Each key is scoped to its workspace. It can access any application and environment in that workspace.

## Capabilities

VERIS exposes one endpoint per capability. The router selects a provider; the capability executor owns the API call.

| Capability | Status | Endpoint | Notes |
|------------|--------|----------|-------|
| Chat | Live | `POST /api/v1/chat` | VERIS-native request/response shape. |
| Chat (OpenAI-compatible) | Live | `POST /api/v1/chat/completions` | Drop-in replacement for OpenAI SDK clients, including streaming. |
| Embeddings | Live | `POST /api/v1/embeddings` | OpenAI-compatible request/response shape. |
| Model listing | Live | `GET /api/v1/models` | Models this key can route to, scoped to its application. |
| Re-index callback | Live | `POST /api/v1/reindex/{jobId}` | Report progress for a VERIS-driven re-embedding run. |
| Image input (vision) | Live | `POST /api/v1/chat/completions` | `image_url` content parts. Only image-capable models are eligible. |
| Rerank | Planned | — | Rank passages against a query. |
| Speech to text | Planned | — | Transcribe audio with routing + fallback. |
| Text to speech | Planned | — | Synthesise natural voices. |
| Image generation | Planned | — | Route image requests by cost and quality. |
| Web search | Planned | — | Grounded results plus source citations. |

Call `GET /api/v1/health` to retrieve the current live capability list at runtime.

## Health check

Use this to prove reachability before sending real requests.

```bash
curl https://veris.digital/api/v1/health
```

Response:

```json
{
  "status": "ok",
  "version": "1.0.0",
  "capabilities": ["chat", "embed"],
  "timestamp": "2026-07-23T12:00:00.000Z"
}
```

## Chat completion

`POST /api/v1/chat` is the canonical VERIS chat endpoint.

### Request

```bash
curl -X POST https://veris.digital/api/v1/chat \
  -H "Authorization: Bearer $VERIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "application": "resume-parser",
    "environment": "production",
    "workload": "extract",
    "input": "Extract name, email, and years of experience from this resume."
  }'
```

### Request fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `application` | string | Yes | Application slug. Use `resume-parser` for the canonical example, or your own application slug. Legacy alias: `project`. OpenAI SDK clients may send it as `model` on this endpoint only. |
| `environment` | string | No | Environment name. Defaults to `production`. |
| `workload` | string | No | Workload tag for routing and cost attribution. |
| `task` | string | No | Optional task classifier. |
| `input` | string | No | Shorthand for a single user message. Either `input` or `messages` is required. |
| `messages` | array | No | Array of `{ role, content }` messages. Either `input` or `messages` is required. |
| `maxCostUsd` | number | No | Reject the request if the cheapest candidate exceeds this cost. |

Any other field is rejected with a `400` naming the field. VERIS never accepts a field and then ignores it. The former `priority` hint has been retired: routing behaviour comes from the application's routing policy, and per-request control is expressed as `maxCostUsd` or the `x-veris-*` headers on `/api/v1/chat/completions`.

### Response

```json
{
  "requestId": "req_5f3a...",
  "status": "ok",
  "provider": "openai",
  "model": "gpt-4o-mini",
  "text": "{\"name\":\"Ada Lovelace\",...}",
  "inputTokens": 42,
  "outputTokens": 128,
  "latencyMs": 812,
  "costUsd": 0.00034,
  "fallbackUsed": false,
  "workload": "extract"
}
```

| Field | Description |
|-------|-------------|
| `requestId` | UUID that deep-links to Request History in VERIS. |
| `status` | `ok` or `fallback`. |
| `provider` | Selected provider ID, e.g. `openai`. |
| `model` | Selected model key, e.g. `gpt-4o-mini`. |
| `text` | Assistant response text. |
| `inputTokens` / `outputTokens` | Token counts from the provider. |
| `latencyMs` | Total request latency. |
| `costUsd` | Estimated cost in USD. |
| `fallbackUsed` | True if the router fell back from the primary candidate. |
| `workload` | Echo of the workload tag. |

## OpenAI-compatible chat

`POST /api/v1/chat/completions` is a genuine OpenAI-compatible endpoint, not a renamed VERIS endpoint. Routing, budgets, fallback and evidence are identical to `/api/v1/chat`.

Two rules make it predictable:

- `model` always means a model. Send `veris-auto` to let VERIS choose. Any other value must name a real model. `model` never names an application.

> **Common mistake.** Sending your application name as the model, for example `"model": "amatch"`, is rejected rather than silently ignored. Send `"model": "veris-auto"` and pass the application as the `x-veris-application` header (or the `application` field on `/api/v1/chat`). To constrain which models VERIS may choose, set model preferences on the workload.
- VERIS routing context travels in `x-veris-*` headers, so the request body stays a standard OpenAI body.

| Header | Required | Description |
|--------|----------|-------------|
| `x-veris-application` | No | Application slug. Defaults to the application the API key is bound to. |
| `x-veris-environment` | No | Environment name. Defaults to the key's environment. |
| `x-veris-workload` | No | Workload tag for routing and cost attribution. |

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://veris.digital/api/v1",
  apiKey: process.env.VERIS_API_KEY,
  defaultHeaders: {
    "x-veris-application": "resume-parser",
    "x-veris-environment": "production",
    "x-veris-workload": "extract",
  },
});

const res = await client.chat.completions.create({
  model: "veris-auto", // VERIS selects the model
  messages: [
    { role: "system", content: "You are a resume parser. Reply with JSON." },
    { role: "user", content: "Extract name, email, and years of experience from this resume." },
  ],
});

console.log(res.choices[0].message.content);
```

### Supported

- Streaming (`stream: true`) with standard SSE chunks, a final `finish_reason`, and `[DONE]`.
- Tool calling (`tools`, `tool_choice`).
- Structured output (`response_format`, including `json_schema`).
- Image input (`image_url` content parts). Requests with images only route to models that accept images.
- Provider-specific controls through `provider_options`.
- `Idempotency-Key` on non-streaming requests. A streamed response cannot be replayed, so sending the header with `stream: true` is rejected.

### Rejected, never ignored

Any standard field VERIS cannot honour returns a `400` naming the exact field. Nothing is accepted and silently discarded.

### Model listing

```bash
curl "https://veris.digital/api/v1/models?application=resume-parser" \
  -H "Authorization: Bearer $VERIS_API_KEY"
```

Returns the models this key can actually route to for that application, using the same eligibility truth as routing. It is not a global catalogue, and it never lists another tenant's models.


## Embeddings

`POST /api/v1/embeddings` is OpenAI-compatible.

### Request

```bash
curl -X POST https://veris.digital/api/v1/embeddings \
  -H "Authorization: Bearer $VERIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "application": "resume-parser",
    "environment": "production",
    "input": "Senior software engineer with 8 years of backend experience."
  }'
```

`input` can be a single string or an array of strings.

### Index locking (important for vector search)

The first embeddings call for an application + `workload` locks an **embedding
index** to the exact model and vector size that answered it. After that, VERIS
keeps failing over across providers that serve the same model, so your vectors
stay in one space while your provider dependency disappears.

- Requesting a different `model` or `dimensions` afterwards returns **409**
  (`index_model_conflict` / `index_dimension_conflict`) instead of silently
  corrupting your index.
- If no connected provider serves the locked model, you get **422** with
  `index_model_unavailable`.
- To change models later, approve a re-index migration in VERIS. Full protocol:
  `docs/EMBEDDING_INDEX.md`.


### Response

```json
{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [0.0023, -0.0081, ...]
    }
  ],
  "model": "text-embedding-3-small",
  "provider": "openai",
  "usage": {
    "prompt_tokens": 12,
    "total_tokens": 12
  },
  "requestId": "req_5f3a...",
  "dimensions": 1536,
  "latencyMs": 214,
  "costUsd": 0.0000024,
  "fallbackUsed": false
}
```

VERIS extensions (`requestId`, `dimensions`, `latencyMs`, `costUsd`, `fallbackUsed`, `provider`) are safe to ignore for OpenAI clients.

### Skip re-ingesting a document you already ingested

Parsing, chunking and embedding the same document twice costs money and adds
nothing. Before you ingest, ask VERIS whether that exact artifact already
exists for this application:

```bash
curl -X POST https://veris.digital/api/v1/ingestion/fingerprint \
  -H "Authorization: Bearer $VERIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "application": "my-app",
    "document_hash": "sha256:2f1c9b...",
    "parser_version": "pdf-2",
    "chunking_version": "v3"
  }'
```

`{"reused": true, "artifact_ref": "vector-store/doc_8814", ...}` means your
own artifact is still valid, so skip the work. `{"reused": false}` means
ingest as normal, then repeat the call with `artifact_ref` (and optionally
`chunk_count`, `ttl_days`) so the next identical upload is free.

Reuse identity is document hash + parser version + chunking version +
embedding model + dimensions + index. Change any one of them and it is a
different artifact, so a re-parse or a model migration never reuses stale
vectors. Reuse never crosses applications, and VERIS stores the hash and the
versions, never your document content.

## Error handling

Every non-2xx response uses this shape:

```json
{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key is invalid.",
    "meta": {}
  }
}
```

`meta` is optional and may contain routing candidates, budget delta, or other context.

### Error codes

| Code | HTTP | When |
|------|------|------|
| `missing_api_key` | 401 | No `Authorization: Bearer …` header. |
| `invalid_api_key` | 401 | Malformed, revoked, expired, or unknown key. |
| `invalid_request` | 400 | Request body failed validation. |
| `project_not_found` | 404 | Application slug does not exist in this workspace. |
| `environment_not_found` | 404 | Environment does not exist for this application. |
| `budget_blocked` | 402 | Monthly budget cap reached. |
| `no_candidates` | 422 | No provider satisfies the routing policy. |
| `rate_limited` | 429 | Too many requests. Back off with jitter. |
| `internal` | 500 | Every upstream provider failed after fallbacks. |

## Rate limits

Limits apply per workspace. Hitting a limit returns `429` with code `rate_limited`.

| Plan | Requests | Concurrent |
|------|----------|------------|
| Free | 60 / minute | 5 |
| Team | 600 / minute | 50 |
| Business | 3,000 / minute | 250 |
| Enterprise | Custom | Custom |

If VERIS cannot read its own rate-limit counters (a metering outage), the
default policy is fail-open: your request is admitted and a
`rate_limit_metering_failed` event is recorded. Self-hosted deployments can set
`VERIS_RATE_LIMIT_FAIL_MODE=closed` to reject instead.

Rejections are visible. Rate-limited, invalid and over-budget requests are
written to Request History with the same `x-veris-request-id` you get back, so
support can find them by id.

## Budget scopes

Caps nest, and the tightest cap wins: organization, workspace, application,
workload, API key. Workload caps use the `workload` value you send with the
request. API key caps apply to the key that authenticated the call.

## Versioning

VERIS guarantees backwards compatibility within a major version. `/api/v1` endpoints remain stable; breaking changes ship in `/v2`. Additive changes (new optional fields, new capabilities) may appear in `/v1` at any time.

## OpenAPI spec and Postman collection

Machine-readable specs are available at:

- JSON: `GET /api/v1/openapi`
- YAML: `GET /api/v1/openapi/yaml`
- Postman collection: `GET /api/v1/postman`

Import the YAML into Postman, Insomnia, or any OpenAPI generator, or download the ready-made collection.

## Quickstart: 3 steps

1. **Send a request**

   ```bash
   curl -X POST https://veris.digital/api/v1/chat \
     -H "Authorization: Bearer $VERIS_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"application":"resume-parser","environment":"production","workload":"extract","input":"Extract name, email, and years of experience from this resume."}'
   ```

2. **Read the response**

   ```json
   {
     "requestId": "req_5f3a...",
     "provider": "openai",
     "model": "gpt-4o-mini",
     "text": "{...}"
   }
   ```

3. **Inspect in VERIS**

   Open **Activity → Request History** in VERIS and find the request by its `requestId`. You will see the router decision, cost breakdown, latency, and a one-click replay button.

## Webhooks and budgets (optional)

- **Webhooks:** receive events for anomalies, budget alerts, and incidents. Configure at **Developer → Webhooks**.
- **Budgets:** cap monthly spend per application. Configure at **Money → Budgets**.

## Support

For questions or issues, open **Mission Control → Help** inside VERIS or contact your workspace admin.
