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": []
    }
  ]
}
Interactive API explorer (requires JavaScript)