Create an instance

Send POST /api/purple-suite/instances with Content-Type: application/json. Creating an instance requires no authentication.

{
  "name": "Linear demo",
  "paintSelections": {
    "pm": "linear"
  }
}

One instance is shared across PurpleSuite apps. Select paints in a single paintSelections map from app to paint. Discover supported selections at the systems catalog.

The response includes instanceId, pat, expiresAt, and name; selecting paints also returns paintSelections and contracts. Store the returned PAT securely.

Authenticate and add sample data

Instance-management requests use Authorization: Bearer <PAT> and X-Instance-ID: <instanceId>. Painted APIs use their own published authentication contract; inspect the returned contracts and follow its specification.

Creation does not seed data. For native apps, the instance seed endpoint adds sample data. Painted APIs have separate data and setup requirements: read their contract before seeding.

For Linear, send GraphQL requests to /api/purple-suite/{instanceId}/contracts/pm/linear/graphql with Authorization: <PAT> (no Bearer prefix). Query teams, users, and states, then create sample issues with issueCreate. The general seed endpoint does not populate Linear issues and resets existing Linear data.

Keep the instance for the demo and report its returned expiration. Open PurpleSuite and use Connect instance with the instance ID and PAT to view it.

Purple Suite Instance Management API OpenAPI

Manage a Purple Suite instance: lifecycle, MCP settings, data (seed/export/import/reset), and **outbound webhooks**. **Auth.** Every operation except `POST /instances` (create) is authenticated with the instance's Personal Access Token: send `Authorization: Bearer <PAT>` and the `X-Instance-ID` header. The PAT is returned once when the instance is created and is shown in the app under Settings → Credentials. **Shared instances (view-only).** An instance shared by link from the app can also be read with a **viewer token** in place of the PAT (same headers). Viewer requests are read-only: any non-GET method, and the settings/export reads on this API, return `403` with `code: viewer_read_only` or `owner_only`. Viewer tokens are minted by the app for internal users and stop working the moment sharing is turned off. **Webhooks.** A webhook fires an HTTP POST to your `url` when a subscribed event occurs (e.g. `hris.time_off_request.created`). Subscriptions support exact event types and wildcards: `*` (everything) and `<app>.*` (a whole app). **Signing.** When a webhook has `signingEnabled`, each delivery is signed with the instance signing secret: header `X-PurpleSuite-Signature: sha256=<hex>` over `"<X-PurpleSuite-Timestamp>.<rawBody>"` (HMAC-SHA256). Generate/rotate the secret via `POST /instances/{instanceId}/webhook-signing/rotate`. **Custom headers.** Each webhook may carry arbitrary outbound headers (e.g. an `Authorization` for your receiver). Header values flagged `encrypted` are stored encrypted at rest and never returned (masked on read).

This reference is readable without JavaScript. Download the OpenAPI document.

Runtime

/api/purple-suite

Manage a Purple Suite instance: lifecycle, MCP settings, data (seed/export/import/reset), and **outbound webhooks**. **Auth.** Every operation except `POST /instances` (create) is authenticated with the instance's Personal Access Token: send `Authorization: Bearer <PAT>` and the `X-Instance-ID` header. The PAT is returned once when the instance is created and is shown in the app under Settings → Credentials. **Shared instances (view-only).** An instance shared by link from the app can also be read with a **viewer token** in place of the PAT (same headers). Viewer requests are read-only: any non-GET method, and the settings/export reads on this API, return `403` with `code: viewer_read_only` or `owner_only`. Viewer tokens are minted by the app for internal users and stop working the moment sharing is turned off. **Webhooks.** A webhook fires an HTTP POST to your `url` when a subscribed event occurs (e.g. `hris.time_off_request.created`). Subscriptions support exact event types and wildcards: `*` (everything) and `<app>.*` (a whole app). **Signing.** When a webhook has `signingEnabled`, each delivery is signed with the instance signing secret: header `X-PurpleSuite-Signature: sha256=<hex>` over `"<X-PurpleSuite-Timestamp>.<rawBody>"` (HMAC-SHA256). Generate/rotate the secret via `POST /instances/{instanceId}/webhook-signing/rotate`. **Custom headers.** Each webhook may carry arbitrary outbound headers (e.g. an `Authorization` for your receiver). Header values flagged `encrypted` are stored encrypted at rest and never returned (masked on read).

