> ## 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.

# Environment and install config

> Typed environment variables and secrets per environment, typed per-workspace install config, and per-member connections.

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

An app has three layers of configuration. Each is declared in code and typed end to end.

| Layer | Set by | Scope | Example |
| - | - | - | - |
| `env` | You, the developer | Per app and environment (preview, production) | An API token for your backend, a base URL |
| `installConfig` | A workspace admin, at install | Per workspace install | The project this workspace maps to |
| Member connections | Each member | Per member and connected service | A member's own account on an MCP server |

## Environment variables and secrets

Declare every variable the app reads on `createReintersectApp`. Plain values use a schema; secrets
use `secret()`:

```ts theme={null}
import { createReintersectApp, secret } from "@reintersect/sdk/app";
import { z } from "zod";

export const app = createReintersectApp({
  name: "Launchpad",
  slug: "launchpad",
  env: {
    VERCEL_TOKEN: secret({ description: "Vercel token with deployment access" }),
    TASKS_URL: z.url().default("https://tasks.example.com"),
    MAX_DEPLOYMENTS: z.coerce.number().int(),
  },
});
```

Environment values always arrive as strings. A schema whose input isn't a string, such as a plain
`number`, is a type error:
`env.MAX_DEPLOYMENTS arrives as a string: use a string schema such as z.coerce.number() or type("string.numeric.parse")`.

A variable is required unless its schema accepts `undefined`; secrets are always required. A deploy
fails with `env-missing` when a required variable has no value in the target environment.

Each piece declares the keys it reads, and only those keys are available in its handlers:

```ts theme={null}
export const promote = defineAction(
  "promote",
  { env: { VERCEL_TOKEN: secret() }, destructive: true },
  async ({ env }) => {
    await fetch("https://api.vercel.com/v13/deployments", {
      headers: { authorization: `Bearer ${env.VERCEL_TOKEN.reveal()}` },
    });
  },
);
```

Adding that piece to an app checks the declaration against the app's `env`. A missing key or a
different type is a compile error at the `add…` call.

### Setting values

Set values per environment with the CLI, in **Settings → Developer → Apps → your app → Environment**,
or from CI with a deploy token:

```bash theme={null}
export REINTERSECT_APP_ID=app_123
npx reintersect env set VERCEL_TOKEN
npx reintersect env set TASKS_URL --plain --preview
npx reintersect env ls --target preview
npx reintersect env rm TASKS_URL --preview
```

`env set` stores a secret in production unless you pass `--plain` or `--preview`, and reads the value
from a prompt or from stdin.

Values are write-only: nobody can read a stored value back, in the dashboard or anywhere else. Each
environment holds up to 128 variables of at most 5 KB each. Hosted apps redeploy when production
variables change.

| Where the app runs | Where values come from |
| - | - |
| Reintersect-hosted, production | The production variables, bound to the Worker. |
| Reintersect-hosted, preview | The preview variables. |
| Self-hosted | Your server's environment (`process.env`), or the `env` object passed to `app.fetch(request, env)`. |
| `reintersect dev` | `.dev.vars` in your project. The CLI tells you which declared variables are missing. |

The variables `REINTERSECT_APP_CLIENT_ID`, `REINTERSECT_APP_CLIENT_SECRET`,
`REINTERSECT_APP_SIGNING_SECRET` and `REINTERSECT_BASE_URL` are read by the SDK itself; see
[Self-hosted apps](/hosting/self-hosted) and [Reintersect-hosted apps](/hosting/reintersect-hosted).

### Secret values

A secret arrives as a `Secret`, not a string. Call `.reveal()` at the point where you need the
value, such as an `authorization` header. Until then, it prints as `<redacted>` in `console.log`,
string templates and `JSON.stringify`, and the SDK's `log` helper doesn't accept it as a field:

```ts theme={null}
import { log } from "@reintersect/sdk/app";

log.info("promoting", { region: env.REGION });
log.info("promoting", { token: env.VERCEL_TOKEN });
//                             ^ Type error: a Secret is not a loggable value
```

`reveal()` returns a plain string, and the types can't follow it after that. Reveal as late as
possible.

On the Effect entry, `secret()` produces a `Redacted<string>` instead. See the
[Effect guide](/effect/handlers#secrets).

## Install config

Install config is for settings that differ per workspace, such as which project a workspace maps to.
Declare a schema on the app; Reintersect generates a form from its JSON Schema (titles, descriptions,
enums and formats) and shows it on the consent screen:

```ts theme={null}
export const app = createReintersectApp({
  name: "Launchpad",
  slug: "launchpad",
  installConfig: z.object({
    project: z.string(),
    defaultEnvironment: z.enum(["staging", "production"]).optional(),
  }),
});
```

Pieces declare the config they read with `config`, and handlers receive it typed:

```tsx theme={null}
export const OnDecision = defineEvent(
  "decision.published",
  { config: z.object({ project: z.string() }), scopes: ["replies:write"] },
  async ({ decision, config, reintersect }) => {
    await reintersect.replies.create({
      conversationId: decision.conversationId,
      content: <Paragraph>Logged for {config.project}.</Paragraph>,
    });
  },
);
```

Reading `config` in a piece that doesn't declare it is a type error. Admins can change install
config later from the app's page in **Settings → Workspace → Integrations**. Reintersect sends the
current config with every delivery, so your app stores nothing.

Top-level config fields marked `writeOnly` or `format: "password"` in the JSON Schema are stored
encrypted, never shown again, and only decrypted to send them to your app. Prefer member connections
for credentials that belong to a person.

## Member connections

Some tools act as a specific person on another service. Reintersect stores those connections per
member. The first time the agent needs one, it shows the member a **Connect** card inline in the
chat, and the member signs in with that service. Admins only make the service available; they never
hand their own account to everyone else. See [MCP authentication](/mcp/authentication).


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