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

# Integration guide

> Point your voice agent to our Context Router endpoint to add response caching without changing your agent logic.

The SLNG Context Router is an OpenAI-compatible chat endpoint. You send a normal Chat Completions request, SLNG decides which underlying model answers, and you get the answer back in the standard OpenAI shape. Existing OpenAI SDKs and most voice-agent orchestrators work unchanged.

## 1. Endpoint and authentication

The router is one POST endpoint that speaks the OpenAI Chat Completions protocol.

```text theme={null}
POST <your-base-url>/chat/completions
Authorization: Bearer <SLNG_API_KEY>
Content-Type: application/json
```

> **Your base URL is region-specific.**
>
> The router is deployed in several regions, and your base URL names the region your traffic is served from. It looks like `https://<region>.context-router.slng.ai/v1`
>
> (India)`https://india.context-router.slng.ai/v1`
>
> (US)`https://us.context-router.slng.ai/v1`
>
> (Indonesia) `https://indonesia.context-router.slng.ai/v1`
>
> (EU) `https://eu.context-router.slng.ai/v1`

You authenticate with **only** your `SLNG_API_KEY`, passed as a Bearer token. The OpenAI SDK's `api_key` parameter sets this header for you.

```python theme={null}
from openai import OpenAI

client = OpenAI(
    # Region-specific; use the exact base URL SLNG gave you (see note above).
    base_url="https://india.context-router.slng.ai/v1",
    api_key="YOUR_SLNG_API_KEY",
)
```

***

## 2. Making a request

A request is a standard OpenAI Chat Completions call.

```python theme={null}
resp = client.chat.completions.create(
    model="slng/auto",
    messages=[
        {"role": "system", "content": "You are a helpful clinic assistant."},
        {"role": "user", "content": "What are your opening hours?"},
    ],
)
print(resp.choices[0].message.content)
```

### Streaming

Streaming works exactly as in OpenAI, for **every** model the router serves (reasoning and non-reasoning alike). **Always set `stream_options.include_usage=True`** so you receive the final usage trailer chunk.

```python theme={null}
stream = client.chat.completions.create(
    model="slng/auto",
    messages=[...],
    stream=True,
    stream_options={"include_usage": True},
)
```

What to expect on the stream:

* It is standard OpenAI SSE: `data: {...}` chunks ending with `data: [DONE]`.
* **You only ever receive the final answer text.** Whatever model answers, the stream carries just the reply itself — nothing else that could reach your TTS.
* **All answer content arrives before the `finish_reason: "stop"` chunk**, so the usual client pattern of stopping at `finish_reason: "stop"` is safe; you will never lose the tail of an answer that way.
* **The `usage` block rides the final chunk** (that is why `stream_options.include_usage=True` matters). A few underlying providers do not report usage on streams; in that case the fields are simply absent.
* **The stream always ends cleanly.** If the underlying model fails mid-stream, the router still sends a final `finish_reason: "stop"` chunk and `[DONE]` instead of leaving your client hanging on a truncated stream.

***

## 3. What you control (the request body)

These are the fields you set. The router reads only these from your request body (plus the optional `template_variables` and `slng_config` extensions):

| Field                          | What it does                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`                        | `slng/auto` lets SLNG pick from the models configured for your org. Or pass a specific configured model name to target it directly.                                                                                                                                                                                                                                                                                              |
| `messages`                     | Standard OpenAI messages array (system / user / assistant / tool).                                                                                                                                                                                                                                                                                                                                                               |
| Sampling params                | `temperature`, `top_p`, `max_tokens` / `max_completion_tokens`, etc. Passed through as usual.                                                                                                                                                                                                                                                                                                                                    |
| `stream`                       | `true` for SSE streaming, `false` for a single JSON response.                                                                                                                                                                                                                                                                                                                                                                    |
| `stream_options.include_usage` | Set `true` to get the usage trailer.                                                                                                                                                                                                                                                                                                                                                                                             |
| `tools` / `tool_choice`        | Standard tool-calling. Tool turns always go to the LLM (caching steps aside for them).                                                                                                                                                                                                                                                                                                                                           |
| `template_variables`           | **SLNG extension — use it whenever your prompt has per-call values.** A flat `{name: value}` object of per-call values that SLNG substitutes into `{{name}}` placeholders in your system prompt. Sending values this way (instead of building the prompt string yourself) is what lets SLNG optimize your calls properly; see [section 7](#7-per-call-values-template_variables). Pass it via your SDK's "extra body" mechanism. |
| `slng_config`                  | **Optional SLNG extension.** Your model configuration sent inline in the request (your own model endpoints and provider API keys), instead of using the configuration SLNG stored for your org. See [section 8](#8-bring-your-own-model-configuration-slng_config). Pass it via your SDK's "extra body" mechanism.                                                                                                               |

### `model: "slng/auto"` vs a named model

* **`slng/auto`** (recommended): SLNG routes the request using the model configuration set up for your org. This is what lets SLNG tune which model answers without you changing any code.
* **A named model**: if you have created a BYOK LLM in SLNG, you can pass the name of it to target that model directly.

***

## 4. Identity, agents, and sessions

Three things travel alongside your request but are **not** fields in the JSON body. Your org identity is fully managed by SLNG; the other two are HTTP headers you send on every request.

* **Internal auth + your org identity (managed for you).** SLNG authenticates your API key and resolves your organization context. You never set this yourself, and there is no body field for it.
* **Agent scope — the `X-Slng-Agent-Id` header.** Send this on every request. It names the logical agent a call belongs to, which lets SLNG apply the right per-agent handling and optimizations. Use a stable value per agent, the same one on every call that agent makes. **Best practice: put a version or date suffix in the value** (for example `clinic-scheduler-v1` or `clinic-scheduler-2026-07`). Whenever you change that agent's configuration in a meaningful way, most importantly its system prompt, bump the suffix (`...-v2`, a new date, etc.) so the new version gets a fresh identity. Reusing the old id after a prompt change means SLNG keeps treating it as the same agent, which can leave optimizations tuned to the previous version in effect. Keeping the id in step with your agent version avoids that. It is a request header, not a body field.
* **Session scope — the `X-Slng-Session-Id` header.** Send this on every request. It identifies a single call (conversation) so SLNG can track per-call state for session-aware features. Use a value unique to each call, like a UUID, and keep it the same across all the turns of that call. It is a request header, not a body field.

The practical takeaway: your JSON body is the standard OpenAI request plus `template_variables` for your per-call values (see [section 5](#5-per-call-values-template_variables), and please do use it). Org identity is handled for you; `X-Slng-Agent-Id` and `X-Slng-Session-Id` are headers you send on every request.

***

## 5. Per-call values (`template_variables`)

Voice agents need per-call values, the caller's name, their city, an appointment time, inside the system prompt. Instead of building those strings yourself before every request, you write a placeholder once and let SLNG fill it in.

> **This is one of the most important integration steps in this guide.** If your system prompt contains *anything* that changes from call to call, send it through `template_variables`. Do not render it into the prompt text yourself. When per-call values are baked into the prompt as plain text, every call looks slightly different to SLNG and it cannot apply its optimizations to your traffic properly. With placeholders + `template_variables`, SLNG sees your stable prompt and the per-call values separately, which is exactly what its optimizations need. The model sees the same final prompt either way, but the difference in the speed and cost SLNG can deliver for you is real.

The pattern in one glance:

```python theme={null}
# DON'T: build the prompt string yourself per call
{"role": "system", "content": f"You are calling {caller_name} in {city}."}