Servers

[
  {
    "url": "https://marketplace.moveworks.com/api/purple-suite"
  }
]

Authentication schemes

{
  "patAuth": {
    "type": "http",
    "scheme": "bearer"
  }
}

Each operation lists its security requirements. An empty security array permits an unauthenticated request. Use the scheme and headers declared for the selected contract.

Operations

GET /instances/systems

Discover systems and available paints

Public catalog of source-system ids, collections and selectable paints. native identifies PurpleSuite API transport; visualOnly identifies vendor appearance with native APIs. Other non-native paints use admitted exact contracts. Internal systems require an authenticated internal user to appear.

Authentication

[]

Request, responses and operation details

{
  "summary": "Discover systems and available paints",
  "description": "Public catalog of source-system ids, collections and selectable paints. native identifies PurpleSuite API transport; visualOnly identifies vendor appearance with native APIs. Other non-native paints use admitted exact contracts. Internal systems require an authenticated internal user to appear.",
  "security": [],
  "responses": {
    "200": {
      "description": "Available systems and paint modes",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "systems": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "paints": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "paint": {
                            "type": "string"
                          },
                          "native": {
                            "type": "boolean"
                          },
                          "visualOnly": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "collections": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "route": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

POST /instances

Create an instance

Public — no PAT required. Creates one shared instance for all apps and returns its PAT (shown once). Provide name and optionally paintSelections, a single map of public app ids to paints from anonymous GET /instances/systems. Internal-only and hidden apps are rejected. Top-level paint is rejected; legacy system metadata is optional and unnecessary for new clients. Invalid selections are rejected before any instance is created. Visual-only paints retain native APIs; consult returned contracts for runtime and spec URLs. Creation does not seed data. POST /instances/{instanceId}/seed populates native sample data and may reset painted storage. For a painted demo, follow the selected contract and create fixtures with its supported operations; Linear issues require GraphQL issueCreate.

Authentication

[]

Request, responses and operation details

{
  "summary": "Create an instance",
  "description": "Public — no PAT required. Creates one shared instance for all apps and returns its PAT (shown once). Provide name and optionally paintSelections, a single map of public app ids to paints from anonymous GET /instances/systems. Internal-only and hidden apps are rejected. Top-level paint is rejected; legacy system metadata is optional and unnecessary for new clients. Invalid selections are rejected before any instance is created. Visual-only paints retain native APIs; consult returned contracts for runtime and spec URLs. Creation does not seed data. POST /instances/{instanceId}/seed populates native sample data and may reset painted storage. For a painted demo, follow the selected contract and create fixtures with its supported operations; Linear issues require GraphQL issueCreate.",
  "security": [],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/CreateInstanceRequest"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/CreateInstanceResponse"
          }
        }
      }
    },
    "400": {
      "description": "Invalid request, unsupported top-level paint field, unsupported app paint, or unavailable exact contract"
    },
    "403": {
      "description": "Supplied Marketplace user PAT is invalid or lacks the instance-create scope"
    },
    "409": {
      "description": "Instance id already exists"
    }
  }
}

GET /instances/{instanceId}

Get instance metadata

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get instance metadata",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Instance"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /instances/{instanceId}

Delete instance

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete instance",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SuccessResponse"
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/extend

Extend the instance TTL

Push out the expiry by a named duration (`ttl`) or to an explicit instant (`expiresAt`). Both are capped at 7 days from now.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Extend the instance TTL",
  "description": "Push out the expiry by a named duration (`ttl`) or to an explicit instant (`expiresAt`). Both are capped at 7 days from now.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "requestBody": {
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "ttl": {
              "type": "string",
              "enum": [
                "1 hour",
                "1 day",
                "7 days"
              ],
              "example": "7 days"
            },
            "expiresAt": {
              "type": "string",
              "format": "date-time",
              "description": "Explicit target expiry (ISO 8601). Must be in the future and within 7 days. Takes precedence over ttl."
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Extended",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/rotate-pat

Rotate the instance PAT

Issues a new PAT and invalidates the current one — anything using the old key stops working. Requires the CURRENT PAT.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Rotate the instance PAT",
  "description": "Issues a new PAT and invalidates the current one — anything using the old key stops working. Requires the CURRENT PAT.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "New PAT (shown once)",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "pat": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}

GET /instances/{instanceId}/lifecycle

Read the instance's expiry behaviour

Internal only. Requires the instance PAT **and** an `X-PS-Auth-Token`.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Read the instance's expiry behaviour",
  "description": "Internal only. Requires the instance PAT **and** an `X-PS-Auth-Token`.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Lifecycle settings",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/InstanceLifecycle"
          }
        }
      }
    },
    "401": {
      "description": "Missing or invalid X-PS-Auth-Token"
    }
  }
}

