# Overview

> The TypeScript and Python SDKs: installing, connecting locally or to the cloud, API keys, retries and errors.

Source: https://useprimitives.com/docs/sdk

The official SDKs wrap the [REST API](/docs/api) with typed clients for groups and every primitive. Both talk to Primitives Cloud and to a local `primitives dev` engine with the same code.

| Language | Package | Requires | Get started |
| --- | --- | --- | --- |
| TypeScript | `primitives-sdk` on npm | Node.js 18+, or any runtime with `fetch` | [Quickstart](/docs/sdk/typescript/quickstart) · [Reference](/docs/sdk/typescript) |
| Python | `primitives-sdk` on PyPI, imported as `primitives_sdk` | Python 3.10+ | [Quickstart](/docs/sdk/python/quickstart) · [Reference](/docs/sdk/python) |

```bash
npm i primitives-sdk
```

```bash
pip install primitives-sdk
```

From any other language, call the [REST API](/docs/api) directly. From an agent, [MCP](/docs/mcp) is usually the better fit.

## Connecting

The client picks its server from the key you give it:

| You pass | It connects to |
| --- | --- |
| A `pk_` API key | `https://api.useprimitives.com` |
| No key | `PRIMITIVES_URL` if set, otherwise `http://localhost:7777` |
| `baseUrl` / `base_url` | That URL, whatever the key |

```ts
import { Primitives } from 'primitives-sdk';

const local = new Primitives();                                        // primitives dev
const cloud = new Primitives({ token: process.env.PRIMITIVES_API_KEY }); // Primitives Cloud
```

```python
import os

from primitives_sdk import Primitives

local = Primitives()                                     # primitives dev
cloud = Primitives(os.environ["PRIMITIVES_API_KEY"])     # Primitives Cloud
```

## Authentication

Cloud requests need an API key, created in the dashboard. There are two kinds:

- **Workspace keys** reach every group and endpoint. Use them in your backend, for setup and administration.
- **Scoped keys** reach one group, and optionally only some tools. Hand these to agents and sandboxes, so each one can only touch its own primitives.

A scoped key can't create, list or delete groups, delete, rename, pause or resume primitives, set or delete secrets, create or revoke grants, or read the audit log; those calls fail with `forbidden`. See [Auth & scoping](/docs/mcp/auth) and [API authentication](/docs/api/authentication).

Keep keys in environment variables or a secret manager, never in source code. The local engine takes no key at all.

## Groups and handles

Both SDKs share one model:

1. A **group** is a named collection of primitives. It starts empty.
2. You **create primitives** in it by type and name, and get back a handle with an immutable `id`.
3. A **handle** (`p.db(id)`, `p.kv(id)`, …) is a typed client for one primitive. Creating one makes no request.
4. Inside a group, `group.db('orders')` resolves a primitive by name on first use.

Every handle has the same lifecycle methods (`info`, `snapshot`, `restore`, `fork`, `pause`, `stats`, …) plus the data methods of its type. Calling a data method on the wrong type fails with `wrong_type`.

## Retries

Both clients retry `429` responses, and `5xx` responses on `GET`, `PUT`, `DELETE` and `HEAD`, with exponential backoff that honours `Retry-After`. They never retry a `POST` on `5xx`, since it may have been applied. Tune it with `maxRetries` (TypeScript, default `3`) or `max_retries` (Python, default `2`).

For writes you may retry yourself, use queue dedupe keys (`dedupeKey` / `dedupe_key`), KV `ifVersion` / `if_version` and idempotent SQL such as `insert … on conflict do nothing`.

## Errors

Every API error is raised as a `PrimitivesError` with the HTTP `status`, a stable `code`, a `message` and the request id:

```ts
import { PrimitivesError } from 'primitives-sdk';

try {
  await kv.set('config', next, { ifVersion: current.version });
} catch (err) {
  if (err instanceof PrimitivesError && err.code === 'precondition_failed') {
    // someone else wrote first: reload and try again
  } else {
    throw err;
  }
}
```

```python
from primitives_sdk import PrimitivesError

try:
    await kv.set("config", nxt, if_version=current.version)
except PrimitivesError as err:
    if err.code != "precondition_failed":
        raise
```

Branch on `code`, not `message`. Every code is listed in [Errors](/docs/api/errors). Include the request id (`requestId` / `request_id`) when you contact support.
