# Quickstart (/docs/quickstart)



If you're adding a streaming chat surface to an existing app — Next.js,
Astro, SvelteKit, Remix, Vite, Workers, Hono, plain Node — this is the
shortest path. It's the same wire-level surface
[Getting started](/docs/getting-started) covers; this page goes deeper
on the env-var detection, the error contract, and what `useChat`
actually gives you.

## One install, one env var, three files [#one-install-one-env-var-three-files]

```bash
npm install @pleach/core
```

Set one provider env var:

```bash
# .env (or your platform's env config)
ANTHROPIC_API_KEY=sk-ant-...
```

```typescript
// app/api/chat/route.ts
import { createPleachRoute } from "@pleach/core/quickstart";

export const POST = createPleachRoute();
```

```tsx
// app/page.tsx
"use client";
import { ChatBox } from "@pleach/core/quickstart";

export default function Home() {
  return <ChatBox apiUrl="/api/chat" />;
}
```

Run `npm run dev`. You have a streaming chat against Anthropic.

## .env setup [#env-setup]

The runtime checks env vars in priority order. The first one with a
non-empty value wins. **Default order is alphabetical** — not
preference-biased — so `ANTHROPIC_API_KEY` wins over
`OPENROUTER_API_KEY` when both are set. This behavior is
regression-tested at
`packages/core/test/quickstart/providerDetection.test.mjs`.

| Provider   | Env var                                                     |
| ---------- | ----------------------------------------------------------- |
| Anthropic  | `ANTHROPIC_API_KEY`                                         |
| DeepSeek   | `DEEPSEEK_API_KEY`                                          |
| Google     | `GOOGLE_GENERATIVE_AI_API_KEY` (or legacy `GOOGLE_API_KEY`) |
| Mistral    | `MISTRAL_API_KEY`                                           |
| Moonshot   | `MOONSHOT_API_KEY`                                          |
| OpenAI     | `OPENAI_API_KEY`                                            |
| OpenRouter | `OPENROUTER_API_KEY`                                        |

The seven-provider surface mirrors the `ProviderFamily` set exported
from `@pleach/core/modelfamily` plus OpenRouter as a gateway
transport. The `(family × callClass)` resolution matrix itself is
host-supplied — reached through the `AgentAdapter.resolveModel<C>()`
seam, not shipped in the package. See
[Provider detection](/docs/quickstart/provider-detection)
for the full resolution matrix and how to override priority.

## What you get [#what-you-get]

* **Streaming response** — `<ChatBox />` renders tokens as they arrive
  via the `useChat` hook, which speaks Pleach's `StreamEvent`
  discriminated union directly.
* **Multi-turn memory** — `useChat` owns a `sessionId` and reuses it
  across calls. Each user message appends to the same session
  history.
* **`bot.reset()`** equivalent — in the React surface, this is
  `clearMessages()` on the `useChat` return; on the recipes path
  (below), it's `bot.reset()` or `bot.newSession()` on the `Chatbot`.
* **`ChatStreamError` rejection** — when the underlying stream emits
  `{ type: "error" }` or the generator throws, the failure surfaces
  to the caller. The original cause is preserved on `error.cause`;
  any stream-emitted `code` is preserved on `error.code`. The
  factory contract is at `packages/recipes/src/chatbot.ts`.

## Troubleshooting [#troubleshooting]

### `"No provider API key found in environment."` (HTTP 503) [#no-provider-api-key-found-in-environment-http-503]

The route handler fails loudly when no provider env var is set rather
than silently falling back to a stub. Surface the 503 in your client;
it's the signal that `ANTHROPIC_API_KEY` (or your equivalent) didn't
reach the runtime.

The error payload is:

```json
{
  "error": "no_provider_detected",
  "detail": "No provider API key found in environment. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, or pass `provider` to createPleachRoute()."
}
```

Fix: set one of the env vars from the table above, or pass
`provider: "anthropic"` explicitly to `createPleachRoute({ provider })`.