PUT /instances/{instanceId}/lifecycle

Reset data at expiry instead of deleting the instance

Internal only. Requires the instance PAT **and** an `X-PS-Auth-Token` (minted by `POST /rbac/purple-suite-token` for internal users). With `dataResetOnly: true` the instance is **not** deleted when it expires: its data is wiped and regenerated and the expiry moves out another 7 days. The instance ID and API key never change, so HTTP actions and connectors pointing at it keep working across demo cycles. All data is still destroyed every 7 days. Renewal is driven by use — after a period of no API activity (`idleCutoffDays`) the setting turns itself off and the instance expires normally.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Reset data at expiry instead of deleting the instance",
  "description": "Internal only. Requires the instance PAT **and** an `X-PS-Auth-Token` (minted by `POST /rbac/purple-suite-token` for internal users).\n\nWith `dataResetOnly: true` the instance is **not** deleted when it expires: its data is wiped and regenerated and the expiry moves out another 7 days. The instance ID and API key never change, so HTTP actions and connectors pointing at it keep working across demo cycles. All data is still destroyed every 7 days.\n\nRenewal is driven by use — after a period of no API activity (`idleCutoffDays`) the setting turns itself off and the instance expires normally.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "required": [
            "dataResetOnly"
          ],
          "properties": {
            "dataResetOnly": {
              "type": "boolean"
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/InstanceLifecycle"
          }
        }
      }
    },
    "400": {
      "description": "dataResetOnly must be a boolean"
    },
    "401": {
      "description": "Missing or invalid X-PS-Auth-Token"
    }
  }
}

GET /instances/{instanceId}/paint

Get per-system paint selections

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get per-system paint selections",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Explicit selections only; a system that is absent uses the native PurpleSuite contract.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "selections": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}

GET /instances/{instanceId}/paint/{system}

Get one system's selected paint

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get one system's selected paint",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    },
    {
      "in": "path",
      "name": "system",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "PurpleSuite source-system id"
    }
  ],
  "responses": {
    "200": {
      "description": "Selection and resolved contract",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PaintSelection"
          }
        }
      }
    }
  }
}

PUT /instances/{instanceId}/paint/{system}

Select one system's visual and API paint

Persists the selection server-side. `purplesuite` restores the native appearance and contract. Explicit visual-only paints retain native APIs and return visualOnly: true. Exact-contract paints are accepted only once their adapter is admitted.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Select one system's visual and API paint",
  "description": "Persists the selection server-side. `purplesuite` restores the native appearance and contract. Explicit visual-only paints retain native APIs and return visualOnly: true. Exact-contract paints are accepted only once their adapter is admitted.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    },
    {
      "in": "path",
      "name": "system",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "PurpleSuite source-system id"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "required": [
            "paint"
          ],
          "properties": {
            "paint": {
              "type": "string",
              "example": "purplesuite"
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated selection and resolved contract",
      "content": {
        "application/json": {
          "schema": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PaintSelection"
              },
              {
                "type": "object",
                "required": [
                  "selections"
                ],
                "properties": {
                  "selections": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Every explicit per-system selection after this write."
                  }
                }
              }
            ]
          }
        }
      }
    },
    "400": {
      "description": "`unsupported_paint` — not renderable for this system; `contract_unavailable` — requires an exact adapter that is not admitted.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string"
              },
              "code": {
                "type": "string",
                "enum": [
                  "unsupported_paint",
                  "contract_unavailable"
                ]
              },
              "supportedPaints": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}

GET /instances/{instanceId}/contracts/{system}

