> For the complete documentation index, see [llms.txt](https://docs.frayme.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.frayme.ai/sdk-reference/api.md).

# @frayme/api

The official TypeScript client for the Frayme API: streaming and non-streaming compose, typed errors, automatic retries with idempotency, and the framework-neutral tool definitions.

```bash
npm i @frayme/api
```

ESM, MIT, Node ≥ 20.19. Version 0.3.2. Runs in Node, edge runtimes, and (in keyless proxy mode) the browser.

## Client

```ts
import Frayme from '@frayme/api';

const frayme = new Frayme({ apiKey: process.env.FRAYME_API_KEY });
```

### Options

| Option                    | Default                                                 | Description                                                                                                                             |
| ------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey`                  | `FRAYME_API_KEY` env var                                | Your `fr_live_…` key. Pass `null` explicitly for keyless proxy mode.                                                                    |
| `baseURL`                 | `FRAYME_BASE_URL` env var, then `https://api.frayme.ai` | API origin. Point it at your own proxy route in keyless mode.                                                                           |
| `maxRetries`              | `2`                                                     | Automatic retries for retryable failures. Retried requests carry an auto-generated `Idempotency-Key`, so a retry can never double-bill. |
| `timeout`                 | `600000` (10 min)                                       | Whole-request timeout in ms — covers streaming reads too.                                                                               |
| `firstEventTimeout`       | `90000`                                                 | Max ms to wait for the first stream event before failing.                                                                               |
| `dangerouslyAllowBrowser` | `false`                                                 | Allow a real key in a browser — for local experiments only, never in anything you ship.                                                 |
| `fetch`                   | global `fetch`                                          | Override for testing or exotic runtimes.                                                                                                |
| `defaultHeaders`          | `{}`                                                    | Extra headers sent with every request.                                                                                                  |

### Keyless proxy mode

The client refuses to run with an API key in a browser-like environment (it would expose the key to every visitor). Instead, keep the key on your server and point the browser client at your own route:

```ts
// browser — no key ever ships to the client
const frayme = new Frayme({ apiKey: null, baseURL: '/api/frayme-proxy' });
```

Your proxy route forwards the request to `https://api.frayme.ai` with the real key attached. This is the client `useFraymeCompose` expects in [`@frayme/runtime`](/sdk-reference/runtime.md).

## compose.stream() vs compose.create()

| Method                           | Returns                                                                                               | Use when                                                                                                                                          |
| -------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `compose.stream(body, options?)` | `ComposeStream` — events + accumulated spec snapshots                                                 | You want progressive rendering. The high-level surface; handles restarts for you.                                                                 |
| `compose.create(body, options?)` | `ComposeResult` (with `stream: false`) or a plain `AsyncIterable` of raw events (with `stream: true`) | You only need the final validated spec (`stream: false` is what the tool definitions use), or you want the raw event stream with no accumulation. |

```ts
// Non-streaming: one awaited result
const result = await frayme.compose.create({
  prompt: 'A pricing page with three tiers',
  stream: false,
});
// result: { generation_id, spec, model, operation_count, validated: true,
//           usage, interactions, components_used, replayed? }
```

The request body is the wire shape of [`POST /v1/compose`](/api-reference/compose.md) — snake\_case fields (`ui_type`, `prior_spec`, `max_operations`, …), typed as `ComposeRequest`.

## ComposeStream

`compose.stream()` returns one object that is an event emitter, an async iterable, and a spec accumulator:

```ts
const stream = frayme.compose.stream({ prompt: 'A pricing page with three tiers' });

stream.on('op', (op, snapshot) => render(snapshot)); // live spec snapshots
stream.on('restarted', () => clearRendered());       // attempt failed → discard
const { spec, generationId, usage } = await stream.finalSpec();
```

### Handlers (`.on(event, handler)`)

| Event       | Handler receives                                                               | Meaning                                                                                                            |
| ----------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `started`   | `ComposeStartedEvent`                                                          | The stream opened; carries `generation_id` and `model`.                                                            |
| `op`        | `(op, snapshot)` — the raw operation plus the progressively accumulated `Spec` | A provisional json-render operation arrived. Render the snapshot.                                                  |
| `restarted` | `ComposeRestartedEvent`                                                        | The previous attempt failed validation — the internal snapshot has been reset. Discard everything rendered so far. |
| `completed` | `ComposeCompletedEvent`                                                        | The validated final commit; carries `generation_id`, `model`, `operation_count`, `usage`, and `replayed`.          |
| `error`     | `FraymeError`                                                                  | The stream failed with a typed error. Never fired for consumer aborts. In-band `error` events are unbilled.        |
| `abort`     | `APIUserAbortError`                                                            | You aborted via `.abort()`, `.controller`, or a passed signal.                                                     |
| `end`       | —                                                                              | Always fired exactly once, after success, error, or abort.                                                         |

### Surface

| Member                              | Description                                                                                                                       |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `finalSpec()`                       | Resolves on `compose.completed` with `{ spec, generationId, model, operationCount, usage, replayed }`; rejects on error or abort. |
| `currentSpec()`                     | The spec accumulated so far — provisional until `compose.completed`.                                                              |
| `abort()`                           | Abort the stream and the underlying network request.                                                                              |
| `controller`                        | The `AbortController` behind `.abort()`, for composing with your own signals.                                                     |
| `for await (const event of stream)` | Iterate the typed events directly. Breaking out of the loop aborts the stream.                                                    |

In `edit` and `continue_journey` modes the accumulator is seeded with your `prior_spec`, so the server's minimal patch applies in place — snapshots are always complete specs.

## Per-request options

Every method accepts a second `options` argument:

| Option           | Description                                                                                                   |
| ---------------- | ------------------------------------------------------------------------------------------------------------- |
| `signal`         | An `AbortSignal` to cancel the request.                                                                       |
| `idempotencyKey` | Supply your own `Idempotency-Key` (≤255 chars). Otherwise one is auto-generated whenever retries are enabled. |
| `maxRetries`     | Override the client-level retry count for this call.                                                          |

Replaying a key within 24 hours returns the original result — same `generation_id`, `replayed: true` — and is free. See [idempotency on the compose page](/api-reference/compose.md).

## me() and health()

```ts
const me = await frayme.me();
// { workspace: { id, name, slug },
//   plan: { tierKey, monthlyGenerations, rateLimitPerMin, generationsRemaining } }
//   ← camelCase, unlike compose

const health = await frayme.health(); // no auth: { status: 'ok', service, catalog_version }
```

## Error classes

Every failure is a subclass of `FraymeError`, carrying `message`, `status`, `code`, and `requestId` (from the `x-request-id` header). Branch with `instanceof`:

| Class                      | Status | Wire code(s)                                                                 |
| -------------------------- | ------ | ---------------------------------------------------------------------------- |
| `BadRequestError`          | 400    | `BAD_REQUEST`, `INVALID_MANIFEST`, `CUSTOM_SLICE_TOO_LARGE`                  |
| `AuthenticationError`      | 401    | `AUTHENTICATION_REQUIRED`                                                    |
| `PaymentRequiredError`     | 402    | `PAYMENT_REQUIRED`                                                           |
| `AuthorizationError`       | 403    | `FORBIDDEN`, `FEATURE_LIMIT`                                                 |
| `NotFoundError`            | 404    | `NOT_FOUND`                                                                  |
| `IdempotencyKeyInUseError` | 409    | `IDEMPOTENCY_KEY_IN_USE`                                                     |
| `ValidationError`          | 422    | `VALIDATION_ERROR` (idempotency body mismatch)                               |
| `RateLimitError`           | 429    | `RATE_LIMITED` — has `.retryAfter` (seconds, from the `Retry-After` header)  |
| `QuotaExceededError`       | 429    | `QUOTA_EXCEEDED` — the monthly cap; waiting won't help, upgrading will       |
| `InternalServerError`      | 500    | `INTERNAL_SERVER_ERROR`                                                      |
| `CompositionFailedError`   | 502    | `COMPOSITION_FAILED`                                                         |
| `ModelUnavailableError`    | 503    | `MODEL_UNAVAILABLE`, `SERVICE_UNAVAILABLE` — check `.code`                   |
| `APIConnectionError`       | —      | Network failure: could not reach the API, or the connection died mid-stream. |
| `APIUserAbortError`        | —      | You aborted the request.                                                     |

```ts
import { RateLimitError, QuotaExceededError } from '@frayme/api';

try {
  await frayme.compose.create({ prompt, stream: false });
} catch (err) {
  if (err instanceof RateLimitError) await sleep((err.retryAfter ?? 1) * 1000);
  else if (err instanceof QuotaExceededError) notifyPlanCap();
  else throw err;
}
```

In-band stream `error` events map to the same classes via their `code`. The full code-to-status map is exported as `ERROR_CODE_TO_STATUS`; the wire taxonomy is documented on the [errors page](/api-reference/errors.md).

## @frayme/api/tools

The `@frayme/api/tools` entrypoint exports `composeToolDefinition`, `actionToolDefinition`, and the client-bound `createComposeTool(frayme)` / `createActionTool(frayme)` — one definition that registers in Vercel AI SDK 6, Mastra, LangChain.js, and the OpenAI Agents SDK. See the [framework guides](/agent-frameworks/ai-sdk.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.frayme.ai/sdk-reference/api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
