# TypeScript client

`lib/seoforsaas/client.ts` wraps the read endpoints. It needs only `fetch` (Node 18+, Bun, Deno, Cloudflare Workers) and ships with `types.ts`, the whole API as TypeScript types. It's a source file in your repository, not a package: read it, and extend it through its options rather than edits, so reinstalling a newer version stays a clean overwrite.

```bash group=install title="shadcn"
npx shadcn@latest add https://seoforsaas.dev/r/client.json
```

```bash group=install title="curl"
for f in types.ts client.ts; do
  curl -fsSL --create-dirs -o "src/lib/seoforsaas/$f" "https://seoforsaas.dev/r/files/lib/seoforsaas/$f"
done
```

## createClient(options)

```ts
import { createClient } from "./lib/seoforsaas/client";

const seoforsaas = createClient({
  projectId: process.env.SEOFORSAAS_PROJECT_ID,
  contentKey: process.env.SEOFORSAAS_CONTENT_KEY,
});
```

| Option | Type | |
|---|---|---|
| `projectId` | `string \| undefined` | **Delivery** → **Connection**. Required. |
| `contentKey` | `string \| undefined` | Required. |
| `apiUrl` | `string` | Defaults to `https://api.seoforsaas.dev`. |
| `requestInit` | `RequestInit` without headers, method, body and signal | Merged into every request: framework cache options go here, for example `{ cache: "force-cache", next: { tags: ["seoforsaas"] } }` in Next.js. |
| `timeoutMs` | `number` | Per request. Defaults to `10000`. |

`projectId` and `contentKey` accept `undefined` so you can pass environment variables as they are: a missing one throws at once, naming the variable.

## Methods

| Method | Returns | |
|---|---|---|
| `listArticles({ page?, limit? })` | `{ articles: Article[], pagination }` | One page, newest first. `limit` defaults to 100 here (the API's own default is 20) and is capped at 100. |
| `listAllArticles()` | `Article[]` | Every page. For indexes and static params. |
| `getArticle(slug)` | `ArticleDetail \| null` | With `related` (always an array); `null` when there's no published article at that slug. |
| `getSitemap()` | `SitemapEntry[]` | `{ url, lastModified }` per published article, newest change first. See [Sitemap](/docs/api/sitemap). |

The methods are plain functions: destructuring them (`const { getArticle } = seoforsaas`) works.

## Errors

A non-2xx response other than a missing article throws `SeoForSaasError` with the HTTP `status` and the API's error `code`. A timeout throws the platform's `TimeoutError`, and a network failure its `TypeError`, unchanged:

```ts
import { createClient, SeoForSaasError } from "./lib/seoforsaas/client";

const seoforsaas = createClient({
  projectId: process.env.SEOFORSAAS_PROJECT_ID,
  contentKey: process.env.SEOFORSAAS_CONTENT_KEY,
});

export async function loadArticles() {
  try {
    return await seoforsaas.listAllArticles();
  } catch (error) {
    if (error instanceof SeoForSaasError && error.status === 401) {
      throw new Error("SEOFORSAAS_CONTENT_KEY is wrong or was revoked", { cause: error });
    }
    throw error;
  }
}
```

Codes are listed under [Errors and limits](/docs/api/errors).
