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

# Text generation

> Generate text, use vision and tools, and route across providers with the SDK or raw REST.

<div className="ndoc-brand-strip"><span className="ndoc-brand"><span className="ndoc-brand-mark"><img src="https://cdn.photogenius.ai/new-ai-images/site/v1/landing/marks/marks/6cf26e8b/openai.svg" alt="" width="28" height="28" loading="lazy" /></span><span>OpenAI</span></span><span className="ndoc-brand"><span className="ndoc-brand-mark"><img src="https://cdn.photogenius.ai/new-ai-images/site/v1/landing/marks/marks/4be04284/claude.svg" alt="" width="28" height="28" loading="lazy" /></span><span>Claude</span></span><span className="ndoc-brand"><span className="ndoc-brand-mark"><img src="https://cdn.photogenius.ai/new-ai-images/site/v1/landing/marks/marks/263e7dab/gemini.svg" alt="" width="28" height="28" loading="lazy" /></span><span>Gemini</span></span><span className="ndoc-brand"><span className="ndoc-brand-mark"><img src="https://cdn.photogenius.ai/new-ai-images/site/v1/landing/marks/marks/21c5faae/deepseek.svg" alt="" width="28" height="28" loading="lazy" /></span><span>DeepSeek</span></span><span className="ndoc-brand"><span className="ndoc-brand-mark"><img src="https://cdn.photogenius.ai/new-ai-images/site/v1/landing/marks/marks/44c15113/qwen.svg" alt="" width="28" height="28" loading="lazy" /></span><span>Qwen</span></span></div>

NinjaChat has two text APIs on the same model network. **Responses** is recommended for new applications; **Chat Completions** is the OpenAI-compatible interface for role-based messages.

<CardGroup cols={2}>
  <Card title="Start with Responses" icon="list-check" href="#responses-api">
    Best for new text, vision, tools, structured output, and typed streaming integrations.
  </Card>

  <Card title="Keep Chat Completions" icon="comments" href="#chat-completions">
    Best when your application already uses OpenAI-style role messages or an OpenAI client.
  </Card>
</CardGroup>

## Responses API

<CodeGroup>
  ```typescript TypeScript SDK theme={null}
  import { NinjaChat } from "@ninjachat/sdk";

  const client = new NinjaChat({ apiKey: process.env.NINJACHAT_API_KEY! });
  const response = await client.responses.create({
    model: "ninja/auto",
    instructions: "Be concise and concrete.",
    input: "Explain edge caching in one paragraph.",
    max_output_tokens: 200,
  });

  console.log(response.output_text);
  console.log(response.cost_usd, response.request_id);
  ```

  ```python Python SDK theme={null}
  import os
  from ninjachat import NinjaChat

  client = NinjaChat(api_key=os.environ["NINJACHAT_API_KEY"])
  response = client.responses.create(
      model="ninja/auto",
      instructions="Be concise and concrete.",
      input="Explain edge caching in one paragraph.",
      max_output_tokens=200,
  )

  print(response["output_text"])
  print(response["cost_usd"], response["request_id"])
  ```

  ```bash cURL theme={null}
  curl https://www.ninjachat.ai/api/v1/responses \
    -H "Authorization: Bearer $NINJACHAT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model":"ninja/auto","input":"Explain edge caching in one paragraph.","max_output_tokens":200}'
  ```
</CodeGroup>

## Chat Completions

<CodeGroup>
  ```typescript TypeScript SDK theme={null}
  const completion = await client.chat.completions.create({
    model: "gpt-5.6-luna",
    messages: [
      { role: "system", content: "Be concise and concrete." },
      { role: "user", content: "Explain edge caching in one paragraph." },
    ],
    max_completion_tokens: 200,
  });

  console.log(completion.choices[0].message.content);
  console.log(completion.resolved_model, completion.provider);
  ```

  ```python Python SDK theme={null}
  completion = client.chat.completions.create(
      model="gpt-5.6-luna",
      messages=[
          {"role": "system", "content": "Be concise and concrete."},
          {"role": "user", "content": "Explain edge caching in one paragraph."},
      ],
      max_completion_tokens=200,
  )

  print(completion["choices"][0]["message"]["content"])
  ```

  ```bash cURL theme={null}
  curl https://www.ninjachat.ai/api/v1/chat/completions \
    -H "Authorization: Bearer $NINJACHAT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model":"gpt-5.6-luna","messages":[{"role":"user","content":"Explain edge caching in one paragraph."}]}'
  ```
