> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pivotal.app/llms.txt.
> For full documentation content, see https://docs.pivotal.app/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pivotal.app/_mcp/server.

# Onboarding phases

An onboarding's `phase` represents where the engagement sits in the Pivotal workflow. The full enum, in order:

| Phase                    | What it means                                                 |
| ------------------------ | ------------------------------------------------------------- |
| `before_getting_started` | Contract signed, intro call scheduled, nothing's started yet. |
| `program_design`         | Requirements gathering, mockups, program scoping.             |
| `review`                 | Design review and customer sign-off on the program.           |
| `program_buildout`       | Implementation underway.                                      |
| `internal_qa`            | Pre-launch testing by the Pivotal team.                       |
| `launch`                 | Live.                                                         |
| `completed`              | Hand-off to ongoing CS. Terminal.                             |

The `state` field is separate and tracks engagement health: `active`, `paused`, `at_risk`, `waiting`. A `phase` is "where we are"; a `state` is "is anything blocking".

## The phase transition flow

`PATCH /onboardings/{id}` changes one or many fields at once. Phase transitions are the most common pattern:

```bash
curl -X PATCH https://my.pivotal.app/api/v1/onboardings/89 \
  -H "Authorization: Bearer $PIVOTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phase": "program_design"}'
```

Every phase change writes two records on the Pivotal side:

1. A **timeline event** on the underlying customer — surfaces on the customer page.
2. A **phase transition** row — feeds the SLA reports inside Pivotal.

The actor on both is the calling API key's name. Nothing in your code needs to do anything extra to get audit coverage.

## Driving an onboarding end to end

Here's a script that walks an onboarding through every phase, pausing briefly between steps:

```typescript title="advance-onboarding.ts"
const API = "https://my.pivotal.app/api/v1";
const KEY = process.env.PIVOTAL_API_KEY!;
const ID = process.argv[2]; // pass onboarding display_id or cuid

const phases = [
  "before_getting_started",
  "program_design",
  "review",
  "program_buildout",
  "internal_qa",
  "launch",
  "completed",
];

async function setPhase(phase: string) {
  const res = await fetch(`${API}/onboardings/${ID}`, {
    method: "PATCH",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ phase }),
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${body.error?.code}`);
  console.log(`→ ${phase}  (updated_at=${body.updated_at})`);
}

for (const phase of phases) {
  await setPhase(phase);
  await new Promise((r) => setTimeout(r, 1000));
}
```

Run it on a test onboarding. Refresh the customer page in Pivotal — the timeline shows each transition.

## Combining phase + state

When an onboarding stalls, set `state` to `paused`, `at_risk`, or `waiting` **without** changing the phase:

```bash
curl -X PATCH https://my.pivotal.app/api/v1/onboardings/89 \
  -H "Authorization: Bearer $PIVOTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"state": "at_risk"}'
```

When it un-sticks, set state back to `active`:

```bash
curl -X PATCH https://my.pivotal.app/api/v1/onboardings/89 \
  -H "Authorization: Bearer $PIVOTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"state": "active"}'
```

Separating "where we are" from "is anything wrong" keeps the phase progression clean and the SLA reporting honest.

The `waiting` state is special: combined with `waiting_on_customer: true`, it tells Pivotal a customer task is blocking, which suspends the SLA clock until the state flips back.

## What you shouldn't do

* **Don't skip phases for cosmetics.** Pivotal computes phase-duration metrics — jumping `before_getting_started` → `launch` reads as "this onboarding launched in 4 days" in dashboards.
* **Don't reuse `completed` for anything else.** It's the terminal phase. Use `state: "paused"` to retract a launch, or `DELETE` to soft-delete a wrong record.
* **Don't PATCH `phase` on a soft-deleted onboarding.** You'll get `404 onboarding_not_found`.

## Querying by phase

`GET /onboardings?phase=launch` filters by phase. Combine with `state`:

```bash
curl "https://my.pivotal.app/api/v1/onboardings?phase=launch&state=active&limit=50" \
  -H "Authorization: Bearer $PIVOTAL_API_KEY"
```

Combined with cursor pagination ([Pagination](/api/welcome/pagination)), this is enough to build a dashboard that polls "what's in launch this week".

## Hooking into Pivotal automations

When `phase` flips to `launch`, Pivotal fires its built-in launch-day automations: a Slack DM to the assigned CSM, a launch entry in the customer calendar, and (if the workspace has it enabled) an auto-drafted launch-recap email. These run inside Pivotal regardless of whether the phase change came from the UI or the API.

If you want to suppress those — say, you're backfilling historical onboardings — make the calls with a **test key** (`pivotal_test_…`). See [Test mode](/api/guides/test-mode).