Discover the active contract's runtime and documentation URLs

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Discover the active contract's runtime and documentation URLs",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    },
    {
      "in": "path",
      "name": "system",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "PurpleSuite source-system id"
    }
  ],
  "responses": {
    "200": {
      "description": "Resolved active contract",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PaintSelection"
          }
        }
      }
    }
  }
}

GET /instances/{instanceId}/mcp/settings

Get MCP settings

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get MCP settings",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Settings",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/McpSettings"
          }
        }
      }
    }
  }
}

PUT /instances/{instanceId}/mcp/settings

Update MCP settings

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update MCP settings",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "requestBody": {
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/McpSettings"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/McpSettings"
          }
        }
      }
    }
  }
}

GET /instances/{instanceId}/webhook-events

List subscribable webhook event types

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List subscribable webhook event types",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Event catalog",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/WebhookEvents"
          }
        }
      }
    }
  }
}

GET /instances/{instanceId}/webhooks

List webhooks

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List webhooks",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Webhooks (secrets masked)",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/WebhookList"
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/webhooks

Create a webhook

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create a webhook",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/WebhookInput"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Webhook"
          }
        }
      }
    },
    "400": {
      "description": "Invalid url / events / headers"
    }
  }
}

GET /instances/{instanceId}/webhooks/{webhookId}

Get a webhook

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get a webhook",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    },
    {
      "in": "path",
      "name": "webhookId",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "Webhook id"
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Webhook"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /instances/{instanceId}/webhooks/{webhookId}

Update a webhook

Partial update. If `headers` is provided it replaces the set; encrypted values must be re-entered.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update a webhook",
  "description": "Partial update. If `headers` is provided it replaces the set; encrypted values must be re-entered.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    },
    {
      "in": "path",
      "name": "webhookId",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "Webhook id"
    }
  ],
  "requestBody": {
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/WebhookInput"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Webhook"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /instances/{instanceId}/webhooks/{webhookId}

Delete a webhook

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete a webhook",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    },
    {
      "in": "path",
      "name": "webhookId",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "Webhook id"
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SuccessResponse"
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/webhooks/{webhookId}/test

Send a test event to the webhook

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Send a test event to the webhook",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    },
    {
      "in": "path",
      "name": "webhookId",
      "required": true,
      "schema": {
        "type": "string"
      },
      "description": "Webhook id"
    }
  ],
  "responses": {
    "200": {
      "description": "Delivery outcome",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/TestResult"
          }
        }
      }
    }
  }
}

GET /instances/{instanceId}/webhook-signing

Get signing-secret status

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get signing-secret status",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Status (never returns the secret)",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SigningStatus"
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/webhook-signing/rotate

Generate / rotate the instance signing secret

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Generate / rotate the instance signing secret",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "New secret (shown once)",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/RotateSigningResponse"
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/seed

Replace instance data with fresh sample data

Replaces native sample data and may reset painted storage. This does not populate every painted API: for example, it clears Linear issues. Seed native apps before creating painted fixtures, then use the selected paint's supported create operations. Optionally pass `localeConfig` to re-localize the instance in the same step — it is persisted on the instance before seeding. Omitted, the instance's stored localeConfig (or the all-countries default) applies.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Replace instance data with fresh sample data",
  "description": "Replaces native sample data and may reset painted storage. This does not populate every painted API: for example, it clears Linear issues. Seed native apps before creating painted fixtures, then use the selected paint's supported create operations. Optionally pass `localeConfig` to re-localize the instance in the same step — it is persisted on the instance before seeding. Omitted, the instance's stored localeConfig (or the all-countries default) applies.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "requestBody": {
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "localeConfig": {
              "$ref": "#/components/schemas/LocaleConfig"
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Seeded"
    },
    "400": {
      "description": "Invalid localeConfig"
    }
  }
}

GET /instances/{instanceId}/export

Export all instance data as JSON

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Export all instance data as JSON",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Export",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "collections": {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/import

Additively import data

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Additively import data",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "collections": {
              "type": "object",
              "additionalProperties": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Imported",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "imported": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    }
  }
}

POST /instances/{instanceId}/reset

Clear all instance data

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Clear all instance data",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "$ref": "#/components/parameters/InstanceIdPath"
    }
  ],
  "responses": {
    "200": {
      "description": "Reset",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "deleted": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    }
  }
}

