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

# Building a client onboarding funnel

> Create a configured client project, connect the client's integrations, and hand off an embeddable invite link — all over the REST API, with no Sapt emails in the loop.

When you onboard clients through your own app or funnel, Sapt stays behind the scenes. A handful of REST calls spin up a **fully-configured client project**, connect the client's tools, and produce **links you embed in your own buttons** — so the client never receives a Sapt-branded email and never sees the Sapt dashboard until they're ready.

Every client project is created as a **sub-project of your parent project** — your top-level account project — so it automatically shares that project's plan and credit pool. You subscribe once and pay from one account, however many clients you run. See [Billing & projects](/docs/mintlify/platform/billing) for exactly how the shared pool works.

## How the pieces fit

Three things are involved. Get these straight and the rest is easy:

| Piece                | What it is                                                                                                                                                                                    |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Parent project**   | Your own top-level project. It owns billing — one plan, one credit pool — and it stores your templates. It's the `{projectId}` in every call below, and the only place you need admin rights. |
| **Template**         | A saved bundle of configuration (roles, workflows, CRM types, branding, …). A template is a **blueprint, not a project**, and it lives **on your parent project**.                            |
| **Client workspace** | Each project the funnel creates: a **sub-project of your parent**, built from the template, sharing the parent's billing. One per client.                                                     |

In one sentence: **under your *parent* project, you stamp a *template* to create a new *child* project for the client.** Two rules follow from that:

