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

# Create your first customer

This guide pushes a complete customer record through the API in a single Node script. You end with a customer, a primary contact, and an active onboarding — visible in `https://my.pivotal.app/customers/<slug>` when the script finishes.

## Prerequisites

* A live API key (`pivotal_…`) from [Admin > API Keys](https://my.pivotal.app/admin/api-keys).
* Node 20+ or Bun. The script uses native `fetch`.
* A scratch slug — pick something that won't collide with a real customer in your workspace. We'll use `aurora-demo`.

Export the key once:

```bash
export PIVOTAL_API_KEY=pivotal_…
```

## The script

```typescript title="create-customer.ts"
const API = "https://my.pivotal.app/api/v1";
const KEY = process.env.PIVOTAL_API_KEY!;
if (!KEY) throw new Error("PIVOTAL_API_KEY is required");

const h = {
  Authorization: `Bearer ${KEY}`,
  "Content-Type": "application/json",
};

async function call<T>(path: string, init?: RequestInit): Promise<T> {
  const res = await fetch(`${API}${path}`, { ...init, headers: { ...h, ...(init?.headers ?? {}) } });
  const body = await res.json();
  if (!res.ok) {
    console.error("API error:", body);
    throw new Error(`${res.status} ${body?.error?.code ?? "unknown"}`);
  }
  return body as T;
}

// 1. Create the customer
const customer = await call<{ id: string; display_id: number; slug: string }>(
  "/customers",
  {
    method: "POST",
    headers: { "Idempotency-Key": "first-customer-aurora-demo" },
    body: JSON.stringify({
      name: "Aurora Outfitters",
      slug: "aurora-demo",
      domain: "aurora-demo.com",
      status: "onboarding",
    }),
  },
);
console.log(`Customer: ${customer.display_id} (${customer.slug})`);

// 2. Attach a primary contact
const contact = await call<{ id: string; display_id: number; email: string }>(
  `/customers/${customer.display_id}/contacts`,
  {
    method: "POST",
    body: JSON.stringify({
      name: "Mira Chen",
      email: "mira@aurora-demo.com",
      title: "Head of Ops",
      is_primary: true,
      labels: ["billing", "technical"],
    }),
  },
);
console.log(`Contact: ${contact.display_id} (${contact.email})`);

// 3. Open an onboarding
const onboarding = await call<{ id: string; display_id: number; phase: string; state: string }>(
  "/onboardings",
  {
    method: "POST",
    headers: { "Idempotency-Key": "first-customer-aurora-demo-onb" },
    body: JSON.stringify({
      customer_id: customer.display_id.toString(),
      phase: "before_getting_started",
      state: "active",
      target_launch_date: "2026-07-15T00:00:00Z",
    }),
  },
);
console.log(`Onboarding: ${onboarding.display_id} (${onboarding.phase})`);

console.log(`\nOpen https://my.pivotal.app/customers/${customer.display_id}-${customer.slug}`);
```

Run it:

```bash
bun create-customer.ts
# or: ts-node create-customer.ts
```

## What happened

Three rows landed:

1. A **customer** in your workspace with `display_id` assigned by Pivotal — the small integer that will appear in the URL.
2. A **contact** under that customer. Because `is_primary: true`, the customer detail page surfaces her at the top of the contacts card.
3. An **onboarding** at the `before_getting_started` phase with a launch date 6+ weeks out.

The customer's activity feed now has three rows, each labelled `API: <key-name>` as the actor. The same events flow into the per-workspace audit log at [Admin > Logs](https://my.pivotal.app/admin/logs).

## Trying it again safely

Re-run the script. Because we sent `Idempotency-Key` on both creates, the second pass returns the cached responses instead of creating duplicates. Nice property to lean on inside webhook handlers and cron jobs.

Drop the `Idempotency-Key` headers and re-run — you'll get `409 slug_taken` on the customer create. That's the expected behavior. Pick a fresh slug or send a `PATCH` instead.

## Cleaning up

The `aurora-demo` customer is soft-deletable:

```bash
curl -X DELETE "https://my.pivotal.app/api/v1/customers/${DISPLAY_ID}" \
  -H "Authorization: Bearer $PIVOTAL_API_KEY"
```

The associated contact and onboarding survive the delete but disappear from list views (the parent customer is gone from list views too). Reach out if you need a hard delete.

## Next

* Set up two-way sync with your CRM in [Integration sync](/api/guides/integration-sync).
* Drive an onboarding through its phases in [Onboarding phases](/api/guides/onboarding-phases).
* Build the same flow in test mode — see [Test mode](/api/guides/test-mode).