Schemas and reusable components

securitySchemes

patAuth

{
  "type": "http",
  "scheme": "bearer"
}

parameters

InstanceId

{
  "in": "header",
  "name": "X-Instance-ID",
  "required": true,
  "schema": {
    "type": "string"
  },
  "description": "The instance id (also sent in the path). Identifies the instance the PAT belongs to."
}

InstanceIdPath

{
  "in": "path",
  "name": "instanceId",
  "required": true,
  "schema": {
    "type": "string"
  },
  "description": "The instance id."
}

schemas

PaintSelection

{
  "type": "object",
  "description": "The paint selected for one system, plus the runtime and docs URLs it resolves to.",
  "required": [
    "system",
    "paint",
    "native",
    "contract"
  ],
  "properties": {
    "system": {
      "type": "string"
    },
    "paint": {
      "type": "string",
      "description": "`purplesuite` for the native appearance, otherwise a supported visual-only or admitted exact-contract paint.",
      "example": "purplesuite"
    },
    "native": {
      "type": "boolean",
      "description": "True while this system serves its native contract, including visual-only paints."
    },
    "visualOnly": {
      "type": "boolean",
      "description": "Present and true when the paint changes appearance while runtime and spec URLs remain PurpleSuite-native."
    },
    "contract": {
      "type": "object",
      "nullable": true,
      "additionalProperties": true,
      "description": "Resolved runtime/docs/spec discovery. A vendor paint is reported only after its exact adapter is admitted."
    }
  }
}

LocaleConfig

{
  "type": "object",
  "required": [
    "countries"
  ],
  "description": "Per-instance seeding locale. `countries` selects which countries sample data (people, bank customers, per-country catalogs) is generated for; `primaryCountry` drives the seeded base currency (IN→INR, AU→AUD, US→USD, GB→GBP, KR→KRW, DE→EUR). Omitted entirely = the legacy default trio (IN, AU, US) with a USD base.",
  "properties": {
    "countries": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "IN",
          "AU",
          "US",
          "GB",
          "KR",
          "DE"
        ]
      },
      "minItems": 1,
      "description": "Countries to seed data for (deduped; stored in canonical IN, AU, US, GB, KR, DE order).",
      "example": [
        "IN",
        "US"
      ]
    },
    "primaryCountry": {
      "type": "string",
      "enum": [
        "IN",
        "AU",
        "US",
        "GB",
        "KR",
        "DE"
      ],
      "description": "Base-currency country. Must be one of `countries`. Defaults to US when selected, else the first selected country.",
      "example": "US"
    }
  }
}

Instance

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "example": "6268ca8f-05b7-4a58-9bb4-e93ac8d7e0f7"
    },
    "name": {
      "type": "string",
      "example": "Orbit Dynamics"
    },
    "system": {
      "type": "string",
      "deprecated": true,
      "description": "Legacy app metadata, present only for instances created by older clients.",
      "example": "hris"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    },
    "expiresAt": {
      "type": "string",
      "format": "date-time"
    },
    "enabledApps": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "nullable": true,
      "description": "MCP app filter; null = all apps"
    },
    "defaultMcpUser": {
      "type": "string",
      "nullable": true,
      "description": "Email the MCP server impersonates by default"
    },
    "localeConfig": {
      "$ref": "#/components/schemas/LocaleConfig"
    },
    "sharing": {
      "type": "object",
      "description": "Link sharing state. While mode is `link`, any internal (Moveworks or ServiceNow) user who opens the share link in the app gets read-only access through a viewer token; they never receive the PAT. Managed from the app (Settings → Sharing), not through this API.",
      "properties": {
        "mode": {
          "type": "string",
          "enum": [
            "private",
            "link"
          ]
        },
        "sharedAt": {
          "type": "string",
          "format": "date-time",
          "nullable": true
        },
        "sharedByUid": {
          "type": "string",
          "nullable": true
        }
      }
    },
    "owner": {
      "type": "object",
      "nullable": true,
      "description": "Who shares the instance (attribution shown to viewers). Recorded the first time the instance is shared; absent until then.",
      "properties": {
        "uid": {
          "type": "string"
        },
        "email": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "photoURL": {
          "type": "string",
          "nullable": true
        },
        "since": {
          "type": "string",
          "format": "date-time"
        }
      }
    }
  }
}