# DO: keep placeholders in the prompt, send the values separately
{"role": "system", "content": "You are calling {{caller_name}} in {{city}}."}
# ...with, in the same request:
# "template_variables": {"caller_name": "Rajesh", "city": "Mumbai"}
```

**Syntax.** Use double braces around the variable name: `{{caller_name}}`, `{{city}}`. Names are letters, digits, and underscore. Single braces (`{name}`), Jinja, and `${var}` are NOT placeholders, only `{{name}}` is. (Limits: up to 64 variables per request, names up to 64 characters, values up to 4000 characters; exceeding these returns a `422`.)

**Where it substitutes.** When you send a `template_variables` object, the router replaces every `{{name}}` with the value you supplied in your **system prompt** (the system message), before it goes to the model.

Only the system message is touched. Your `user` and `assistant` message content is never modified, so a literal `{{...}}` a caller happens to type is left exactly as-is.

```python theme={null}
resp = client.chat.completions.create(
    model="slng/auto",
    messages=[
        {"role": "system", "content": "You are calling {{caller_name}} in {{city}}."},
        {"role": "user", "content": "hello?"},
    ],
    extra_body={
        "template_variables": {"caller_name": "Rajesh", "city": "Mumbai"},
    },
)
```

The model receives "You are calling Rajesh in Mumbai."

**Missing values are rejected.** If your request references a `{{name}}` you did not supply in `template_variables`, the router returns a `422`:

```json theme={null}
{
  "error": {
    "message": "Missing required template variables: appointment_time, city",
    "type": "invalid_request_error",
    "param": "template_variables",
    "code": "missing_template_variables"
  }
}
```

The error lists the missing variable names (sorted, deduplicated), only the names you need to supply. Extra variables you send that match no placeholder are ignored.

> **Watch out for literal double braces.** Any `{{...}}` in your system prompt is treated as a variable reference. If your prompt contains a literal `{{` for some other reason (a code sample, a templating example), it will be read as a placeholder and trigger a `missing_template_variables` 422 unless you supply a matching value. Avoid stray double braces in prompts you do not intend as variables. If you have a live integration that cannot avoid them, SLNG can put your org in a lenient mode where un-supplied placeholders are left as-is instead of 422ing (ask SLNG; see [section 10](#10-opt-in-features)).

\---Completions request, SLNG decides which underlying model answers, and you get the answer back in the standard OpenAI shape. Existing OpenAI SDKs and most voice-agent orchestrators work unchanged.

### Limits and errors

* The `slng_config` object must stay under **256 KB** when serialized; larger returns a 400.
* A config that fails validation returns a 400 starting with `invalid slng_config:` and a description of what is wrong. Your credentials are never included in these messages.
* Sending `slng_config` as anything other than a JSON object (a string, a list) returns a 422 with code `invalid_slng_config`.
* Sending it before SLNG has enabled it for your integration returns a 400 `inline slng_config is not enabled`.

## Related

<CardGroup cols={2}>
  <Card title="Context Router" icon="layers" href="/execution-layer/context-router">
    Where the router fits in the execution layer.
  </Card>

  <Card title="Connect ElevenLabs" icon="microphone" href="/llm-router/elevenlabs">
    Use the Context router as a custom model in Eleven labs.
  </Card>

  <Card title="Connect Vapi" icon="phone" href="/llm-router/vapi">
    Use the Context router as a custom model in Vapi.
  </Card>

  <Card title="Connect Retell" icon="bot" href="/llm-router/retell">
    Use the Context router as a custom model in Retell.
  </Card>
</CardGroup>
