# Apps Installed into a Workspace

Count the integrations or apps a workspace installs against its plan, and sell more of them.

An app or integration installed into a workspace belongs to the workspace, so the workspace's plan decides how many it may hold. Declare a limit for it on your workspace plans, claim a slot when an app is installed and release it when it is removed. BuildBase keeps the count and refuses the install past the ceiling.

```typescript
const res = await fetch(
  `${serverUrl}/api/v1/public/workspaces/${workspaceId}/limits/installed-apps-per-workspace/claim`,
  {
    method: 'POST',
    headers: {
      'x-session-id': sessionId,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ resourceId: `app:${appId}` }),
  }
);

if (res.status === 402) return showUpgrade(await res.json());
if (!res.ok) throw new Error('Could not install the app');
await installApp(workspaceId, appId);
```

> **Note:**
  Declare a LIMIT item `installed-apps-per-workspace` with `"scope":
  "workspace"`, and give each workspace plan its value: `3` on a Team plan, `-1`
  on a Business plan. The slug is yours to name; BuildBase does not reserve this
  one.

## Install and uninstall

Claim when the app is installed, with the app's id as the `resourceId`. Installing the same app twice is the same claim, so a retried request never takes a second slot. Release with the same `resourceId` when it is removed:

```typescript
await fetch(
  `${serverUrl}/api/v1/public/workspaces/${workspaceId}/limits/installed-apps-per-workspace/release`,
  {
    method: 'POST',
    headers: {
      'x-session-id': sessionId,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ resourceId: `app:${appId}` }),
  }
);
```

The member installing needs permission to record usage in the workspace, the same permission that claims any workspace limit. `GET .../limits/installed-apps-per-workspace` answers the ceiling, `claimed` and `remaining` for a "2 of 3 apps" line, and `GET .../claims` lists what is installed.

Count installs by claims only. If you also pass your own count as `current` to `useLimit`, every app counts twice.

## Per project inside a workspace

Pass a `partitionKey` to count per project instead of per workspace: with a ceiling of 3, each project holds three apps.

```json
{ "resourceId": "app:slack", "partitionKey": "project:42" }
```

## When your end users install

If the people installing are your customers' end users rather than the customer who pays, mark the item `endUserBoundary`. At the ceiling they get `403 CEILING_REACHED` with no plan in the message, and you receive `entitlement.ceiling_reached` with the workspace, the slug and the count, at most once an hour per workspace, so your backend can tell the workspace's owner.

## Selling more installs

A higher plan raises the ceiling, and so does an [add-on](/billing/add-ons): a recurring offer on the same limit sells install slots one at a time, without a plan change.

```json
{
  "slug": "extra-app-slots",
  "name": "+2 app slots",
  "shape": "recurring",
  "items": [
    {
      "itemSlug": "installed-apps-per-workspace",
      "itemKind": "limit",
      "quantityPerUnit": 2,
      "mode": "add"
    }
  ],
  "maxQuantity": 10,
  "pricingVariants": [
    { "currency": "usd", "prices": { "monthly": 500, "yearly": 5000 } }
  ]
}
```

A Team workspace with a ceiling of 3 that buys one unit can install five apps. The claim's `402` is the moment to offer it:

> **Note:**

`usePurchaseAddOn`, `createAddOnPurchaseRedirectUrls` and `useLimit`: **Requires `@buildbase/sdk` 1.0.0 or later.**

```tsx
import {
  createAddOnPurchaseRedirectUrls,
  usePurchaseAddOn,
} from '@buildbase/sdk/react';

function MoreSlots({ workspaceId }: { workspaceId: string }) {
  const { purchaseAddOn, loading } = usePurchaseAddOn(workspaceId);

  return (
    <button
      disabled={loading}
      onClick={() =>
        purchaseAddOn({
          offerSlug: 'extra-app-slots',
          ...createAddOnPurchaseRedirectUrls(workspaceId),
        })
      }
    >
      Add two more app slots
    </button>
  );
}
```

The slots are charged on the workspace's own subscription, for the rest of the period now and then with every renewal. Cancel them and they keep counting until the end of the period already paid for.

**A paid app.** To charge for one app rather than for slots, declare a workspace-scope feature for it, such as `app-salesforce-sync`, sell it as a recurring offer that grants that feature, and check the feature before the install claims its slot. The app's buyers and a plan that includes it pass the same check.

A plan's limit plus any add-on units is the ceiling. Existing installs stay when the ceiling drops, because a plan changed or the add-on ended, and new installs are refused until the workspace is back under it.

## Next steps

- [Plans for users vs plans for workspaces](/billing/users-and-workspaces#claims) - Every claim answer and its code.
- [Add-ons](/billing/add-ons) - Offers, buying, cancelling and refunds.
- [Webhooks](/webhooks/overview) - `entitlement.ceiling_reached` and the rest.
