# Instructions for LLM Agents

## Quick start

```typescript
import { assemblyApi } from '@assembly-js/node-sdk';

// assemblyApi is async — always await it (v4+)
const assembly = await assemblyApi({ apiKey: process.env.ASSEMBLY_API_KEY! });

const clients = await assembly.listClients({});
```

## Authentication

The SDK reads your API key and sets the `X-API-Key` header automatically.

```typescript
const assembly = await assemblyApi({ apiKey: process.env.ASSEMBLY_API_KEY! });
```

- Store your key in `ASSEMBLY_API_KEY` and never hard-code it.
- The Assembly **platform API** (`https://docs.assembly.com/reference`) is **admin-only**; do not expose the key to browser clients.
- For marketplace apps the SDK accepts a per-request `token` alongside the key (see [Marketplace apps on Next.js](#marketplace-apps-on-nextjs)).

## Next.js App Router usage

### Server action

```typescript
'use server';
import { assemblyApi } from '@assembly-js/node-sdk';

export async function getClients() {
  const assembly = await assemblyApi({ apiKey: process.env.ASSEMBLY_API_KEY! });
  return assembly.listClients({});
}
```

### Route handler (`app/api/contracts/route.ts`)

```typescript
import { NextResponse } from 'next/server';
import { assemblyApi } from '@assembly-js/node-sdk';

export async function GET() {
  const assembly = await assemblyApi({ apiKey: process.env.ASSEMBLY_API_KEY! });
  const contracts = await assembly.listContracts({});
  return NextResponse.json(contracts);
}
```

### Async server component page

```typescript
import { assemblyApi } from "@assembly-js/node-sdk";

export default async function ContractsPage() {
  const assembly = await assemblyApi({ apiKey: process.env.ASSEMBLY_API_KEY! });
  const { data } = await assembly.listContracts({});

  return (
    <ul>
      {data?.map((c) => (
        <li key={c.id}>{c.id}</li>
      ))}
    </ul>
  );
}
```

## Marketplace apps on Next.js

Marketplace apps receive a short-lived encrypted `token` in the URL. Read it from `searchParams` in a server component and pass it to `assemblyApi`:

```typescript
// app/page.tsx
import { assemblyApi } from "@assembly-js/node-sdk";

interface Props {
  searchParams: Promise<{ token?: string }>;
}

export default async function AppPage({ searchParams }: Props) {
  const { token } = await searchParams;
  const assembly = await assemblyApi({
    apiKey: process.env.ASSEMBLY_API_KEY!,
    token,
  });

  const { data } = await assembly.listClients({});

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
```

The SDK decrypts the token and scopes all requests to the correct workspace and client.

### Token lifetime — do not cache

Session tokens **expire after 5 minutes**. When the app is rendered inside the Assembly dashboard, the parent webapp proactively **replaces** the token before it expires on two channels simultaneously:

1. **The iframe URL is updated** so the new token appears in `searchParams`. Your server component re-renders on the next request with the fresh value — no app-bridge required for server-side code.
2. **A postMessage is pushed** to `@assembly-js/app-bridge` (if it's been initialized on the page), which updates its internal token state so `getCurrent()` returns the new value on the client.

That means the `token` value is a moving target: **never cache, persist, or copy it into application state** — always read it fresh from `searchParams` on the server or `AssemblyBridge.sessionToken.getCurrent()` on the client.

On the client, use [`@assembly-js/app-bridge`](https://www.npmjs.com/package/@assembly-js/app-bridge) — a **frontend-only** companion package — to read the current token and request refreshes:

```typescript
'use client';
import AssemblyBridge from '@assembly-js/app-bridge';

async function postToServer(token: string) {
  return fetch('/api/contracts', {
    method: 'POST',
    headers: { 'x-assembly-token': token },
  });
}

async function callServer() {
  // Read the freshest token every time you need to make a server call.
  // The parent keeps this value up-to-date automatically.
  const { token } = AssemblyBridge.sessionToken.getCurrent();
  let res = await postToServer(token);

  // If the server reports an auth failure, request a new token and retry once.
  if (res.status === 401) {
    const refreshed = await AssemblyBridge.sessionToken.refresh();
    res = await postToServer(refreshed.token);
  }
  return res;
}
```

The returned `token` is the same encrypted JWT this SDK accepts. `POST` it from the client to one of your own route handlers and forward it to `assemblyApi({ apiKey, token })` on the server — the token is opaque on the client and must not be decoded there.

Do not import `@assembly-js/app-bridge` on the server — it depends on `window`/`postMessage` and will fail to load in Node.

## Preferred fields

- **Always use `companyIds` (array) instead of `companyId` (scalar) when filtering lists** — supports multi-company clients without extra round-trips.
- **Always use `clientId` + `companyId` instead of `recipientId`** — `recipientId` is deprecated and removed in v5; explicit targeting avoids ambiguity when a client belongs to multiple companies.

## Common workflows

### Create a client

```typescript
const client = await assembly.createClient({
  requestBody: {
    givenName: 'Jane',
    familyName: 'Doe',
    email: 'jane@example.com',
  },
});
```

### Send a contract

```typescript
const contract = await assembly.sendContract({
  requestBody: {
    contractTemplateId: 'ctmpl_xxx',
    clientId: 'cl_xxx',
    companyId: 'co_xxx',
  },
});
```

### Create an invoice

```typescript
const invoice = await assembly.createInvoice({
  requestBody: {
    clientId: 'cl_xxx',
    companyId: 'co_xxx',
    daysUntilDue: 14,
    lineItems: [{ description: 'Consulting', amount: 50000, quantity: 1 }],
    paymentMethodPreferences: [{ type: 'creditCard', feePaidByClient: false }],
  },
});
```

### List messages

```typescript
const messages = await assembly.listMessages({ channelId: 'mc_xxx' });
```

### Upload a file to a file channel

```typescript
// 1. Create (or look up) a file channel
const channel = await assembly.createFileChannel({
  requestBody: { membershipType: 'individual', clientId: 'cl_xxx' },
});

// 2. Register the file object in that channel — this returns a presigned
//    S3 `uploadUrl` you PUT the bytes to.
const file = await assembly.createFile({
  fileType: 'file',
  requestBody: {
    path: '/documents/report.pdf',
    channelId: channel.id!,
  },
});

// 3. Upload the file bytes directly to the presigned URL
await fetch(file.uploadUrl!, {
  method: 'PUT',
  body: fileBytes, // e.g. a Buffer, Blob, or ReadableStream
});
```

## Pagination

List methods return a `data` array and a `nextToken` cursor. Pass `nextToken` back to fetch the next page:

```typescript
let nextToken: string | undefined;
do {
  const page = await assembly.listClients({ nextToken, limit: 50 });
  // process page.data …
  nextToken = page.nextToken;
} while (nextToken);
```

- `limit` defaults vary by endpoint (typically 100). Maximum is endpoint-specific.
- Some older endpoints use numeric `offset` instead of a cursor; check the method signature.

## Errors

The SDK wraps responses in `CancelablePromise`. Failed requests throw an `ApiError`. `ApiError` is a class, so narrow with `instanceof` instead of casting:

```typescript
import { ApiError } from '@assembly-js/node-sdk';

try {
  const client = await assembly.retrieveClient({ clientId: 'cl_missing' });
} catch (err) {
  if (err instanceof ApiError) {
    console.error(err.status); // HTTP status code, e.g. 404
    console.error(err.message); // Human-readable message
    console.error(err.body); // Raw response body
  } else {
    throw err;
  }
}
```

`CancelablePromise` also exposes a `.cancel()` method if you need to abort in-flight requests.

## Where to find more

- Full API reference: <https://docs.assembly.com>
- OpenAPI spec: not shipped in the npm tarball; generate it locally with `yarn generate-api` — the spec lands at `codegen/spec.json`.