* **The child belongs to the parent project, never to the template.** A template isn't a project — it's just the configuration that gets copied in. Every client is a sub-project of your parent project, which is exactly why they all share its billing and credits.
* **The template must live on that same parent project.** You pass both in one path — `…/projects/{parentId}/templates/{templateId}/apply` — and Sapt requires the template to belong to that parent. Keep your client templates on your parent project; one project can hold as many as you like. (Sourcing a template from a *different* project than the billing parent isn't supported — by design, so a client always resolves to one clear billing owner.)

The only access you need is **admin on the parent project** — the `sub_projects:write` permission. No per-client billing, no separate accounts, nothing else to wire up.

The pattern across the steps below is the same every time: **you call Sapt, Sapt hands you back a link or an id, you drive your own UI with it.** Three phases:

<Note>
  **Prerequisite — a template saved on your parent project.** Create one from the dashboard while in your parent project (**Snapshots → Save**) or over the API with `POST /projects/{parentProjectId}/snapshot`, then reference its `templateId` below. A snapshot captures that project's configuration — roles, sidebar, workflows, CRM types/relations, branding, brand voice, and agents — and stores it as a reusable template on that same project.
</Note>

## 1. Create the client project from your template

One call creates the sub-project **and** stamps your template onto it.

<Note>
  **`{projectId}` is your *parent* project** (the billing owner, where the template lives); **`{templateId}`** is the template on it. The client is created as a **sub-project beneath the parent** and inherits its billing.
</Note>

```bash theme={null}
curl -X POST https://api.sapt.ai/projects/$PARENT_PROJECT_ID/templates/$TEMPLATE_ID/apply \
  -H "Authorization: ApiKey sapt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Acme Co",
        "variableValues": { "clientName": "Acme", "supportEmail": "help@acme.com" }
      }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "project": { "id": "9f1c…", "name": "Acme Co", "slug": "acme-co", "createdAt": "2026-06-24T11:00:00.000Z" },
    "templateApply": {
      "status": "applied",
      "report": {
        "counts": { "roles": 3, "workflows": 5, "objectTypes": 4, "objectRelations": 2, "sidebarSections": 3, "perRoleHides": 1 },
        "appliedCount": 21,
        "skipped": [],
        "failures": [],
        "missingVariables": []
      }
    }
  }
}
```

* **`data.project.id`** is the new client project — carry it through every step below.
* **`variableValues`** fills any `{{placeholders}}` your template declares (client name, support email, brand color, …). Anything you leave out comes back under `report.missingVariables` and is left as a literal placeholder to fill later.
* **`report.failures`** is empty on a clean apply; anything that couldn't be created is listed there with a reason rather than failing the whole call.

## 2. Connect the client's integrations

Mint a secure **connect link** for each tool the client needs to hook up — Meta, Gmail, Google Business Profile, and the rest. You hand the client the link (embed it in a button), and you poll until they finish signing in. The client connects their own accounts without ever logging into Sapt.

```bash theme={null}
# Mint a connect link for a provider (set clientInvite so the link survives being embedded).
curl -X POST https://api.sapt.ai/projects/$CLIENT_PROJECT_ID/connect-sessions \
  -H "Authorization: ApiKey sapt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "providerId": "meta", "clientInvite": true }'
```

```json theme={null}
{
  "connectUrl": "https://api.sapt.ai/integrations/oauth/meta/start/abc123…",
  "token": "abc123…",
  "expiresAt": "2026-06-24T12:00:00.000Z"
}
```

`connectUrl` is the link you drop into your funnel — a "Connect Meta" button. After the client finishes, poll the session until it leaves `pending`:

```bash theme={null}
curl https://api.sapt.ai/projects/$CLIENT_PROJECT_ID/connect-sessions/$TOKEN \
  -H "Authorization: ApiKey sapt_your_api_key"
# → { "status": "completed", ... }   (pending → completed | failed)
```

<Note>
  `clientInvite: true` extends the link's lifetime so it survives being embedded in an onboarding step instead of expiring in minutes. To discover which providers a project can connect (and their exact `providerId` / `authKind`), call `GET /projects/{id}/integrations`. The connect flow — every provider, API-key integrations, and the full status table — is covered in **[Connecting integrations](/docs/mintlify/api/connect-integrations)**.
</Note>

## 3. Hand off an invite link — not an email

Finally, give the client access to their finished workspace. Pass **`sendEmail: false`** and Sapt skips its stock email and returns an **`acceptUrl`** instead — the link you wire into your own "Get started" button.

```bash theme={null}
# First grab the role to assign (your template ships its own roles).
curl https://api.sapt.ai/projects/$CLIENT_PROJECT_ID/roles \
  -H "Authorization: ApiKey sapt_your_api_key"
# → { "success": true, "data": [ { "id": "2a7b…", "name": "Admin" }, … ] }

# Then invite — sendEmail:false returns the embeddable acceptUrl.
curl -X POST https://api.sapt.ai/projects/$CLIENT_PROJECT_ID/invitations \
  -H "Authorization: ApiKey sapt_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "email": "owner@acme.com", "projectRoleId": "'$ROLE_ID'", "sendEmail": false }'
```

```json theme={null}
{
  "invitationId": "b3e9…",
  "email": "owner@acme.com",
  "action": "invited",
  "expiresAt": "2026-07-01T11:00:00.000Z",
  "acceptUrl": "https://app.sapt.ai/accept-invite?token=…"
}
```

| Field       | What it tells you                                                                                                                      |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `acceptUrl` | Where the client accepts the invite. Returned **only** when `sendEmail: false` — embed it in your button, QR code, or onboarding step. |
| `action`    | `invited` (a new invitation was created) or `skipped` (e.g. the user is already a member).                                             |
| `expiresAt` | When the link stops working.                                                                                                           |

<Note>
  Want Sapt to email the invite instead? Omit `sendEmail` (or set it to `true`) and Sapt sends the acceptance link directly — `acceptUrl` comes back `null`. The `sendEmail: false` path is the one to use when *you* own the welcome experience.
</Note>

## Putting it together

The whole funnel, end to end, threading each response into the next with [`jq`](https://jqlang.github.io/jq/):

```bash theme={null}
API="https://api.sapt.ai"
AUTH="Authorization: ApiKey sapt_your_api_key"

# 1. Create + configure the client project from your template.
CLIENT_ID=$(curl -s -X POST "$API/projects/$PARENT_PROJECT_ID/templates/$TEMPLATE_ID/apply" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{ "name": "Acme Co", "variableValues": { "clientName": "Acme" } }' \
  | jq -r '.data.project.id')

# 2. Mint a connect link for each integration the client needs (embed each in a button).
CONNECT_URL=$(curl -s -X POST "$API/projects/$CLIENT_ID/connect-sessions" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{ "providerId": "meta", "clientInvite": true }' \
  | jq -r '.connectUrl')

# 3. Grab a role, then mint the embeddable invite link.
ROLE_ID=$(curl -s "$API/projects/$CLIENT_ID/roles" -H "$AUTH" \
  | jq -r '.data[] | select(.name == "Admin") | .id')

ACCEPT_URL=$(curl -s -X POST "$API/projects/$CLIENT_ID/invitations" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d "{ \"email\": \"owner@acme.com\", \"projectRoleId\": \"$ROLE_ID\", \"sendEmail\": false }" \
  | jq -r '.acceptUrl')

echo "Client project: $CLIENT_ID"
echo "Connect Meta button → $CONNECT_URL"
echo "Get started button  → $ACCEPT_URL"
```

Both `connectUrl` and `acceptUrl` are links you own — wire them into buttons, emails, QR codes, or wherever your funnel lives. Nothing is sent on Sapt's behalf unless you ask for it.

<Note>
  **Auth.** Use an API key that holds `sub_projects:write` on the parent project (step 1) plus access to the new project's integrations, roles, and members (steps 2–3). The key that creates the project gets that access on the project it just made.
</Note>