For keyless local dev — no env var, no 503 — pass
`createPleachRoute({ demo: process.env.NODE_ENV !== "production" })`.
With no key it drives a real graph turn over canned model text and
writes real audit rows (response header `x-pleach-mode: demo`); a
resolved key always wins, and it stays off in a production build.
It's the mode `npx pleach init` scaffolds, behind an `/audit` view.

### `localStorage is not defined` [#localstorage-is-not-defined]

If you see this from the runtime in an SSR / Node context (server
route, edge function, test harness), it was resolved 2026-06-15 in
`fix(core): generateClientId guards window.localStorage consistently
(jsdom + SSR safe)`. The fix lives at
`packages/core/src/utils/uuid.ts` — `generateClientId()` now checks
`typeof window === "undefined" || !window.localStorage` before
attempting any read.

Upgrade `@pleach/core` to a version published after 2026-06-15.

### `ChatStreamError` rejection [#chatstreamerror-rejection]

`Chatbot.ask()` (on the recipes path) rejects with `ChatStreamError`
when the underlying stream fails. The original failure is preserved:

```typescript
try {
  await bot.ask("hello");
} catch (err) {
  if (err instanceof ChatStreamError) {
    console.error("stream failed", err.message);
    console.error("root cause:", err.cause);
    console.error("stream code:", err.code);
  }
}
```

Common root causes: provider API key invalid (check `.env`),
provider rate limit hit, request aborted upstream. Inspect
`err.cause` for the provider-level error.

## Alternative: `@pleach/recipes/chatbot` (no route handler) [#alternative-pleachrecipeschatbot-no-route-handler]

If you don't need an HTTP boundary — for example you're building a
CLI, a Node script, or a server-internal helper — reach for the
recipes path:

```bash
npm install @pleach/recipes @pleach/core
```

```typescript
import { simpleChatbot } from "@pleach/recipes/chatbot";
import { setOrchestratorAdapterCtor } from "@pleach/core/runtime";

// At app boot: register your provider adapter ctor once.
setOrchestratorAdapterCtor(MyOrchestratorAdapter);

const bot = simpleChatbot({
  systemPrompt: "You are a helpful assistant.",
  orchestratorConfig: {
    provider: "anthropic",
    model: "claude-sonnet-4",
    apiKey: process.env.ANTHROPIC_API_KEY!,
  },
});

console.log(await bot.ask("hello"));
console.log(await bot.ask("what did I just say?"));  // same session
await bot.reset();
console.log(await bot.ask("fresh conversation"));    // new session
```

**Honest scope-limit:** unlike the route handler, `simpleChatbot` does
NOT auto-detect provider env vars. You supply the provider explicitly
via `orchestratorConfig` and register the adapter ctor at app boot.
When `orchestratorConfig` is omitted, `ask()` resolves to the
substrate placeholder string
(`"This is a placeholder response from the harness runtime."`) — by
design, so consumers without a wired provider see something
deterministic rather than a crash.

The `bot.runtime` field is the underlying `SessionRuntime`. Use it
directly when you need plugins, checkpoint inspection, custom
observers, or anything below the `ask()` surface. See
[Upgrading to @pleach/core](/docs/quickstart/upgrading-to-core).

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="Provider detection" href="/docs/quickstart/provider-detection" description="Env-var resolution matrix, priority override, custom env-var aliases." />

  <Card title="Upgrading to @pleach/core" href="/docs/quickstart/upgrading-to-core" description="When `simpleChatbot` isn't enough — plugins, custom tools, custom orchestratorConfig, direct SessionRuntime use." />

  <Card title="Which SKU do I need?" href="/docs/which-sku" description="14 published packages, but most projects need 3 or 4." />

  <Card title="Recipes" href="/docs/recipes" description="End-to-end runnable patterns — Next.js chat, OTel, multi-tenant, compliance, RAG, subagent swarms." />
</Cards>
