# Let customers sign in

URL: /products/app/let-customers-sign-in · Id: `guide:let-customers-sign-in`

> Say "let my customers sign in" and get a private workspace per customer, passkey and email-link sign-in on your domain, and invites for their team.

Each [Customer](/business/nouns/customer) gets a private workspace, its [Tenant](/business/nouns/tenant), and its people sign in on your [Domain](/business/nouns/domain) with the [Role](/business/nouns/role) you give them.

Needs: [Customer](/business/nouns/customer)

**Before it runs**

- You leave the chat: Never: it all happens in the chat.
- First question at Live: “Who at the customer gets the first invite?”

## What you get
- 1 private workspace per customer, kept apart from every other (Tenant)
- 1 sign-in by passkey or email link on your domain (Portal)
- 1 invite email from your domain, with your brand (Message)
- 1 member with a role inside that workspace (Person)
- 1 seat added to the bill when the plan prices per person (Subscription)
- 1 seat count kept in step with the bill (Subscription)
- 1 single sign-on and directory sync, off until a customer asks (SSO Connection)

## Your agent does
**For agents**

- Guide: `guide:let-customers-sign-in`, status `live`. Page: [/products/app/let-customers-sign-in.md](/products/app/let-customers-sign-in.md).
- Approval: none. Run it when the founder asks.
- Leaves the chat: never.
- Needs first: [Customer](/business/nouns/customer).
- Run: call the steps in order, each through the API, SDK, CLI or MCP tab below; a later step passes the `$id` an earlier one returns. Each value marked example is the sample business’s: pass the founder’s own.

| Step | Verb | Inputs (type, required, this guide’s value) | Returns |
| --- | --- | --- | --- |
| 1 | [`Tenant.provision`](/business/nouns/tenant/provision) (`verb:Tenant.provision`) | `customer` Customer (an id), required: example `"customer_harbor"` | `tenant` Tenant |
| 2 | [`Tenant.invite`](/business/nouns/tenant/invite) (`verb:Tenant.invite`) | `tenant` Tenant (an id), required: the `$id` step 1 returns; `email` email, required: example `"maria@harborlets.com"` | `person` Person, `tenant` Tenant |

Returns: the last call answers with `person`, `tenant` and its `action`, whose `status` is `done`, `pending` (waiting on an Approval or a Worker), `failed`, `canceled` or `refused`:

```json
{
  "api": "https://api.sb/leaseline.app",
  "person": {
    "$id": "https://api.sb/leaseline.app/people/person_1",
    "$type": "Person"
  },
  "tenant": {
    "$id": "https://api.sb/leaseline.app/tenants/tenant_1",
    "$type": "Tenant"
  },
  "action": {
    "$id": "https://api.sb/leaseline.app/actions/action_1",
    "operation": "Tenant.invite",
    "status": "done"
  },
  "links": {
    "self": "https://api.sb/leaseline.app/actions/action_1"
  }
}
```

Next: offer [Set up SSO for a customer](/products/app/set-up-sso-for-a-customer) (`guide:set-up-sso-for-a-customer`) when `Deal.advanced` fires, or “Require single sign-on for every customer above 50 seats”, or “Let a customer admin remove members”.

Calls: `Tenant.provision` → `Tenant.invite`

Ask:

```text
Let my customers sign in.
Let a property manager invite their team.
Turn on single sign-on for customers who need it.
```

API:

```bash
# 1. Tenant.provision
TENANT=$(curl -X POST https://api.sb/leaseline.app/tenants/provision \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer":"customer_harbor"}' | jq -r '.tenant["$id"] | split("/") | last')

# 2. Tenant.invite
curl -X POST https://api.sb/leaseline.app/tenants/$TENANT/invite \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"maria@harborlets.com"}'
```

SDK:

```ts
import { $ } from 'startup-builder'

const tenant = await $.Tenant.provision({ customer: 'customer_harbor' })
await $.Tenant.invite({ tenant: tenant.$id, email: 'maria@harborlets.com' })
```

CLI:

```bash
TENANT=$(npx startup-builder do Tenant.provision --json '{"customer":"customer_harbor"}' | jq -r '.tenant["$id"] | split("/") | last')
npx startup-builder do Tenant.invite --json '{"tenant":"'"$TENANT"'","email":"maria@harborlets.com"}'
```

MCP:

```json
{
  "name": "do",
  "arguments": {
    "code": "const tenant = await $.Tenant.provision({ customer: 'customer_harbor' })\nreturn await $.Tenant.invite({ tenant: tenant.$id, email: 'maria@harborlets.com' })"
  }
}
```

## Change it
- Require single sign-on for every customer above 50 seats.
- Let a customer admin remove members.
- Sign people out after 12 hours.

## Under the hood
- Systems: [Customer access](/systems/customer-access)
- Nouns: [Tenant](/business/nouns/tenant), [Customer](/business/nouns/customer), [Person](/business/nouns/person), [Role](/business/nouns/role), [SSO Connection](/business/nouns/sso-connection)
- Events: [`Tenant.provisioned`](/business/events/Tenant.provisioned), [`Tenant.invited`](/business/events/Tenant.invited)
- Stripe objects: `Subscription Item`