</CodeGroup>

## Parameters

The two endpoints share routing, billing, and most sampling controls, but not every field exists on both. The **API** column says where a parameter is accepted; sending a field to the other endpoint returns `400 unsupported_parameter`.

| Parameter               | API       | Type             | Default          | Description                                                                                                                                         |
| ----------------------- | --------- | ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`                 | Both      | string           | —                | One concrete [model ID](/models) or `ninja/auto`. Mutually exclusive with `models`.                                                                 |
| `models`                | Both      | array            | —                | 1–15 ordered candidates for [fallback routing](/fallback-chains). `ninja/auto` is allowed only as the first entry. Mutually exclusive with `model`. |
| `input`                 | Responses | string or array  | required         | Text, or 1–100 typed items (`message`, `function_call`, `function_call_output`).                                                                    |
| `instructions`          | Responses | string           | —                | Developer instruction sent ahead of `input`.                                                                                                        |
| `messages`              | Chat      | array            | required         | 1–100 `developer`, `system`, `user`, `assistant`, or `tool` messages.                                                                               |
| `max_output_tokens`     | Responses | integer          | —                | Output ceiling, up to 131,072.                                                                                                                      |
| `max_completion_tokens` | Chat      | integer          | —                | Output ceiling, up to 131,072. `max_tokens` is accepted as an alias — see [OpenAI compatibility](/openai-compatibility).                            |
| `temperature`           | Both      | number           | provider default | 0–2.                                                                                                                                                |
| `top_p`                 | Both      | number           | —                | Nucleus sampling (0–1).                                                                                                                             |
| `stop`                  | Chat      | string or array  | —                | Up to 4 stop sequences.                                                                                                                             |
| `frequency_penalty`     | Chat      | number           | —                | -2 to 2. Penalizes tokens by how often they've already appeared.                                                                                    |
| `presence_penalty`      | Chat      | number           | —                | -2 to 2. Penalizes tokens that have appeared at all.                                                                                                |
| `seed`                  | Chat      | integer          | —                | Best-effort deterministic sampling seed.                                                                                                            |
| `n`                     | Chat      | integer          | `1`              | Only `1` is accepted; anything else is `400 unsupported_value`.                                                                                     |
| `stream`                | Both      | boolean          | `false`          | Typed SSE iteration in both SDKs — [Streaming](/streaming).                                                                                         |
| `stream_options`        | Chat      | object           | —                | `{ "include_usage": true }`. Responses streams always finish with usage.                                                                            |
| `response_format`       | Chat      | object           | —                | `{"type": "text" \| "json_object" \| "json_schema"}`; `json_schema` wraps `{ name, schema, strict }` under `json_schema`.                           |
| `text.format`           | Responses | object           | —                | Same three formats, flat: `{"type": "json_schema", "name": "...", "schema": {...}, "strict": true}`.                                                |
| `tools`                 | Both      | array            | —                | Up to 32 function tools. The shapes differ — see [Function calling](#function-calling).                                                             |
| `tool_choice`           | Both      | string or object | —                | `"auto"`, `"none"`, `"required"`, or one named function. The object shapes differ — see [Function calling](#function-calling).                      |
| `parallel_tool_calls`   | Both      | boolean          | —                | Let the model emit several tool calls in one turn.                                                                                                  |
| `store`                 | Responses | boolean          | `false`          | Only `false` is accepted. `true` returns `400 store_unsupported`; `previous_response_id` returns `400 previous_response_unsupported`.               |
| `metadata`              | Responses | object           | —                | String map (values ≤ 512 chars) echoed back on the response object.                                                                                 |
| `user`                  | Both      | string           | —                | Opaque end-user identifier for your own tracking (max 256 chars).                                                                                   |
| `routing`               | Both      | object           | project defaults | Strategy, provider allow/exclude/order, fallbacks, data policy, caching, and `max_cost_usd` (max `1000`).                                           |

### Spend cap

`routing.max_cost_usd` is checked twice. Before any provider call the gateway computes the **maximum token hold** — your input estimate plus the output ceiling at the candidate models' metered rates — and rejects the request with `400 max_cost_exceeded` (the body includes `maximum_hold_usd`) if that hold is above the cap. At settle time the metered charge is capped at `max_cost_usd`; usage priced above it is absorbed by NinjaChat, never billed to you. See [Spend controls](/budget-routing).

### Structured output

Ask for JSON with `response_format` (Chat Completions) or `text.format` (Responses). `json_object` returns any valid JSON; `json_schema` constrains it to your schema (`strict` defaults to `true`). Models without JSON mode return `400 model_not_json_capable` — filter on the `json_mode` capability in [`GET /models`](/models).

## Multi-turn conversations

Two ways to carry a conversation:

<Tabs>
  <Tab title="Responses">
    Send the conversation items your application stores. NinjaChat API v1 is stateless and does not retain response bodies.

    ```typescript theme={null}
    const response = await client.responses.create({
      model: "gpt-5.6-luna",
      input: conversationItems,
      store: false,
    });
    ```

    [State management →](/sessions)
  </Tab>

  <Tab title="Manual history">
    Pass the full conversation every request:

    ```json theme={null}
    {
      "model": "gpt-5.6-luna",
      "messages": [
        {"role": "user", "content": "What is photosynthesis?"},
        {"role": "assistant", "content": "Photosynthesis converts sunlight..."},
        {"role": "user", "content": "How does it compare to solar panels?"}
      ]
    }
    ```
  </Tab>
</Tabs>

## Vision input

Any [vision-capable model](/models) accepts images by sending `content` as an array of parts instead of a plain string — mix `text` and `image_url` parts in one message:

```json theme={null}
{
  "model": "gpt-5.6-luna",
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "What's in this image?"},
        {"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg", "detail": "auto"}}
      ]
    }
  ]
}
```

`image_url.url` accepts a public HTTPS URL or a base64 data URL. `detail` is optional (`auto`, `low`, or `high`). Sending images to a model that isn't vision-capable returns a `model_not_vision_capable` error listing which models support it.

## Function calling

Pass OpenAI-style `tools` and NinjaChat routes the model's tool calls back to you the same way OpenAI does — the model doesn't execute anything itself, it just tells you what to call:

```json theme={null}
{
  "model": "gpt-5.6-luna",
  "messages": [{"role": "user", "content": "What's the weather in Austin?"}],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get the current weather for a city",
      "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    }
  }],
  "tool_choice": "auto"
}
```

Only [tool-capable models](/models) accept `tools` — passing them to a model that doesn't support tool calling returns a `model_not_tool_capable` error.

On `/responses` the definition is flat (no `function` wrapper) and `tool_choice` names the function directly:

```json theme={null}
{
  "model": "gpt-5.6-luna",
  "input": "What's the weather in Austin?",
  "tools": [{
    "type": "function",
    "name": "get_weather",
    "description": "Get the current weather for a city",
    "parameters": {
      "type": "object",
      "properties": {"city": {"type": "string"}},
      "required": ["city"]
    }
  }],
  "tool_choice": {"type": "function", "name": "get_weather"}
}
```

The same forced choice on Chat Completions is `{"type": "function", "function": {"name": "get_weather"}}`. Tool calls come back as `function_call` output items on Responses and as `tool_calls` on Chat Completions; send results back as `function_call_output` input items or `tool` messages respectively. Only `function` tools are supported — a built-in tool type returns `400 unsupported_tool`.

## Routing is explicit

Everything else about the request stays the same — only the model and routing fields change:

| Field                                         | Behavior                                                                                                                  |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `model: "gpt-5.6-luna"`                       | That exact model                                                                                                          |
| `model: "ninja/auto"`                         | Use the Ninja Router (`ninja/auto`).                                                                                      |
| `routing.strategy: "latency"`                 | Prefer the fastest eligible provider rail.                                                                                |
| `routing.strategy: "cost"`                    | Prefer the lowest-cost eligible provider rail.                                                                            |
| `models: ["claude-sonnet-5", "gpt-5.6-luna"]` | Try an explicit ordered fallback set.                                                                                     |
| `routing.max_cost_usd: 0.05`                  | Reject the request (`400 max_cost_exceeded`) if the maximum token hold exceeds the cap, and cap the settled charge at it. |
| `routing.data_policy: "zero_retention"`       | Require rails matching that data policy.                                                                                  |

<Tip>
  Try any of these live in the [Playground](https://www.ninjachat.ai/developers/playground) — every run shows the exact request that made it.
</Tip>
