> 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.

# Quickstart

This walkthrough takes you from an empty workspace to a customer, contact, and onboarding visible in the Pivotal UI. Total wall time: about three minutes.

## 1. Create an API key

Open [Admin > API Keys](https://my.pivotal.app/admin/api-keys) inside your workspace and click **Create key**. Pick a name (`local-dev`, `prod-server`, `ci`) and the environment:

* **Live** — `pivotal_…` — mutates production state, fires webhooks, counts against the live quota.
* **Test** — `pivotal_test_…` — mutates production state too, but skips outbound integration calls (Slack, Resend, Stripe). See [Test mode](/api/guides/test-mode).

Copy the key once and stash it in your secret manager. Pivotal stores only a hash; the cleartext is shown exactly once at create time.

Treat keys like passwords. They grant full read/write across the workspace. Rotate by creating a new key, swapping it in your services, then revoking the old one — there is no "downtime" between create and revoke.

## 2. Confirm the key works

Hit `/me`. It echoes the key's identity and the workspace it belongs to. No side effects, costs one request from your rate limit.

```bash title="curl"
curl https://my.pivotal.app/api/v1/me \
  -H "Authorization: Bearer pivotal_REPLACE_ME"
```

```typescript title="Node (fetch)"
const res = await fetch("https://my.pivotal.app/api/v1/me", {
  headers: { Authorization: `Bearer ${process.env.PIVOTAL_API_KEY}` },
});
const me = await res.json();
console.log(me);
```

```python title="Python (requests)"
import os, requests
res = requests.get(
    "https://my.pivotal.app/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['PIVOTAL_API_KEY']}"},
)
print(res.json())
```

```go title="Go (net/http)"
req, _ := http.NewRequest("GET", "https://my.pivotal.app/api/v1/me", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PIVOTAL_API_KEY"))
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
```

You should see something like:

```json
{
  "object": "api_key_identity",
  "key_name": "local-dev",
  "key_prefix": "pivotal_abc1",
  "org_id": "org_2pXh…",
  "org_slug": "vml",
  "mode": "live"
}
```

If you get a `401`, double-check the header format — it's `Authorization: Bearer <key>`, not `Bearer: <key>` or `Authorization: <key>`.

## 3. Create a customer

```bash title="curl"
curl https://my.pivotal.app/api/v1/customers \
  -H "Authorization: Bearer pivotal_REPLACE_ME" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Aurora Outfitters",
    "slug": "aurora",
    "domain": "aurora.com",
    "status": "onboarding"
  }'
```

```typescript title="Node (fetch)"
const res = await fetch("https://my.pivotal.app/api/v1/customers", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PIVOTAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Aurora Outfitters",
    slug: "aurora",
    domain: "aurora.com",
    status: "onboarding",
  }),
});
const customer = await res.json();
```

```python title="Python (requests)"
res = requests.post(
    "https://my.pivotal.app/api/v1/customers",
    headers={
        "Authorization": f"Bearer {os.environ['PIVOTAL_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "name": "Aurora Outfitters",
        "slug": "aurora",
        "domain": "aurora.com",
        "status": "onboarding",
    },
)
customer = res.json()
```

```go title="Go (net/http)"
body, _ := json.Marshal(map[string]any{
    "name":            "Aurora Outfitters",
    "slug":            "aurora",
    "domain":          "aurora.com",
    "status":          "onboarding",
})
req, _ := http.NewRequest("POST", "https://my.pivotal.app/api/v1/customers", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PIVOTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
```

`slug` is the URL-safe handle. Pivotal also returns a `display_id` (the small integer that appears in URLs like `/customers/245`).

```json
{
  "object": "customer",
  "id": "cl9bn00of0000g7l8gcq8c4xk",
  "display_id": 245,
  "name": "Aurora Outfitters",
  "slug": "aurora",
  "domain": "aurora.com",
  "status": "onboarding",
  "created_at": "2026-05-26T17:21:09.412Z",
  "updated_at": "2026-05-26T17:21:09.412Z"
}
```

## 4. Attach a primary contact

```bash
curl https://my.pivotal.app/api/v1/customers/245/contacts \
  -H "Authorization: Bearer pivotal_REPLACE_ME" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mira Chen",
    "email": "mira@aurora.com",
    "title": "Head of Ops",
    "is_primary": true
  }'
```

Note the path — contacts always live under a customer. The `245` is the customer's `display_id`; the customer's cuid works just as well.

## 5. Open an onboarding

```bash
curl https://my.pivotal.app/api/v1/onboardings \
  -H "Authorization: Bearer pivotal_REPLACE_ME" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "245",
    "phase": "before_getting_started",
    "state": "active",
    "target_launch_date": "2026-07-15T00:00:00Z"
  }'
```

Refresh `/customers/aurora` in the Pivotal UI. You should see Mira on the customer card and the loyalty launch onboarding in the sidebar. The activity feed will show an entry for each API call — the calling key's name is the actor.

## Next

Key formats, rotation, and the difference between live and test.

Every error code you can get and what to do about it.

Safe retries on `POST` with `Idempotency-Key`.

Walk a list end to end without missing rows.