> ## 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.

# TypeScript and Python SDKs

> Install the official NinjaChat SDKs for typed responses, streaming, media generation, routing, usage, and webhooks.

<Warning>
  NinjaChat API keys are server-side credentials. Load `NINJACHAT_API_KEY` from your deployment platform or secret manager; never include an `nj_sk_...` key in browser or mobile code.
</Warning>

## Choose an integration

<CardGroup cols={2}>
  <Card title="TypeScript SDK" icon="npm" href="#install">
    Typed clients for Node.js, Next.js and workers.
  </Card>

  <Card title="Python SDK" icon="python" href="#install">
    For Python services, notebooks and background workers.
  </Card>

  <Card title="Vercel AI SDK" icon="bolt" href="#vercel-ai-sdk">
    Official provider for the AI SDK — `generateText`, `streamText`, tools, and structured outputs, with the routing trace on `providerMetadata`.
  </Card>

  <Card title="OpenAI client" icon="arrows-rotate" href="/openai-compatibility">
    Keep an existing OpenAI integration and change the base URL, API key, and model ID.
  </Card>

  <Card title="Raw REST" icon="brackets-curly" href="/api-reference/create-a-stateless-response">
    Call the API without an SDK.
  </Card>
</CardGroup>

## Install

<CodeGroup>
  ```bash TypeScript theme={null}
  npm install @ninjachat/sdk
  ```

  ```bash Python theme={null}
  pip install ninjachat
  ```
</CodeGroup>

The TypeScript package requires Node.js 18 or newer. The Python package requires Python 3.10 or newer.

## Create a client

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

  const client = new NinjaChat({
    apiKey: process.env.NINJACHAT_API_KEY!,
  });
  ```

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

  client = NinjaChat(api_key=os.environ["NINJACHAT_API_KEY"])
  ```
</CodeGroup>

You do not need to configure a base URL. Both clients default to `https://www.ninjachat.ai/api/v1`.

<AccordionGroup>
  <Accordion title="Next.js route handler" icon="code">
    ```typescript app/api/generate/route.ts theme={null}
    import { NinjaChat } from "@ninjachat/sdk";

    const client = new NinjaChat({ apiKey: process.env.NINJACHAT_API_KEY! });

    export async function POST(request: Request) {
      const { input } = await request.json();
      const response = await client.responses.create({ model: "ninja/auto", input });
      return Response.json({ text: response.output_text, requestId: response.request_id });
    }
    ```
  </Accordion>

  <Accordion title="FastAPI route" icon="bolt">
    ```python app.py theme={null}
    import os
    from fastapi import FastAPI
    from pydantic import BaseModel
    from ninjachat import NinjaChat

    app = FastAPI()
    client = NinjaChat(api_key=os.environ["NINJACHAT_API_KEY"])

    class GenerateBody(BaseModel):
        input: str

    @app.post("/generate")
    def generate(body: GenerateBody):
        response = client.responses.create(model="ninja/auto", input=body.input)
        return {"text": response["output_text"], "request_id": response["request_id"]}
    ```
  </Accordion>
</AccordionGroup>

## Generate a response

Use Responses for new stateless text, vision, tool, and structured-output integrations:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const response = await client.responses.create({
    model: "ninja/auto",
    input: "Give me three names for a developer tool.",
    max_output_tokens: 100,
    routing: {
      strategy: "balanced",
      data_policy: "no_training",
    },
  });

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

  ```python Python theme={null}
  response = client.responses.create(
      model="ninja/auto",
      input="Give me three names for a developer tool.",
      max_output_tokens=100,
      routing={"strategy": "balanced", "data_policy": "no_training"},
  )

  print(response["output_text"])
  print(response["cost_usd"], response["request_id"])
  ```
</CodeGroup>

## Use Chat Completions

Use Chat Completions when your application already works with role-based messages:

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

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

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

  print(completion["choices"][0]["message"]["content"])
  ```
</CodeGroup>

Already using OpenAI's client? You can keep it and [change the base URL and key](/openai-compatibility).

## Stream output

<CodeGroup>
  ```typescript TypeScript theme={null}
  const events = await client.responses.create({
    model: "ninja/auto",
    input: "Write a short launch announcement.",
    stream: true,
  });

  for await (const event of events) {
    if (event.type === "response.output_text.delta") {
      process.stdout.write(event.delta ?? "");
    }
  }
  ```

  ```python Python theme={null}
  events = client.responses.create(
      model="ninja/auto",
      input="Write a short launch announcement.",
      stream=True,
  )

  for event in events:
      if event.get("type") == "response.output_text.delta":
          print(event.get("delta", ""), end="")
  ```
</CodeGroup>

See [Streaming](/streaming) for event behavior and complete examples.

## Images and video

<CodeGroup>
  ```typescript TypeScript theme={null}
  const image = await client.images.generate({
    model: "nano-banana-2",
    prompt: "A paper crane on a drafting table, soft morning light",
  });
  console.log(image.data[0].url);

  const job = await client.videos.generate({
    model: "veo-3.1-fast",
    prompt: "Ocean waves at dusk, slow aerial push-in",
  });
  const video = await client.videos.waitFor(job.id);
  console.log(video.video_url);
  ```

  ```python Python theme={null}
  image = client.images.generate(
      model="nano-banana-2",
      prompt="A paper crane on a drafting table, soft morning light",
  )
  print(image["data"][0]["url"])

  job = client.videos.generate(
      model="veo-3.1-fast",
      prompt="Ocean waves at dusk, slow aerial push-in",
  )
  video = client.videos.wait_for(job["id"])
  print(video["video_url"])
  ```
</CodeGroup>

Video polling stops automatically when the job completes or fails. Failed generation jobs are refunded by the API.

## Models, usage, and traces

<CodeGroup>
  ```typescript TypeScript theme={null}
  const models = await client.models.list();
  const usage = await client.usage("7d");
  const balance = await client.balance();
  const health = await client.health();
  const trace = await client.requests.get(response.request_id);
  ```

  ```python Python theme={null}
  models = client.models.list()
  usage = client.usage("7d")
  balance = client.balance()
  health = client.health()
  trace = client.requests.get(response["request_id"])
  ```
</CodeGroup>

See [Observability](/observability) for the fields to log, health checks, alerting, and signed webhook verification.

## Search and webhooks

Run AI-assisted search through the same client:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const results = await client.search.query({
    query: "What changed in the latest ECMAScript specification?",
    max_results: 5,
  });

  console.log(results.answer, results.sources);
  ```

  ```python Python theme={null}
  results = client.search.query(
      query="What changed in the latest ECMAScript specification?",
      max_results=5,
  )

  print(results["answer"], results["sources"])
  ```
