> For the complete documentation index, see [llms.txt](https://docs.nerovasystems.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nerovasystems.com/guides/provision/partner-playbook.md).

# Provision and activate tenants

Build your platform's provisioning integration: create tenants idempotently for every merchant on your platform, attach their provider connections, drive activation until the manifest reports `ready: true`, and recover from every failure state without creating duplicates.

> **Experimental API.** Everything on this page runs on your Live key (`nrv_live_`), which starts on a free testing allowance and keeps working once a commercial agreement is signed or a card is saved. Only Live keys exist: any attempt to create a non-Live key returns `403 api_key.sandbox_disabled`. See [Lifecycle and availability](https://docs.nerovasystems.com/documentation/api/lifecycle).

## What you'll build

A provisioning pipeline — a queue consumer, a migration script, or a signup-flow handler — that takes a merchant from your system of record to an active Nerova tenant, and that is safe to re-run at every step.

This guide goes deep on the operational *how*. For the model itself — what a tenant is, the two state machines it reports, and how grants control visibility — read [Accounts and tenants](https://docs.nerovasystems.com/documentation/concepts/accounts-and-tenants) first.

## Prerequisites

* A Live key with the scopes `merchant:provision`, `merchant:read`, `capability:read`, `channel:manage`, `employee:read`, `employee:manage`, `mandate:read`, `mandate:write`, and `receipt:read`. The [Quickstart](https://docs.nerovasystems.com/documentation/getting-started/quickstart) covers key creation; the scope table lives in [Authentication](https://docs.nerovasystems.com/documentation/getting-started/authentication#scopes).
* Follow [Platform access](https://docs.nerovasystems.com/documentation/console/platform-access) for approval and the free testing allowance. The allowance covers your test tenants; if it runs out before a commercial agreement is signed or a card is saved, the key and the AI pause.
* A stable identifier for each merchant in your own system of record. You will store it as `externalReference` and derive idempotency keys from it.

## The lifecycle at a glance

```
POST /tenants ──► provisioningState: AccountTenantPending
                        │ (poll GET /tenants/{tenantId})
                        ▼
                  MachineGrantPending ──► Ready   or   Failed (lastErrorCode)
                                           │
                  POST /tenants/{tenantId}/connections
                                           │
                  GET  /tenants/{tenantId}/activation/manifest
                        │ resolve every blockingReason
                        ▼
                  POST /tenants/{tenantId}/activation/activate ──► receipt
```

Two rules make the whole pipeline safe:

1. **Every mutation carries a caller-generated `Idempotency-Key`.** Replaying a request returns the prior result instead of acting twice.
2. **The activation manifest is the only readiness authority.** Until it says `ready: true`, runtime work fails closed — you never have to guess whether a tenant is usable.

## 1. Create tenants idempotently

Derive the idempotency key from your own merchant identifier, not from a random value generated per attempt. That way a crashed worker, a redelivered queue message, or a re-run migration replays the same command:

```bash
curl --request POST \
  --url "https://api.nerovasystems.com/api/v1/tenants" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NEROVA_API_KEY}" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: provision-tenant-your-crm-id-123" \
  --data '{
    "displayName": "Demo Salon",
    "externalReference": "your-crm-id-123"
  }'
```

Store the returned tenant `id` against your merchant record, but treat `externalReference` as the durable join: Nerova returns it on every tenant read, so a lost mapping table is recoverable from `GET /api/v1/tenants`.

Do not reuse an idempotency key for a different command. If you rename a merchant, that is a `PATCH /api/v1/tenants/{tenantId}` with its own key — not a replayed create.

## 2. Poll provisioning to Ready

Creation is asynchronous in the general case. Poll the tenant until `provisioningState` leaves its pending states:

```bash
curl --request GET \
  --url "https://api.nerovasystems.com/api/v1/tenants/{tenantId}" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NEROVA_API_KEY}"
```

| `provisioningState`    | What it means                                         | What you do                                 |
| ---------------------- | ----------------------------------------------------- | ------------------------------------------- |
| `AccountTenantPending` | The account-side tenant record is being created       | Wait and poll; `pendingStep` names the wait |
| `MachineGrantPending`  | Your key's grant to the new tenant is being installed | Wait and poll                               |
| `Ready`                | Provisioning is complete                              | Continue to connections                     |
| `Failed`               | A step failed; `lastErrorCode` carries a stable code  | See below                                   |

Poll with bounded backoff (for example 1 s, 2 s, 5 s, then every 10 s) rather than a tight loop. Your pipeline should tolerate asynchronous provisioning.

**On `Failed`:** `lastErrorCode` is a stable, partner-safe code such as `account.tenant_unavailable` or `account.tenant_grant_unavailable` — it names the failed step without leaking internals. Log it with the tenant `id` and the `X-Request-Id` of the read, then re-issue the original create with the **same** `Idempotency-Key`. Provisioning resumes or replays safely; it never creates a second tenant for the same key.

## 3. Attach the provider connection

A `Ready` tenant still cannot do work: it has no connection to the provider platform that owns its calendar. Create one (scope `channel:manage`):

```bash
curl --request POST \
  --url "https://api.nerovasystems.com/api/v1/tenants/{tenantId}/connections" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NEROVA_API_KEY}" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: connect-your-crm-id-123" \
  --data '{
    "provider": "ProviderApiV1",
    "credential": { "apiKey": "<PROVIDER_CREDENTIAL>" },
    "externalMerchantId": "provider-merchant-42",
    "externalLocationId": "provider-location-1",
    "displayName": "Demo Salon — main location",
    "timeZone": "Africa/Johannesburg",
    "currency": "ZAR"
  }'
```

Nerova validates the credential against the provider before storing it — an invalid credential is rejected, not stored — and never redisplays it. A second create for the same provider binding returns `409`; read the existing connection back instead of retrying. The full credential model, including the `state`, `health`, and `certificationState` fields on the response, is in [Connections and provider credentials](https://docs.nerovasystems.com/documentation/concepts/connections).

For a test tenant, point the connection at a provider environment you control, such as the [reference provider](https://docs.nerovasystems.com/guides/zero-to-live/step-3-implement-provider-api) you build in Zero to live. The merchant and location identifiers above are placeholders.

## 4. Drive activation from the manifest

The activation manifest is a checklist the server maintains for you. Read it, resolve what blocks, and repeat:

```bash
curl --request GET \
  --url "https://api.nerovasystems.com/api/v1/tenants/{tenantId}/activation/manifest" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NEROVA_API_KEY}"
```

The response reports `ready`, an array of `blockingReasons` — each with a stable `code`, a `category`, and a partner-safe `detail` — and `nextAllowedActions`, which names the activation calls the tenant accepts in its current state. Branch on `code` and `category`, never on the human-readable `detail`.

Work through the blocking reasons with the activation endpoints. All of them live under `/api/v1/tenants/{tenantId}/activation/`, take an `Idempotency-Key`, and accept an `expectedVersion` for optimistic concurrency:

| Step                    | Call                                                                                                       | Purpose                                                                                                                                                                                      |
| ----------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Provisioning references | `PUT …/activation/provisioning`                                                                            | Record your host merchant reference, the provider merchant reference, and location mappings                                                                                                  |
| Identity confirmation   | `POST …/activation/identity-confirmations`                                                                 | Confirm the host and provider references describe the same business                                                                                                                          |
| Channel connection      | `POST …/activation/connection-sessions`, then `POST …/activation/connection-sessions/{sessionId}/complete` | Bind the customer-facing channel (`"channel": "WhatsApp"`). The create response returns a hosted `launchUrl` for the merchant to open; the session expires after 10 minutes if not completed |
| Autonomy mandate        | `PUT …/activation/mandate`                                                                                 | Set the requested level per duty (`Never`, `AskFirst`, `DoItTellMe`, `JustDoIt`), capped by the manifest's `mandateCeiling`                                                                  |
| Consents                | `PUT …/activation/consents`                                                                                | Capture the merchant's acceptance of each consent requirement at its current version                                                                                                         |

Each mutation response echoes an `idempotencyStatus` of `Applied` or `Replayed`, so your pipeline can tell a first execution from a safe replay.

Before flipping the switch, dry-run the readiness check:

```bash
curl --request POST \
  --url "https://api.nerovasystems.com/api/v1/tenants/{tenantId}/activation/preview" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NEROVA_API_KEY}" \
  --header "Idempotency-Key: preview-your-crm-id-123-01"
```

Preview evaluates provider evidence, channel evidence, and remaining `blockingReasons` without changing anything. When it reports `ready: true`, activate:

```bash
curl --request POST \
  --url "https://api.nerovasystems.com/api/v1/tenants/{tenantId}/activation/activate" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NEROVA_API_KEY}" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: activate-your-crm-id-123" \
  --data '{ "reasonCode": "merchant_onboarding_complete" }'
```

Activation returns `201` with a **receipt**: hashes of the provider and channel evidence (`providerEvidenceHash`, `channelEvidenceHash`), the `mandateContractVersion`, and the `consentVersions` the activation was granted on. Store `receiptId`; you can re-fetch the receipt any time with `GET …/activation/receipts/{receiptId}` (scope `receipt:read`). The receipt is your audit answer to "what exactly was this tenant activated with?".

Activating a tenant that is not ready fails closed with a problem detail that carries the outstanding blocking reasons — the same codes the manifest shows. Nothing activates partially.

## 5. Handle failures without duplicating work

### Replace a bad credential and resume

If a provider credential is rejected at connection creation, or a previously `Active` connection turns `Invalid` (rotated or revoked upstream), replace the secret in place:

```bash
curl --request PUT \
  --url "https://api.nerovasystems.com/api/v1/tenants/{tenantId}/connections/{connectionId}/credential" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${NEROVA_API_KEY}" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: recredential-your-crm-id-123-02" \
  --data '{ "credential": { "apiKey": "<NEW_PROVIDER_CREDENTIAL>" } }'
```

Replacement is validate-before-swap: a failing new secret leaves the old one untouched, and a successful swap increments `credentialVersion`. Confirm health with `POST …/connections/{connectionId}/test`, then resume the activation loop from the manifest — the blocking reasons that pointed at the connection clear on the next read. For an already-active tenant, run `POST …/activation/reconcile` to re-evaluate evidence after the repair.

### Interrupted pipeline

Every step above is a safe re-entry point. On restart, your worker reads `GET /api/v1/tenants/{tenantId}` and the activation manifest, then replays the step the state machines point at — with the original idempotency keys. Never mint a fresh key to "force" a retry of the same command; if a write timed out, read the resource back first, as described in [Conventions](https://docs.nerovasystems.com/documentation/api/conventions#idempotency).

### Concurrency conflicts

If two workers race on the same tenant, the loser gets `409` (state moved in a conflicting way) or `412` (its `expectedVersion` is stale). Both mean the same thing operationally: re-read the manifest, recompute the next step, and retry with the current `version`. See [Handle errors and retries](/guides/build-well/error-handling.md#conflicts-409-and-412).

## 6. Operate the fleet

* **Pause, suspend, resume.** `POST …/activation/pause` (the merchant asked to stop), `POST …/activation/suspend` (you or Nerova stopped it), and `POST …/activation/activate` again to resume — each with a `reasonCode`. Tenant-level lifecycle (`Active`, `Suspended`, `Archived`) is separate: `POST /api/v1/tenants/{tenantId}/lifecycle/{state}`.
* **Watch attention items.** `GET /api/v1/tenants/{tenantId}/attention` surfaces what needs a human; acknowledge, resolve, or hand off per item.
* **Read the ledgers.** The work ledger (`GET …/work`) and activity feed (`GET …/activity`) record what happened — metadata only, never conversation content; see [Channels and routing](https://docs.nerovasystems.com/documentation/concepts/channels#the-activity-ledger-is-metadata-only).
* **Offboard cleanly.** `POST …/activation/deactivate` with a reason code, then archive the tenant. Deleting a connection an active tenant depends on fails closed and surfaces in `blockingReasons` — deactivate first.

## Next steps

* Wire the same pipeline through a typed client: [SDK guides](https://docs.nerovasystems.com/sdks).
* Harden every call in this pipeline: [Handle errors and retries](/guides/build-well/error-handling.md).
* Plan the path to real merchants: [Go-live checklist](/guides/provision/go-live.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.nerovasystems.com/guides/provision/partner-playbook.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