CreateInstanceRequest

{
  "type": "object",
  "required": [
    "name"
  ],
  "example": {
    "name": "Acme demo",
    "paintSelections": {
      "pm": "jira",
      "itsm": "servicenow"
    }
  },
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "example": "Orbit Dynamics"
    },
    "system": {
      "type": "string",
      "deprecated": true,
      "description": "Compatibility-only metadata for older clients. Optional and does not restrict the instance to an app. New clients should omit it: every instance is shared across all apps.",
      "example": "pm"
    },
    "instanceId": {
      "type": "string",
      "nullable": true,
      "pattern": "^[A-Za-z0-9._-]+$",
      "minLength": 1,
      "maxLength": 1500,
      "description": "Optional custom id (else a UUID is generated). Use 1-1500 ASCII letters, numbers, dots, underscores or hyphens. Names ., .., systems, mcp-apps, openapi.json and __...__ are reserved; route names are case-insensitive."
    },
    "localeConfig": {
      "$ref": "#/components/schemas/LocaleConfig"
    },
    "paintSelections": {
      "type": "object",
      "additionalProperties": {
        "type": "string",
        "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
      },
      "description": "Optional map of public app ids to supported paints for this shared instance. Discover allowed apps and paints from anonymous GET /instances/systems. Internal-only and hidden apps are not accepted. Missing apps use the native appearance; an empty or omitted map keeps all defaults. All selections are validated and saved atomically. Visual-only paints retain native APIs; exact-contract paints require an admitted adapter.",
      "example": {
        "pm": "jira",
        "itsm": "servicenow"
      }
    }
  }
}

CreateInstanceResponse

{
  "type": "object",
  "properties": {
    "instanceId": {
      "type": "string"
    },
    "pat": {
      "type": "string",
      "description": "Personal Access Token — shown once. Use as Bearer token + X-Instance-ID for every other call.",
      "example": "ps_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "name": {
      "type": "string"
    },
    "system": {
      "type": "string",
      "deprecated": true,
      "description": "Compatibility-only metadata, returned only when supplied on creation."
    },
    "expiresAt": {
      "type": "string",
      "format": "date-time"
    },
    "localeConfig": {
      "$ref": "#/components/schemas/LocaleConfig"
    },
    "paintSelections": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "description": "Persisted selections, returned when paintSelections was supplied. Native purplesuite entries are omitted."
    },
    "contracts": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/components/schemas/PaintSelection"
      },
      "description": "Runtime and spec discovery keyed by every explicitly requested system, including native selections. Returned when paintSelections was supplied."
    }
  }
}

WebhookHeader

{
  "type": "object",
  "required": [
    "name"
  ],
  "properties": {
    "name": {
      "type": "string",
      "example": "Authorization"
    },
    "value": {
      "type": "string",
      "description": "Header value. On write, send the plaintext value. On read, encrypted values are masked and omitted.",
      "example": "Bearer my-receiver-token"
    },
    "encrypted": {
      "type": "boolean",
      "description": "Store this value encrypted at rest (never returned afterward).",
      "example": true
    }
  }
}

WebhookInput

{
  "type": "object",
  "required": [
    "url",
    "events"
  ],
  "properties": {
    "url": {
      "type": "string",
      "format": "uri",
      "description": "HTTPS endpoint that receives the POST.",
      "example": "https://hooks.example.com/purplesuite"
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Event types to subscribe to. Exact (`hris.time_off_request.created`) or wildcard (`hris.*`, `*`). See GET /instances/{instanceId}/webhook-events.",
      "example": [
        "hris.time_off_request.created"
      ]
    },
    "active": {
      "type": "boolean",
      "default": true,
      "description": "Master on/off for this webhook."
    },
    "signingEnabled": {
      "type": "boolean",
      "default": false,
      "description": "Sign deliveries with the instance signing secret."
    },
    "description": {
      "type": "string",
      "example": "PTO notifications"
    },
    "headers": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/WebhookHeader"
      },
      "description": "Custom outbound headers attached to every delivery."
    }
  }
}

Webhook

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "url": {
      "type": "string",
      "format": "uri"
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "active": {
      "type": "boolean"
    },
    "signingEnabled": {
      "type": "boolean"
    },
    "description": {
      "type": "string"
    },
    "headers": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/WebhookHeader"
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time"
    }
  }
}

