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

# Control Plane API Reference

> Complete endpoint reference for Profile, Settings, Team, and Support endpoint families exposed by @pcnaid/applepay-control-plane.

# Control Plane API Reference

This specification documents the request bodies, responses, authentication scopes, and error semantics for the Profile, Settings, Team, and Support endpoint families exposed by `@pcnaid/applepay-control-plane` at `https://api.lifecyclemanager.app`.

All routes require a valid Auth0 Bearer token (`Authorization: Bearer <token>`). Tenants are strictly isolated by `auth.merchantId` extracted from token claims; every database query parameterizes `merchant_id = ${auth.merchantId}`.

***

## 1. Profile (`/applepay-lifecycle/v1/merchants`)

### `GET /applepay-lifecycle/v1/merchants/me`

* **Scope required:** `read:merchants`
* **Description:** Returns the authenticated merchant identity, operational configuration, tax identifier, and contact address.
* **Response `200 OK`:**

```json theme={null}
{
  "id": "00000000-0000-4000-8000-000000000001",
  "businessName": "Acme Widgets, Inc.",
  "environment": "sandbox",
  "email": "owner@acme.example",
  "ownerName": "Jane Doe",
  "contactName": "Jane Doe",
  "contactPhone": "+15551234567",
  "contactAddress": {
    "name": "Acme Widgets, Inc.",
    "line1": "123 Market St",
    "line2": "Suite 400",
    "city": "San Francisco",
    "region": "CA",
    "postalCode": "94105",
    "country": "US"
  },
  "address": {
    "name": "Acme Widgets, Inc.",
    "line1": "123 Market St",
    "line2": "Suite 400",
    "city": "San Francisco",
    "region": "CA",
    "postalCode": "94105",
    "country": "US"
  },
  "taxId": "12-3456789",
  "avatarUrl": null,
  "applePayActive": true,
  "notifications": 0,
  "stripeAccountId": "acct_123",
  "stripeChargesEnabled": true,
  "stripePayoutsEnabled": true,
  "stripeDetailsSubmitted": true,
  "stripeOnboardingStatus": "completed",
  "planId": "starter",
  "planName": "Starter",
  "subscriptionStatus": "active",
  "signupCompleted": true,
  "currentSignupStep": 4,
  "partnerInternalMerchantIdentifier": null,
  "createdAt": "2026-01-01T00:00:00Z",
  "updatedAt": "2026-01-01T00:00:00Z"
}
```

### `PATCH /applepay-lifecycle/v1/merchants/me`

* **Scope required:** `write:merchants`
* **Description:** Updates merchant profile attributes including tax ID and full address object. Validates input lengths (`businessName <= 255`, `contactName <= 255`, `contactPhone <= 50`, `taxId <= 50`, address lines `<= 255`, `postalCode <= 50`, `country <= 10`).
* **Request Body:**

```json theme={null}
{
  "businessName": "Acme Widgets, Inc.",
  "contactName": "Jane Doe",
  "contactPhone": "+15551234567",
  "taxId": "12-3456789",
  "address": {
    "name": "Acme Widgets, Inc.",
    "line1": "123 Market St",
    "line2": "Suite 400",
    "city": "San Francisco",
    "region": "CA",
    "postalCode": "94105",
    "country": "US"
  }
}
```

* **Response `200 OK`:** Full merchant object (same shape as `GET /merchants/me`).
* **Errors:** `400 Bad Request` on validation failures (`validation_error`), `403 Forbidden` if missing required user authorization.

***

## 2. Notification Settings (`/applepay-lifecycle/v1/settings`)

### `GET /applepay-lifecycle/v1/settings/notifications`

* **Scope required:** `read:merchants`
* **Description:** Retrieves notification preferences for the authenticated member. Returns default preferences when no row has been persisted yet.
* **Response `200 OK`:**

```json theme={null}
{
  "merchantId": "00000000-0000-4000-8000-000000000001",
  "auth0UserId": "auth0|owner_1",
  "emailEnabled": true,
  "notifyDisputes": true,
  "notifyRefunds": true,
  "notifyDomainStatus": true,
  "notifyRegistrationErrors": true,
  "notifySubscriptionBilling": true,
  "notifyWeeklyReport": false,
  "transactionAlerts": true,
  "failureAlerts": true,
  "weeklySummaries": false,
  "updatedAt": "2026-01-01T00:00:00Z"
}
```

### `PUT /applepay-lifecycle/v1/settings/notifications`

