> ## 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 and streams

> Call the Reintersect API from Effect handlers, routes, jobs and scripts with the Effect app client, post to app streams, render payloads and use the member API client.

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

The Effect app client has the same [methods](/reference/client#methods) and scope rules as the
Promise client. Each method returns an `Effect` whose error channel holds the endpoint's tagged
errors, so failures show up in your handler's type. See [Errors](/effect/errors#errors-from-the-client).

## In handlers

`reintersect` in the context is already authorized for the install the delivery came from. It has
the methods for the scopes the piece declares, and its block methods accept the blocks the piece can
use:

```tsx src/onDecision.tsx theme={null}
import { defineEvent } from "@reintersect/sdk/effect/app";
import { Launch } from "./launch";

export const OnDecision = defineEvent(
  "decision.published",
  { scopes: ["blocks:write"], blocks: { Launch } },
  function* ({ decision, reintersect }) {
    yield* reintersect.blocks.insert(Launch, {
      conversationId: decision.conversationId,
      props: { title: decision.title },
    });
  },
);
```

In a route, call `reintersect.forInstall(installId)` to get a client for an install your app knows.
See [Routes](/effect/commands-and-events#routes).

## Outside handlers

In a job, a script or your own backend, build a client with `ReintersectAppClient.make(app, options)`.
It needs an `HttpClient`, such as `FetchHttpClient.layer`:

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

export const markDone = (installId: string, blockId: string) =>
  Effect.gen(function* () {
    const reintersect = yield* ReintersectAppClient.make(app, { installId });

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

| Option | Description |
| - | - |
| `installId`, `clientCredentials?` | Mint install tokens for this install. Without `clientCredentials`, the client reads `REINTERSECT_APP_CLIENT_ID` and `REINTERSECT_APP_CLIENT_SECRET`. |
| `token` | An install token (`reiapp_…`) or interaction token (`reiint_…`) to use as is. |
| `baseUrl` | The API origin. Defaults to `REINTERSECT_BASE_URL` or `https://api.reintersect.com`. |

See [Authentication](/reference/client#authentication) for how tokens are minted and cached.

## App streams

An app stream posts events into a circle as conversations from your app. There are two ways to post.

With an install, call `streams.sendEvent` on the client. It needs `replies:write`:

```ts theme={null}
yield* reintersect.streams.sendEvent({
  circleId,
  markdown: "Deploy of **web** finished.",
  threadKey: "deploy:web",
  idempotencyKey: `deploy:web:${deployId}`,
});
```

Without an install, post to a stream URL from the circle's app settings with `ReintersectStream`. The
URL is the credential, so keep it secret. `ReintersectStream.make` checks the options and returns an
`Either`, which you can yield inside `Effect.gen`:

```ts src/announceDeploy.ts theme={null}
import { ReintersectStream } from "@reintersect/sdk/effect";
import { Config, Effect, Redacted, Schedule } from "effect";

export const announceDeploy = (deployId: string) =>
  Effect.gen(function* () {
    const url = yield* Config.redacted("DEPLOY_STREAM_URL");
    const stream = yield* ReintersectStream.make({ url: Redacted.value(url) });

    yield* stream
      .post({
        markdown: "Deploy of **web** finished.",
        threadKey: "deploy:web",
        idempotencyKey: `deploy:web:${deployId}`,
      })
      .pipe(
        Effect.retry({
          schedule: Schedule.jittered(Schedule.exponential("1 second")),
          times: 3,
          while: (error) => error._tag === "ReintersectSdkNetworkError",
        }),
      );
  });
```

Both make exactly one request and never retry on their own. An `idempotencyKey` makes a resend post
once, so a retry like the one above is safe. A stream post fails with
`ReintersectStreamRateLimitedError` (with `retryAfterSeconds`), `ReintersectStreamInvalidTokenError`,
`ReintersectStreamRevokedError`, `ReintersectStreamInvalidPayloadError` or
`ReintersectStreamPayloadTooLargeError`, or a transport error. See
[Writing to circles](/writing-to-circles#app-stream-events) for threads, titles and personas.

## renderPayload

`renderPayload(content)` from `@reintersect/sdk/effect` renders JSX into the document JSON the API
accepts, for an HTTP client of your own. It returns an `Effect`:

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

const document = yield* renderPayload(
  <Paragraph>
    Deploy <Bold>v2.4</Bold> is live.
  </Paragraph>,
);
```

Files and images are rejected, because nothing uploads them.

## The member API client

`Reintersect` is a service for Reintersect's member API, which acts as a member with an API key from
**Settings → Developer → API keys**. It reads circles, conversations, replies and files, and creates
and updates replies. It's separate from the app client. Provide it with `ReintersectClient.layer` or,
to read `REINTERSECT_API_KEY` and `REINTERSECT_BASE_URL` through `Config`, with
`ReintersectClient.layerConfig()`:

```ts src/circles.ts theme={null}
import { Reintersect, ReintersectClient } from "@reintersect/sdk/effect";
import { Effect } from "effect";

export const launchCircle = Effect.gen(function* () {
  const reintersect = yield* Reintersect;

  return yield* reintersect.circles.get({ slug: "launches" });
}).pipe(Effect.provide(ReintersectClient.layerConfig()));
```

A missing circle fails with `ReintersectApiCircleNotFoundError`.


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