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

# Installations

> Scope connectors per customer tenant in a multi-tenant application

## When you need this

If you are building for one organization—your own—you can skip this page. Your API key is already the tenant boundary, and connector requests without extra headers operate in that organization's own namespace.

You need installations when **one Case.dev organization serves many customer organizations**. A practice-management product with hundreds of firms behind a single Case.dev account is the case this exists for. Without installations, every firm's connections and links would share one namespace.

An **installation** is one customer tenant inside your Case.dev organization.

```mermaid theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
flowchart TD
    A["Your Case.dev organization<br/>One API key, one billing relationship"] --> B["Installation: Firm A<br/>Its connections, links, and vault grants"]
    A --> C["Installation: Firm B<br/>Isolated from Firm A"]
    A --> D["Installation: Firm C<br/>Isolated from Firm A and B"]
```

## Ensure an installation

Installations are created just in time and idempotently. Call this on a tenant's first connector use; calling it again returns the same installation.

```bash title="Endpoint" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
POST /connectors/v1/installations
```

```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
curl -X POST https://api.case.dev/connectors/v1/installations \
  -H "Authorization: Bearer $CASEDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "application": "p3",
    "external_tenant_id": "firm_8842"
  }'
```

| Field                | Description                                                                                          |
| -------------------- | ---------------------------------------------------------------------------------------------------- |
| `application`        | Your application key: lowercase, starting with a letter, 2–32 characters (`^[a-z][a-z0-9_-]{1,31}$`) |
| `external_tenant_id` | Your own identifier for the customer—whatever your system already calls them                         |

Returns `201` when created, `200` when it already existed. Either way you get the installation id.

<Info>
  Application keys are **global**. The first organization to register a key owns it; another organization asking for the same key gets `403`. Pick something specific to your product.
</Info>

## Send the installation on every request

```bash title="Header" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
X-Case-Installation-Id: inst_7f21ab
```

With this header, the caller sees only that installation's connections and links. Without it, the caller sees only organization-scoped rows.

<Warning>
  Once your organization has registered an application, there is **no organization-scoped fallback**. Connector requests that omit `X-Case-Installation-Id` are rejected with `403 installation_required`.

  This is deliberate. A bug that drops the header produces a refused request rather than one customer's documents appearing in another customer's vault.
</Warning>

| Code                    | Status | Meaning                                                          |
| ----------------------- | ------ | ---------------------------------------------------------------- |
| `installation_required` | 403    | Your organization hosts an application and the header is missing |
| `installation_invalid`  | 403    | The id is unknown, or belongs to another organization            |
| `installation_disabled` | 403    | The installation exists but its status is not `active`           |

An unknown id and another organization's id return the same error, so the API cannot be used to probe for which installations exist.

## Grant vaults to an installation

An installation cannot touch a vault until you grant it. Grants are explicit and checked twice: when a link is created, and again every time a run executes.

```bash title="Endpoint" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
PUT /connectors/v1/installations/{id}/vaults/{vaultId}
```

```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
curl -X PUT https://api.case.dev/connectors/v1/installations/inst_7f21ab/vaults/vault_abc123 \
  -H "Authorization: Bearer $CASEDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "relationship": "owned",
    "can_read": true,
    "can_write": true,
    "can_manage": false,
    "source": "provisioning"
  }'
```

### Which capability does what

| Capability   | Needed for                                                   |
| ------------ | ------------------------------------------------------------ |
| `can_write`  | Import links—writing provider files into the vault           |
| `can_read`   | Export links—reading vault documents to send to the provider |
| `can_manage` | Mirror deletion and purge—removing vault documents           |

Grant the narrowest set that supports the flows you actually offer. An import-only integration does not need `can_manage`.

| Field          | Values                                             | Notes                                                             |
| -------------- | -------------------------------------------------- | ----------------------------------------------------------------- |
| `relationship` | `owned`, `shared`                                  | Whether the installation owns the vault or was given access to it |
| `source`       | `provisioning`, `lazy_reconcile`, `explicit_share` | How the grant came about. Useful in your own audits.              |

### Reconciliation never revokes

`PUT` only ever adds or updates. If your reconcile pass omits a vault it granted last time, **nothing is revoked**—a transient glitch in your provisioning job cannot silently cut off access.

Revocation is always the explicit call:

```bash title="Endpoint" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
DELETE /connectors/v1/installations/{id}/vaults/{vaultId}
```

Re-granting a revoked vault reactivates it.

## What happens when access goes away

Disabling an installation or revoking a grant **pauses** the affected links. Nothing is deleted, no documents move, and no ledger state is lost. Restore the grant and resume the link, and it picks up from its cursor.

## Scoping to a user within a tenant

The installation is the tenant boundary. If you also need to keep one user's provider credentials from being used by another user in the same tenant, send a subject assertion:

```bash title="Header" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
X-Case-Connector-Subject: user_5512
```

Values match `^[A-Za-z0-9_-]{1,255}$`; anything else returns `400 connector_subject_invalid`.

Scoping is exact—a request carrying a subject can never fall through to an organization-scoped connection, and omitting the header cannot reach a subject-scoped one.

<Warning>
  This is a **server-to-server assertion**, not an end-user credential. Derive it from your own authenticated session server-side.

  Never copy it from a browser field, query parameter, or anything a client can set. A caller holding your API key is already trusted to act for the tenant; this header only partitions users inside it, so a forged value crosses a real privilege boundary.
</Warning>

## Checklist for a multi-tenant integration

* Ensure the installation on first use, and cache the id against your own tenant record
* Send `X-Case-Installation-Id` on every connector request—there is no fallback
* Grant each vault explicitly, with the narrowest capabilities
* Derive `return_url` and any subject assertion server-side, never from client input
* Treat `403 installation_required` as a bug in your request path, not a permissions problem to work around

## Next steps

<CardGroup>
  <Card title="Connect an Account" href="/connectors/oauth">
    Hosted OAuth, scoped to an installation
  </Card>

  <Card title="Import & Export" href="/connectors/transfers">
    Create links against a granted vault
  </Card>

  <Card title="Errors & Recovery" href="/connectors/errors">
    Grant failures, access loss, and paused links
  </Card>
</CardGroup>