* **Scope required:** `write:merchants`
* **Description:** Upserts notification preferences for the authenticated member in `merchant_notification_settings`. Accepts dashboard convenience keys (`transactionAlerts`, `failureAlerts`, `weeklySummaries`) or granular database flags.
* **Request Body:**

```json theme={null}
{
  "transactionAlerts": true,
  "failureAlerts": true,
  "weeklySummaries": false
}
```

* **Response `200 OK`:** Updated notification settings object.

### `GET /applepay-lifecycle/v1/settings`

* **Scope required:** `read:merchants`
* **Description:** Unified endpoint returning both profile attributes and notification preferences in a single call.
* **Response `200 OK`:**

```json theme={null}
{
  "profile": {
    "businessName": "Acme Widgets, Inc.",
    "taxId": "12-3456789",
    "address": {
      "name": "Acme Widgets, Inc.",
      "line1": "123 Market St",
      "line2": "Suite 400",
      "city": "San Francisco",
      "region": "CA",
      "postalCode": "94105",
      "country": "US"
    }
  },
  "notifications": {
    "transactionAlerts": true,
    "failureAlerts": true,
    "weeklySummaries": false
  }
}
```

### `PATCH /applepay-lifecycle/v1/settings`

* **Scope required:** `write:merchants`
* **Description:** Unified endpoint accepting partial `{ profile?, notifications? }` payloads.
* **Response `200 OK`:** Unified settings object.

***

## 3. Team Management (`/applepay-lifecycle/v1/team`)

### `GET /applepay-lifecycle/v1/team`

* **Scope required:** `read:merchants`
* **Description:** Lists all active team members and unexpired pending invitations for the merchant tenant.
* **Response `200 OK`:**

```json theme={null}
{
  "members": [
    {
      "id": "auth0|owner_1",
      "auth0UserId": "auth0|owner_1",
      "email": "owner@acme.example",
      "role": "owner",
      "status": "active",
      "joinedAt": "2026-01-01T00:00:00Z",
      "createdAt": "2026-01-01T00:00:00Z",
      "updatedAt": "2026-01-01T00:00:00Z"
    }
  ],
  "invitations": [
    {
      "id": "00000000-0000-4000-8000-000000000010",
      "email": "colleague@acme.example",
      "role": "analyst",
      "status": "invited",
      "joinedAt": null,
      "expiresAt": "2026-01-08T00:00:00Z",
      "invitedBy": "auth0|owner_1",
      "createdAt": "2026-01-01T00:00:00Z"
    }
  ],
  "data": []
}
```

### `POST /applepay-lifecycle/v1/team/invitations`

* **Scope required:** `write:merchants`
* **Description:** Generates and stores a new team invitation with a SHA-256 token hash and 7-day expiration. Rejects duplicate members or active open invitations with `409 Conflict`.
* **Authorization:** Requires caller to be an `owner` (via Auth0 claims or `merchant_members` role) or `admin`. Admins may only invite analysts (`analyst`). Owners may invite either `admin` or `analyst`. Callers with insufficient role receive `403 Forbidden` (`{ "error": "insufficient_role" }`).
* **Delivery:** Generates and returns a one-time `inviteUrl` directly in the `201 Created` response, constructed as `${origin}/invite/accept?token=${token}`. The dashboard origin is the optional `DASHBOARD_ORIGIN` variable when set; otherwise derived from `ENVIRONMENT` per URL law (production `https://lifecyclemanager.app`). The raw token is never logged and only its SHA-256 hash is persisted.
* **Request Body:**

```json theme={null}
{
  "email": "colleague@acme.example",
  "role": "admin"
}
```

*(Accepts `"admin"` or `"analyst"`. `"viewer"` is automatically mapped to `"analyst"`).*

* **Response `201 Created`:**

```json theme={null}
{
  "id": "00000000-0000-4000-8000-000000000010",
  "email": "colleague@acme.example",
  "role": "admin",
  "status": "invited",
  "joinedAt": null,
  "expiresAt": "2026-01-08T00:00:00Z",
  "invitedBy": "auth0|owner_1",
  "createdAt": "2026-01-01T00:00:00Z",
  "inviteUrl": "https://lifecyclemanager.app/invite/accept?token=..."
}
```

### `DELETE /applepay-lifecycle/v1/team/invitations/:id`

* **Scope required:** `write:merchants`
* **Description:** Deletes or revokes a pending invitation. The `:id` parameter must be a valid UUID; malformed IDs immediately return `404 Not Found` (`{ "error": "invitation_not_found" }`). Restricted to the merchant owner (`403 owner_only` otherwise).
* **Response `200 OK`:**