</CodeGroup>

For long-running video and pipeline jobs and spend alerts, register a signed webhook instead of polling — in [Developers → Webhooks](https://www.ninjachat.ai/developers/webhooks) or via the SDK. The events are `video.completed`, `video.failed`, `pipeline.completed`, `pipeline.failed`, `budget.alert`, and `balance.low`; omit `events` to subscribe to all six. Store the returned signing secret immediately; it is shown once.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const endpoint = await client.webhooks.create({
    url: "https://example.com/webhooks/ninjachat",
    events: ["video.completed", "video.failed", "budget.alert"],
  });

  console.log(endpoint.secret);
  ```

  ```python Python theme={null}
  endpoint = client.webhooks.create(
      url="https://example.com/webhooks/ninjachat",
      events=["video.completed", "video.failed", "budget.alert"],
  )

  print(endpoint["secret"])
  ```
</CodeGroup>

## Handle errors

Every failed request raises a typed `NinjaChatError` with an HTTP status, machine-readable code, and request ID when the server produced one.

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

  try {
    await client.models.retrieve("missing-model");
  } catch (error) {
    if (error instanceof NinjaChatError) {
      console.error(error.status, error.code, error.requestId);
    }
  }
  ```

  ```python Python theme={null}
  from ninjachat import NinjaChatError

  try:
      client.models.retrieve("missing-model")
  except NinjaChatError as error:
      print(error.status, error.code, error.request_id)
  ```
</CodeGroup>

The clients retry `429`, `500`, `502`, `503`, and `504` responses (up to `maxRetries`, default `2`), honor `Retry-After`, and attach an `Idempotency-Key` to billed requests before retrying so a retry can only replay, never double-bill. A `409 request_in_flight` is retried only when the request carried an idempotency key; `408` is never retried. Pass `maxRetries: 0` (`max_retries=0`) to disable retries. See [Error handling](/error-handling) and [Rate limits](/rate-limits).

Before customer traffic, complete the [go-live checklist](/production-readiness).

## Vercel AI SDK

If your app is built on the [AI SDK](https://ai-sdk.dev), use the official provider rather than a generic OpenAI-compatible shim — you keep NinjaChat's routing policy and its routing trace.

```bash theme={null}
npm install @ninjachat/ai-sdk-provider
```

```typescript theme={null}
import { generateText } from "ai";
import { ninjachat } from "@ninjachat/ai-sdk-provider";

const { text, providerMetadata } = await generateText({
  model: ninjachat("ninja/auto"),
  prompt: "Write a haiku about clean APIs.",
  providerOptions: {
    ninjachat: { routing: { strategy: "cost", allow_fallbacks: true } },
  },
});

// What actually ran, what it cost, and how to trace it.
const { resolvedModel, provider, costUsd, requestId } = providerMetadata!.ninjachat;
```

`streamText`, tool calling, structured outputs, and image inputs all work. Reads `NINJACHAT_API_KEY` from the environment, or call `createNinjaChat({ apiKey })`. For an ordered fallback chain, `ninjachat.chain(["claude-opus-5", "gpt-5.6-sol"])` sends the `models` array.

Media generation, search, batch, compare, and pipelines live in [`@ninjachat/sdk`](#install) — the AI SDK provider covers text.

## Package links

<CardGroup cols={2}>
  <Card title="TypeScript on npm" icon="npm" href="https://www.npmjs.com/package/@ninjachat/sdk">
    `@ninjachat/sdk`
  </Card>

  <Card title="Python on PyPI" icon="python" href="https://pypi.org/project/ninjachat/">
    `ninjachat`
  </Card>

  <Card title="AI SDK provider on npm" icon="bolt" href="https://www.npmjs.com/package/@ninjachat/ai-sdk-provider">
    `@ninjachat/ai-sdk-provider`
  </Card>

  <Card title="Source code" icon="github" href="https://github.com/bloon-ai/ninjachat-sdk">
    Browse the SDKs and release history
  </Card>

  <Card title="API quickstart" icon="rocket" href="/quickstart">
    Make your first request
  </Card>
</CardGroup>

## More capabilities

* [Speech](/speech): `client.audio.speech.create` returns audio bytes (Python) or a binary `Response` (TypeScript).
* [Embeddings](/embeddings): `client.embeddings.create` returns vectors.
* [Reranking](/reranking): `client.rerank.create` returns ranked documents.
* [Anthropic Messages](/anthropic-compatibility): `client.messages.create` supports messages and streaming.
* [Account management](/management): use a management credential for keys, projects and account operations.