WebhookList

{
  "type": "object",
  "properties": {
    "webhooks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Webhook"
      }
    }
  }
}

WebhookEvents

{
  "type": "object",
  "properties": {
    "events": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "example": [
        "*",
        "hris.*",
        "hris.time_off_request.created",
        "crm.opportunities.updated"
      ]
    }
  }
}

TestResult

{
  "type": "object",
  "properties": {
    "ok": {
      "type": "boolean",
      "description": "Whether the receiver returned a 2xx."
    },
    "status": {
      "type": "integer",
      "nullable": true,
      "description": "HTTP status returned by the receiver."
    },
    "error": {
      "type": "string",
      "nullable": true
    }
  }
}

SigningStatus

{
  "type": "object",
  "properties": {
    "configured": {
      "type": "boolean"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
    }
  }
}

RotateSigningResponse

{
  "type": "object",
  "properties": {
    "secret": {
      "type": "string",
      "description": "The signing secret in cleartext — shown ONCE. Store it to verify signatures.",
      "example": "whsec_xxxxxxxx"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    }
  }
}

InstanceLifecycle

{
  "type": "object",
  "properties": {
    "dataResetOnly": {
      "type": "boolean",
      "description": "When true the instance is wiped and reseeded at expiry rather than deleted, so its ID and API key persist."
    },
    "dataResetEnabledBy": {
      "type": "string",
      "nullable": true
    },
    "dataResetEnabledAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
    },
    "lastAutoResetAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
    },
    "autoResetCount": {
      "type": "integer",
      "description": "How many automatic resets have run."
    },
    "lastAccessedAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true,
      "description": "Coarse (refreshed at most every few hours). Auto-renewal stops once this is older than idleCutoffDays."
    },
    "idleCutoffDays": {
      "type": "integer",
      "description": "Days of no API activity after which dataResetOnly turns itself off."
    },
    "expiresAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
    }
  }
}

McpSettings

{
  "type": "object",
  "properties": {
    "enabledApps": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "nullable": true,
      "description": "null = expose every app; [] = none; a list = only those apps."
    },
    "defaultMcpUser": {
      "type": "string",
      "nullable": true
    }
  }
}

SuccessResponse

{
  "type": "object",
  "properties": {
    "success": {
      "type": "boolean"
    },
    "id": {
      "type": "string"
    }
  }
}

Document metadata and extensions

{
  "openapi": "3.0.0",
  "info": {
    "title": "Purple Suite Instance Management API",
    "version": "1.0.0",
    "description": "Manage a Purple Suite instance: lifecycle, MCP settings, data (seed/export/import/reset), and **outbound webhooks**.\n\n**Auth.** Every operation except `POST /instances` (create) is authenticated with the instance's Personal Access Token: send `Authorization: Bearer <PAT>` and the `X-Instance-ID` header. The PAT is returned once when the instance is created and is shown in the app under Settings → Credentials.\n\n**Shared instances (view-only).** An instance shared by link from the app can also be read with a **viewer token** in place of the PAT (same headers). Viewer requests are read-only: any non-GET method, and the settings/export reads on this API, return `403` with `code: viewer_read_only` or `owner_only`. Viewer tokens are minted by the app for internal users and stop working the moment sharing is turned off.\n\n**Webhooks.** A webhook fires an HTTP POST to your `url` when a subscribed event occurs (e.g. `hris.time_off_request.created`). Subscriptions support exact event types and wildcards: `*` (everything) and `<app>.*` (a whole app).\n\n**Signing.** When a webhook has `signingEnabled`, each delivery is signed with the instance signing secret: header `X-PurpleSuite-Signature: sha256=<hex>` over `\"<X-PurpleSuite-Timestamp>.<rawBody>\"` (HMAC-SHA256). Generate/rotate the secret via `POST /instances/{instanceId}/webhook-signing/rotate`.\n\n**Custom headers.** Each webhook may carry arbitrary outbound headers (e.g. an `Authorization` for your receiver). Header values flagged `encrypted` are stored encrypted at rest and never returned (masked on read)."
  },
  "security": [
    {
      "patAuth": []
    }
  ]
}