```json theme={null}
{
  "ok": true,
  "deleted": true
}
```

### `PATCH /applepay-lifecycle/v1/team/members/:auth0UserId`

* **Scope required:** `write:merchants`
* **Description:** Updates the role of a team member. Restricted to the merchant owner. Never allows demoting or reassigning the owner (`400 cannot_demote_owner`).
* **Request Body:**

```json theme={null}
{
  "role": "analyst"
}
```

* **Response `200 OK`:** Updated member record.

### `DELETE /applepay-lifecycle/v1/team/members/:auth0UserId`

* **Scope required:** `write:merchants`
* **Description:** Removes a member from the merchant team. Restricted to the merchant owner. The owner can never remove themselves or be removed (`400 cannot_remove_owner`).
* **Response `200 OK`:**

```json theme={null}
{
  "ok": true,
  "deleted": true
}
```

***

## 4. Support (`/applepay-lifecycle/v1/support`)

### `GET /applepay-lifecycle/v1/support/tickets`

* **Scope required:** `read:merchants`
* **Description:** Lists all support tickets for the merchant tenant ordered by last update time, including initial message body.
* **Response `200 OK`:**

```json theme={null}
{
  "tickets": [
    {
      "id": "00000000-0000-4000-8000-000000000020",
      "subject": "Domain association file not found",
      "body": "The verification path returns 404 for checkout.example.com",
      "status": "open",
      "dbStatus": "open",
      "priority": "high",
      "severity": "high",
      "category": "domains",
      "createdAt": "2026-01-01T00:00:00Z",
      "updatedAt": "2026-01-01T00:00:00Z",
      "closedAt": null
    }
  ],
  "data": []
}
```

### `POST /applepay-lifecycle/v1/support/tickets`

* **Scope required:** `write:merchants`
* **Description:** Opens a new support ticket and creates the initial message in `support_ticket_messages` within a single database transaction. Maps dashboard `severity` (`medium` -> `normal`) to `priority`. Validates subject (2-255 characters) and body (1-5000 characters).
* **Request Body:**

```json theme={null}
{
  "subject": "Domain association file not found",
  "body": "The verification path returns 404 for checkout.example.com",
  "severity": "high"
}
```

* **Response `201 Created`:** Created ticket object with initial body.

### `GET /applepay-lifecycle/v1/support/tickets/:id`

* **Scope required:** `read:merchants`
* **Description:** Retrieves a ticket by ID along with its full message history. The `:id` parameter must be a valid UUID; malformed IDs immediately return `404 Not Found` (`{ "error": "ticket_not_found" }`). Returns `404` if the ticket does not exist or belongs to another merchant.
* **Response `200 OK`:**

```json theme={null}
{
  "id": "00000000-0000-4000-8000-000000000020",
  "subject": "Domain association file not found",
  "body": "The verification path returns 404 for checkout.example.com",
  "status": "open",
  "dbStatus": "open",
  "priority": "high",
  "severity": "high",
  "category": "domains",
  "createdAt": "2026-01-01T00:00:00Z",
  "updatedAt": "2026-01-01T00:00:00Z",
  "closedAt": null,
  "messages": [
    {
      "id": "00000000-0000-4000-8000-000000000030",
      "ticketId": "00000000-0000-4000-8000-000000000020",
      "authorType": "merchant",
      "authorId": "auth0|owner_1",
      "body": "The verification path returns 404 for checkout.example.com",
      "createdAt": "2026-01-01T00:00:00Z"
    }
  ]
}
```

### `POST /applepay-lifecycle/v1/support/tickets/:id/messages`

* **Scope required:** `write:merchants`
* **Description:** Adds a new reply message to an existing ticket and bumps the ticket's `updated_at` timestamp atomically in a single database transaction (`sql.begin`). The `:id` parameter must be a valid UUID; malformed IDs immediately return `404 Not Found` (`{ "error": "ticket_not_found" }`).
* **Request Body:**

```json theme={null}
{
  "body": "DNS records have been updated and are now resolving."
}
```

* **Response `201 Created`:**

```json theme={null}
{
  "id": "00000000-0000-4000-8000-000000000031",
  "ticketId": "00000000-0000-4000-8000-000000000020",
  "authorType": "merchant",
  "authorId": "auth0|owner_1",
  "body": "DNS records have been updated and are now resolving.",
  "createdAt": "2026-01-01T01:00:00Z"
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.