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

# Client

> Call the Reintersect API as your app: the client in handlers, forInstall, createReintersectClient, the Effect client, renderPayload and errors.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

Your app talks to Reintersect through a client typed by your app. It has only the methods your scopes
allow, and its block methods accept only blocks your app includes.

| Where | How you get a client |
| - | - |
| Action, event, command, message action and reference handlers | `reintersect` in the context, already authorized for the install of the delivery. |
| Routes | `reintersect.forInstall(installId)` in the context. |
| Anywhere else (scripts, jobs, your backend) | `createReintersectClient(app, options)` from `@reintersect/sdk`, or `ReintersectAppClient.make(app, options)` from `@reintersect/sdk/effect`. |

## Methods

| Method | Scope | Description |
| - | - | - |
| `blocks.insert(Block, { conversationId \| replyTo \| circleId, props })` | `blocks:write` | Inserts a block in a message from your app. |
| `blocks.get(Block, blockId)` | none | Reads one of your install's blocks. |
| `blocks.update(Block, blockId, { state, expectedVersion? })` | `blocks:write` | Shallow-merges state. |
| `blocks.replace(Block, blockId, { state, expectedVersion? })` | `blocks:write` | Replaces state. |
| `blocks.revalidate(blockId)` | none | Renders the block again. |
| `blocks.query(Block, { stateMatch, cursor?, limit? })` | none | Pages through your blocks of a type whose state matches. |
| `blocks.updateWhere(Block, { stateMatch, state })` | `blocks:write` | Merges state into every match. |
| `references.push({ kind, externalId, fields, version })` | `references:write` | Pushes reference fields. |
| `references.revalidate({ kind, externalId })` | `references:write` | Resolves a reference again. |
| `notifications.send({ memberId, text, link? })` | `notifications:write` | Sends an inbox and push notification. |
| `replies.create({ conversationId \| replyTo \| circleId, content })` | `replies:write` | Posts a message as your app. `content` is JSX or a document. Answers with `circleId` and a `conversationId` that is `null` for a chat message not filed in a conversation. |
| `streams.sendEvent({ circleId, content \| markdown, threadKey?, title?, persona?, idempotencyKey? })` | `replies:write` | Posts an event to your app's stream in a circle. See [Writing to circles](/writing-to-circles#app-stream-events). |
| `invocations.respond({ interactionToken, answer })` | none | Answers a deferred [command](/commands#deferring) or [message action](/message-actions#timing). |

Calling a method whose scope the piece didn't declare is a type error that names the scope. Passing
a block the app doesn't include is a type error that names the block. `circleId` targets work only in circles that
use chat; see [Writing to circles](/writing-to-circles#replies-and-blocks). Search objects and memories have no client
methods yet; call [their endpoints](/search-and-memories) with `fetch`.

## Promise results

Promise methods resolve to `{ data, error }` and never reject for modeled failures:

```ts theme={null}
const { data, error } = await reintersect.blocks.update(TaskBlock, blockId, {
  state: { status: "done" },
  expectedVersion: 4,
});

if (error?._tag === "ReintersectAppBlockVersionConflictError") {
  // someone else changed the block: read it again
}
```

Cancellation and unexpected defects reject. The `error` union lists every tagged error the endpoint
can return, plus transport and token failures.

## Authentication

A client authenticates as one install, and the SDK never needs a long-lived token:

| Situation | What the SDK uses |
| - | - |
| Inside a delivery | The one-hour install token in the delivery body. For calls about the block an action runs on, the action's interaction token (15 minutes). `invocations.respond` sends the run's `interactionToken` in the body and authenticates with the install token. |
| `forInstall(installId)`, self-hosted | A token minted with `REINTERSECT_APP_CLIENT_ID` and `REINTERSECT_APP_CLIENT_SECRET` through `POST /v0/apps/installs/:installId/access-tokens`, cached for 55 minutes and renewed two minutes before expiry. |
| `forInstall(installId)`, Reintersect-hosted | No token: the request carries the install id and the hosted gateway adds the token after checking the install. |
| `createReintersectClient(app, { token })` | The token you pass: an install token or an interaction token. |
| `createReintersectClient(app, { installId, clientCredentials? })` | Minted like `forInstall`. Without `clientCredentials`, it reads the two environment variables. |

`createReintersectClient(app, options)` options:

| Option | Description |
| - | - |
| `token` | An install (`reiapp_…`) or interaction (`reiint_…`) token. |
| `installId`, `clientCredentials?` | Mint tokens for this install. `clientCredentials` is `{ clientId, clientSecret }`. |
| `baseUrl` | The API origin. Defaults to `REINTERSECT_BASE_URL` or `https://api.reintersect.com`. |
| `fetch` | A custom `fetch`. |

When the SDK fails to get a token, it returns the API's own error where one exists:
`ReintersectAppInstallNotFoundError` (no such install for your app), `ReintersectAppInstallRevokedError`
(uninstalled or suspended) and `ReintersectApiRateLimitExceededError`. Otherwise they are
`ReintersectAppInstallTokenError` (`reason`: `Rejected`, `Unreachable` or `InvalidResponse`) or
`ReintersectAppConfigurationError` (missing credentials).

## Retries

The SDK retries reads (`get`, `query`, `revalidate`) twice with jittered backoff on network errors,
timeouts and `503`. Writes run once, so retry them yourself when it's safe, for example with
`expectedVersion`. Only `streams.sendEvent` takes an `idempotencyKey`, so a resent event posts once.

## Effect client

```ts theme={null}
import { ReintersectAppClient } from "@reintersect/sdk/effect";
import { FetchHttpClient } from "@effect/platform";
import { Effect } from "effect";
import { app } from "./app";
import { TaskBlock } from "./taskBlock";

const program = Effect.gen(function* () {
  const reintersect = yield* ReintersectAppClient.make(app, { installId: "inst_123" });

  yield* reintersect.blocks.update(TaskBlock, "blk_123", { state: { status: "done" } });
}).pipe(Effect.provide(FetchHttpClient.layer));
```

Methods return `Effect`s with the same errors in the error channel. Inside Effect handlers, the
context's `reintersect` is the Effect flavour. See [Client and streams](/effect/client-and-streams) in the
Effect guide.

## renderPayload

`renderPayload(content)` renders JSX or a structured document into the exact JSON the API accepts,
for any HTTP client or another language's server:

```tsx theme={null}
import { Bold, Paragraph, renderPayload } from "@reintersect/sdk";

const { data, error } = await renderPayload(
  <Paragraph>
    Deploy <Bold>v2.4</Bold> is live.
  </Paragraph>,
);

await fetch("https://api.reintersect.com/v0/replies", {
  method: "POST",
  headers: { authorization: `Bearer ${installToken}`, "content-type": "application/json" },
  body: JSON.stringify({ conversationId, content: data }),
});
```

It rejects files and images, because nothing uploads them. The Effect entry exports
`renderPayload` returning an `Effect`.

## The member API client

`createReintersectClient({ apiKey })`, with one argument, is the client for Reintersect's member API.
It acts as a member with an API key from **Settings → Developer → API keys**, for circles,
conversations, replies and files. It's separate from the app client, and the SDK's README documents
it. `createReintersectStream` posts to circle app stream URLs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.