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

# SDKs

> Official @vorel/sdk TypeScript client. Native fetch only, zero runtime deps, Node 18+ and browser. Resource-grouped methods, async-iterator pagination, typed errors.

`@vorel/sdk` is a TypeScript client wrapping the `/api/v1/*` surface. Native `fetch` only (no axios / undici / node-fetch). Works on Node 18+ (where global `fetch` is GA) and the browser.

<Note>
  **Integration path today: the REST API + live OpenAPI spec.** `@vorel/sdk` exists as a
  fully-typed client, but it is not yet published to a public package registry; a published
  `@vorel/sdk` is on the roadmap. Until it lands, integrate against the REST endpoints directly
  (see [API introduction](/api-reference/introduction)) or generate a typed client from the
  [live OpenAPI spec](https://app.vorel.ai/api/v1/openapi.json). The TypeScript shapes below
  document the client's surface so a generated or hand-rolled client can match it.
</Note>

For non-TypeScript languages, generate a client from the [live OpenAPI spec](https://app.vorel.ai/api/v1/openapi.json) via `openapi-generator`, `oazapfts`, or your tooling of choice.

## Client surface

Zero runtime dependencies. The client is small enough to drop into a Lambda / Cloudflare Worker
without bundling concerns. Once published, you'll initialise it like this:

## Initialise the client

```typescript theme={null}
import { VorelClient } from '@vorel/sdk';

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

Construction options:

| Option      | Type           | Default                  | Notes                                                                               |
| ----------- | -------------- | ------------------------ | ----------------------------------------------------------------------------------- |
| `apiKey`    | `string`       | required                 | Full plaintext from the issuance flow. The SDK throws if missing.                   |
| `baseUrl`   | `string`       | `'https://app.vorel.ai'` | Override for staging / local dev (`http://localhost:3000`).                         |
| `fetchImpl` | `typeof fetch` | `globalThis.fetch`       | Override for tests; Node 17 and below must pass a polyfilled fetch.                 |
| `timeoutMs` | `number`       | `15000`                  | Per-request timeout. AbortController-bounded so a hung 5xx doesn't leak the socket. |

## Resources

The client exposes 6 resource clients, one per public-API resource:

```typescript theme={null}
client.conversations; // List, get, create, update, send
client.leads; // List (with stale_for_days filter), get, create, update, handoff
client.appointments; // List, get, create, update
client.offerings; // List, get, create, update
client.analytics; // weeklyRollup
client.crm; // createRecord
```

Each resource has `list(opts?)` returning a typed `ApiPage<T>`, and `iterate(opts?)` returning
an async iterator that auto-paginates.

## Listing + auto-pagination

```typescript theme={null}
// Single page
const page = await client.conversations.list({ limit: 50 });
console.log(page.data, page.next_cursor, page.has_more);

// Auto-paginated iteration, stops when has_more=false
for await (const conv of client.conversations.iterate()) {
  console.log(conv.id, conv.channel, conv.status);
}

// Auto-paginated with a filter
for await (const lead of client.leads.iterate({ stale_for_days: 3 })) {
  await reEngage(lead);
}
```

`iterate()` hides cursor management. Break out of the loop early and the iterator stops fetching
further pages.

## Mutations

Every write resource takes a typed input shape:

```typescript theme={null}
// Pre-create a conversation from your CRM webhook
const conv = await client.conversations.create({
  channel: 'whatsapp',
  customer_identifier: '+9715xxxxxxxx',
  customer_name: 'John Smith',
  customer_language: 'en',
});

// Attach a lead to it
const lead = await client.leads.create({
  conversation_id: conv.id,
  name: 'John Smith',
  phone: '+9715xxxxxxxx',
  intent: 'looking to buy',
  status: 'qualified', // emits both lead.created and lead.qualified webhooks
  attributes: {
    bedrooms: 2,
    budget_max: 2_500_000,
    preferred_areas: ['Dubai Marina', 'JLT'],
  },
});

// Schedule a viewing
const appt = await client.appointments.create({
  conversation_id: conv.id,
  lead_id: lead.id,
  customer_name: 'John Smith',
  customer_phone: '+9715xxxxxxxx',
  scheduled_start: '2026-05-15T14:00:00+04:00',
  scheduled_end: '2026-05-15T14:30:00+04:00',
  location_text: 'Marina Heights Tower 1, Apt 1502',
});

// Patch slots later as you learn more
await client.leads.update(lead.id, {
  attributes: { financing_needed: true },
});

// Push to the tenant's configured CRM
const crmResult = await client.crm.createRecord({
  object: 'lead',
  fields: {
    name: 'John Smith',
    phone: '+9715xxxxxxxx',
    budget_max: 2_500_000,
  },
  idempotency_key: `${conv.id}:lead-create-1`,
});
if (crmResult.ok) {
  console.log('CRM external_id:', crmResult.external_id);
} else {
  console.warn('CRM soft-fail:', crmResult.error, crmResult.detail);
}
```

`PATCH` calls are **strict-partial**: pass only the fields you want to change. JSONB attribute
fields (`leads.attributes`, `offerings.attributes`) are **shallow-merged** server-side, with `null`
deleting the key.

## Error handling

The SDK exposes 4 typed error subclasses, all extending `VorelApiError`:

```typescript theme={null}
import {
  VorelApiError,
  VorelUnauthorizedError,
  VorelForbiddenError,
  VorelRateLimitedError,
  VorelTransportError,
} from '@vorel/sdk';

try {
  const lead = await client.leads.create({
    /* ... */
  });
} catch (err) {
  if (err instanceof VorelRateLimitedError) {
    // 429: honour Retry-After
    await sleep((err.retryAfterSeconds ?? 60) * 1000);
    // ...retry with jitter
  } else if (err instanceof VorelUnauthorizedError) {
    // 401: bad / revoked key
    throw new Error('Vorel API key is invalid; rotate it');
  } else if (err instanceof VorelForbiddenError) {
    // 403: scope insufficient
    throw new Error(`Key needs the right scope: ${err.message}`);
  } else if (err instanceof VorelTransportError) {
    // 5xx, network errors, timeouts: transient. Retry with backoff.
  } else if (err instanceof VorelApiError) {
    // 4xx other than 401/403/429: permanent. Inspect err.code + err.message.
    console.error('API error:', err.status, err.code, err.message);
  } else {
    throw err; // unexpected
  }
}
```

Every error carries `requestUrl` so you can trace which call failed without rebuilding the URL.
`VorelRateLimitedError.retryAfterSeconds` parses the `Retry-After` header for you (returns `null`
when the header is missing or malformed).

## Scheduled workflows in plain Node

The SDK is shaped for workflows that run alongside the agent rather than inside it:

```typescript theme={null}
// Daily nurture cadence: find stale-qualified leads, send a re-engagement template
import { VorelClient } from '@vorel/sdk';

const client = new VorelClient({ apiKey: process.env.VOREL_API_KEY! });

for await (const lead of client.leads.iterate({ stale_for_days: 3 })) {
  if (!lead.conversation_id) continue;
  await client.conversations.send(lead.conversation_id, {
    text: `Hi ${lead.name ?? 'there'}, just checking in. Still interested?`,
    language: 'en',
  });
}
```

Before building this, check whether a [trigger rule](/automation/triggers) already does it. The
trigger engine acts on the same events without you running a scheduler.

## OpenAPI spec for non-TypeScript languages

The full OpenAPI 3.1 spec is live at:

```
https://app.vorel.ai/api/v1/openapi.json
```

Generate clients in any language via:

```bash theme={null}
# Python
openapi-python-client generate --url https://app.vorel.ai/api/v1/openapi.json

# Go
oapi-codegen -package vorel openapi.json > vorel.go

# Postman / Insomnia / Bruno
# Import the URL directly: most tools support pulling the spec live.
```

Vorel doesn't ship language-specific SDKs beyond TypeScript today; the OpenAPI spec is the path
for any other language.

## SDK versioning

`@vorel/sdk` tracks the public-API surface. Breaking changes to the API are version-bumped
(`/v1` → `/v2`) before they ship; the SDK pins to the API version it targets via the `User-Agent`
header (`@vorel/sdk/0.1.0`). The current SDK targets `/v1`.

## What's NOT in the SDK today

* **Resource-level retry loops.** The SDK throws typed errors; you implement retry policy
  per integration (it's hard to retry generally without knowing the integration's idempotency
  story). For pagination, the iterator handles cursor management but won't retry inside a single
  page on a transient 5xx.
* **Streaming endpoints.** Every endpoint today is request/response JSON; no streaming methods
  on the client.
* **Webhook signature verification helper.** The verification logic is a 3-line HMAC-SHA256 hex
  compare, easier to write inline than to import. See [Webhooks](/api-reference/webhooks) for
  the canonical snippet.
* **Webhook ingestion handler.** No "build a Vorel webhook listener" framework adapter; the
  signature-verify + idempotency-on-id pattern is small enough to write per-integration.

## Related docs

* [API introduction](/api-reference/introduction): surface overview
* [Authentication](/api-reference/authentication): issuing keys + scopes
* [Webhooks](/api-reference/webhooks): outbound webhook contract
* [Triggers](/automation/triggers): act on the same events without writing an integration
* [MCP](/api-reference/mcp): connect an AI client instead of writing code
