Purple Suite SCM API OpenAPI

--- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

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

Runtime

/api/purple-suite/scm

--- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

Servers

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

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 /suppliers

List suppliers

Purple SCM suppliers are the vendors the company sources physical goods and services from, with tax ID, annual contract value, category, country, quality rating, onboarding date and status (active | inactive | pending_approval | blacklisted). Supply-chain vendor master, distinct from ERP finance vendors and from CRM accounts (customers). Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List suppliers",
  "description": "Purple SCM suppliers are the vendors the company sources physical goods and services from, with tax ID, annual contract value, category, country, quality rating, onboarding date and status (active | inactive | pending_approval | blacklisted). Supply-chain vendor master, distinct from ERP finance vendors and from CRM accounts (customers). Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Supplier"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  }
}

POST /suppliers

Onboard supplier

Add a supplier to the Purple SCM vendor master with its tax ID, annual contract value, category, country and rating. New suppliers usually start pending_approval; search the existing list first to avoid duplicates.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Onboard supplier",
  "description": "Add a supplier to the Purple SCM vendor master with its tax ID, annual contract value, category, country and rating. New suppliers usually start pending_approval; search the existing list first to avoid duplicates.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Supplier"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Supplier"
          }
        }
      }
    }
  }
}

GET /suppliers/{id}

Get supplier

Fetch one supplier by id (e.g. SUP-0001) with its tax ID, annual contract value, status, category, country and rating — the record behind sourcing, contract and quality questions.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get supplier",
  "description": "Fetch one supplier by id (e.g. SUP-0001) with its tax ID, annual contract value, status, category, country and rating — the record behind sourcing, contract and quality questions.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Supplier"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /suppliers/{id}

Update supplier

Partially update a supplier — change tax ID, annual contract value, category, country or rating, or move its status. Setting status to 'blacklisted' stops future sourcing from them and should be deliberate.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update supplier",
  "description": "Partially update a supplier — change tax ID, annual contract value, category, country or rating, or move its status. Setting status to 'blacklisted' stops future sourcing from them and should be deliberate.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Supplier"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Supplier"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /suppliers/{id}

Delete supplier

Permanently delete a supplier from the vendor master. Destructive and irreversible, and it orphans their contracts, purchase orders, shipments and quality lots — set status to 'inactive' or 'blacklisted' instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete supplier",
  "description": "Permanently delete a supplier from the vendor master. Destructive and irreversible, and it orphans their contracts, purchase orders, shipments and quality lots — set status to 'inactive' or 'blacklisted' instead.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /contracts

List supplier contracts

Purple SCM contracts are the commercial agreements with a supplier (master supply agreements and similar) with value, start/end dates, auto-renew flag and status draft | active | signed | expired | cancelled. Supplier agreements, not customer contracts or purchase orders. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List supplier contracts",
  "description": "Purple SCM contracts are the commercial agreements with a supplier (master supply agreements and similar) with value, start/end dates, auto-renew flag and status draft | active | signed | expired | cancelled. Supplier agreements, not customer contracts or purchase orders. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Contract"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  }
}

POST /contracts

Create supplier contract

Draft a new commercial agreement with a supplier, with its term dates, value and auto-renew flag. Also the correct move when an expired contract needs replacing, since expired contracts cannot be reactivated.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create supplier contract",
  "description": "Draft a new commercial agreement with a supplier, with its term dates, value and auto-renew flag. Also the correct move when an expired contract needs replacing, since expired contracts cannot be reactivated.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Contract"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Contract"
          }
        }
      }
    }
  }
}

GET /contracts/{id}

Get supplier contract

Fetch one contract by id (e.g. CON-0001) with its supplier, value, term dates, auto-renew flag and status — the check before raising a PO against it or letting it lapse.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get supplier contract",
  "description": "Fetch one contract by id (e.g. CON-0001) with its supplier, value, term dates, auto-renew flag and status — the check before raising a PO against it or letting it lapse.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Contract"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /contracts/{id}

Update supplier contract

Partially update a contract — revise value, dates or auto-renew, or move its status. The state machine blocks two moves (422): an expired contract cannot be returned to active/draft (create a new one), and a cancelled contract cannot be signed.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update supplier contract",
  "description": "Partially update a contract — revise value, dates or auto-renew, or move its status. The state machine blocks two moves (422): an expired contract cannot be returned to active/draft (create a new one), and a cancelled contract cannot be signed.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Contract"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Contract"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /contracts/{id}

Delete supplier contract

Permanently delete a contract record. Destructive and irreversible, and it removes the commercial history — set status to 'cancelled' or let it expire instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete supplier contract",
  "description": "Permanently delete a contract record. Destructive and irreversible, and it removes the commercial history — set status to 'cancelled' or let it expire instead.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /shipments

List inbound shipments

Purple SCM shipments are inbound freight from suppliers, with carrier (FedEx | UPS | DHL | USPS | Maersk | MSC), tracking number, origin, destination, estimated delivery and status pending | in_transit | out_for_delivery | delivered | exception | cancelled. Use for 'where is my delivery' questions. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List inbound shipments",
  "description": "Purple SCM shipments are inbound freight from suppliers, with carrier (FedEx | UPS | DHL | USPS | Maersk | MSC), tracking number, origin, destination, estimated delivery and status pending | in_transit | out_for_delivery | delivered | exception | cancelled. Use for 'where is my delivery' questions. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Shipment"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  }
}

POST /shipments

Create shipment

Record an inbound shipment from a supplier with its carrier, tracking number, origin, destination and estimated delivery.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create shipment",
  "description": "Record an inbound shipment from a supplier with its carrier, tracking number, origin, destination and estimated delivery.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Shipment"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Shipment"
          }
        }
      }
    }
  }
}

GET /shipments/{id}

Get shipment

Fetch one shipment by id (e.g. SHIP-0001) with its carrier, tracking number, route, ETA and current status — the record to quote when tracking a delivery.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get shipment",
  "description": "Fetch one shipment by id (e.g. SHIP-0001) with its carrier, tracking number, route, ETA and current status — the record to quote when tracking a delivery.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Shipment"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /shipments/{id}

Update shipment

Partially update a shipment — advance its status, correct the ETA or fix carrier/tracking details. Status only moves forward through pending → in_transit → out_for_delivery → delivered (going backwards is rejected with 422), and a delivered shipment cannot be cancelled.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update shipment",
  "description": "Partially update a shipment — advance its status, correct the ETA or fix carrier/tracking details. Status only moves forward through pending → in_transit → out_for_delivery → delivered (going backwards is rejected with 422), and a delivered shipment cannot be cancelled.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Shipment"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Shipment"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /shipments/{id}

Delete shipment

Permanently delete a shipment record. Destructive and irreversible, and it loses the delivery trail — cancel an undelivered shipment via a status update instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete shipment",
  "description": "Permanently delete a shipment record. Destructive and irreversible, and it loses the delivery trail — cancel an undelivered shipment via a status update instead.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /purchase_orders

List supply-chain purchase orders

Purple SCM purchase orders are procurement POs raised on a supplier for goods (SCMPO-####), with total amount, line-item count, delivery date and status draft | submitted | approved | received | cancelled. This is the supply-chain view; the finance/ERP purchase-order ledger is a separate system with its own ids. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List supply-chain purchase orders",
  "description": "Purple SCM purchase orders are procurement POs raised on a supplier for goods (SCMPO-####), with total amount, line-item count, delivery date and status draft | submitted | approved | received | cancelled. This is the supply-chain view; the finance/ERP purchase-order ledger is a separate system with its own ids. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PurchaseOrder"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  }
}

POST /purchase_orders

Create purchase order

Raise a purchase order on a supplier with total amount, line items and requested delivery date. New POs start in draft/submitted; approve them by updating status.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create purchase order",
  "description": "Raise a purchase order on a supplier with total amount, line items and requested delivery date. New POs start in draft/submitted; approve them by updating status.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/PurchaseOrder"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PurchaseOrder"
          }
        }
      }
    }
  }
}

GET /purchase_orders/{id}

Get purchase order

Fetch one supply-chain purchase order by id (e.g. SCMPO-0001) with its supplier, total amount, line items, delivery date and status.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get purchase order",
  "description": "Fetch one supply-chain purchase order by id (e.g. SCMPO-0001) with its supplier, total amount, line items, delivery date and status.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PurchaseOrder"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /purchase_orders/{id}

Update purchase order

Partially update a purchase order, including approving it (status 'approved') or cancelling it. The state machine rejects (422) cancelling a PO already received or in transit, and approving a cancelled PO.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update purchase order",
  "description": "Partially update a purchase order, including approving it (status 'approved') or cancelling it. The state machine rejects (422) cancelling a PO already received or in transit, and approving a cancelled PO.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/PurchaseOrder"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PurchaseOrder"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /purchase_orders/{id}

Delete purchase order

Permanently delete a purchase order. Destructive and irreversible, and it breaks the audit trail against the supplier — set status to 'cancelled' instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete purchase order",
  "description": "Permanently delete a purchase order. Destructive and irreversible, and it breaks the audit trail against the supplier — set status to 'cancelled' instead.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /quality_lots

List quality inspection lots

Purple SCM quality lots are received batches of goods under incoming inspection, with item SKU, quantity, supplier, defect rate, inspector and status pending | in_inspection | passed | failed | quarantined. Use these for supplier quality and defect-rate questions. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List quality inspection lots",
  "description": "Purple SCM quality lots are received batches of goods under incoming inspection, with item SKU, quantity, supplier, defect rate, inspector and status pending | in_inspection | passed | failed | quarantined. Use these for supplier quality and defect-rate questions. Filter/sort server-side with $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send these as _filter, _orderby, …) rather than listing everything and filtering locally.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/QualityLot"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  }
}

POST /quality_lots

Create quality lot

Register a received batch for incoming quality inspection, with its SKU, quantity and supplier. It starts pending; record the outcome by updating its status and defect rate.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create quality lot",
  "description": "Register a received batch for incoming quality inspection, with its SKU, quantity and supplier. It starts pending; record the outcome by updating its status and defect rate.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/QualityLot"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/QualityLot"
          }
        }
      }
    }
  }
}

GET /quality_lots/{id}

Get quality lot

Fetch one quality lot by id (e.g. QL-0001) with its SKU, quantity, supplier, defect rate, inspector and inspection status.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get quality lot",
  "description": "Fetch one quality lot by id (e.g. QL-0001) with its SKU, quantity, supplier, defect rate, inspector and inspection status.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/QualityLot"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /quality_lots/{id}

Update quality lot

Record an inspection outcome on a lot — set status to in_inspection, passed, failed or quarantined, and write the defect rate and inspector. Quarantining or failing a lot holds the goods, so state the reason.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update quality lot",
  "description": "Record an inspection outcome on a lot — set status to in_inspection, passed, failed or quarantined, and write the defect rate and inspector. Quarantining or failing a lot holds the goods, so state the reason.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/QualityLot"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/QualityLot"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /quality_lots/{id}

Delete quality lot

Permanently delete a quality lot record. Destructive and irreversible, and it erases the supplier's quality evidence — record a failed or quarantined outcome instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete quality lot",
  "description": "Permanently delete a quality lot record. Destructive and irreversible, and it erases the supplier's quality evidence — record a failed or quarantined outcome instead.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /openapi.json

Get OpenAPI spec

Returns the OpenAPI 3.0 specification for this API. No authentication required.

Authentication

[]

Request, responses and operation details

{
  "summary": "Get OpenAPI spec",
  "description": "Returns the OpenAPI 3.0 specification for this API. No authentication required.",
  "security": [],
  "responses": {
    "200": {
      "description": "OpenAPI spec",
      "content": {
        "application/json": {
          "schema": {
            "type": "object"
          }
        }
      }
    }
  }
}

Schemas and reusable components

securitySchemes

patAuth

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

parameters

InstanceId

{
  "in": "header",
  "name": "X-Instance-ID",
  "required": true,
  "schema": {
    "type": "string"
  }
}

ImpersonateUser

{
  "in": "header",
  "name": "X-PS-Impersonate-User",
  "required": false,
  "schema": {
    "type": "string",
    "format": "email"
  },
  "description": "Optional. Act as a specific user in the target instance — the value is that user's email (must exist in the instance). Resolution order for the effective user: (1) this header, if set; (2) the instance's configured default MCP user; (3) otherwise no user is bound. User-scoped paths (e.g. /users/{email}/…) also accept the literal \"me\", and Graph-style apps serve /me/… twins of those paths (Graph: /me ≡ /users/{signed-in-user}) — both resolve to whatever this header (or the default MCP user) points at. An explicit path email always wins over the header. The unified MCP server does not send this header — it relies on the per-instance default — so it is primarily for direct REST/HTTP callers."
}

schemas

Supplier

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "SUP-0001"
    },
    "name": {
      "type": "string",
      "example": "Global Supply Co"
    },
    "status": {
      "type": "string",
      "enum": [
        "active",
        "inactive",
        "pending_approval",
        "blacklisted"
      ],
      "example": "active"
    },
    "category": {
      "type": "string",
      "enum": [
        "Raw Materials",
        "Electronics",
        "Office Supplies",
        "Services",
        "Packaging"
      ],
      "example": "Electronics"
    },
    "country": {
      "type": "string",
      "example": "USA"
    },
    "taxId": {
      "type": "string",
      "example": "TAX-042317"
    },
    "annualContractValue": {
      "type": "number",
      "example": 180000
    },
    "rating": {
      "type": "integer",
      "minimum": 1,
      "maximum": 5,
      "example": 4
    },
    "onboardedAt": {
      "type": "string",
      "format": "date-time",
      "example": "2025-05-01T00:00:00.000Z"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Contract

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "CON-0001"
    },
    "supplierId": {
      "type": "string",
      "example": "SUP-0001"
    },
    "title": {
      "type": "string",
      "example": "Master Supply Agreement - 2025"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "active",
        "signed",
        "expired",
        "cancelled"
      ],
      "example": "active"
    },
    "value": {
      "type": "number",
      "example": 250000
    },
    "startDate": {
      "type": "string",
      "format": "date-time",
      "example": "2025-01-01T00:00:00.000Z"
    },
    "endDate": {
      "type": "string",
      "format": "date-time",
      "example": "2026-01-01T00:00:00.000Z"
    },
    "autoRenew": {
      "type": "boolean",
      "example": true
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Shipment

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "SHIP-0001"
    },
    "supplierId": {
      "type": "string",
      "example": "SUP-0001"
    },
    "status": {
      "type": "string",
      "enum": [
        "pending",
        "in_transit",
        "out_for_delivery",
        "delivered",
        "exception",
        "cancelled"
      ],
      "example": "in_transit"
    },
    "carrier": {
      "type": "string",
      "enum": [
        "FedEx",
        "UPS",
        "DHL",
        "USPS",
        "Maersk",
        "MSC"
      ],
      "example": "FedEx"
    },
    "trackingNumber": {
      "type": "string",
      "example": "1Z999AA10123456784"
    },
    "origin": {
      "type": "string",
      "example": "Shanghai"
    },
    "destination": {
      "type": "string",
      "example": "New York"
    },
    "estimatedDelivery": {
      "type": "string",
      "format": "date-time",
      "example": "2026-06-05T00:00:00.000Z"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

PurchaseOrder

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "SCMPO-0001"
    },
    "supplierId": {
      "type": "string",
      "example": "SUP-0001"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "submitted",
        "approved",
        "received",
        "cancelled"
      ],
      "example": "draft"
    },
    "totalAmount": {
      "type": "number",
      "example": 45000
    },
    "lineItems": {
      "type": "integer",
      "example": 5
    },
    "deliveryDate": {
      "type": "string",
      "format": "date-time",
      "example": "2026-06-15T00:00:00.000Z"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

QualityLot

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "QL-0001"
    },
    "supplierId": {
      "type": "string",
      "example": "SUP-0001"
    },
    "itemSku": {
      "type": "string",
      "example": "SKU-A1B2C3D4"
    },
    "quantity": {
      "type": "integer",
      "example": 500
    },
    "status": {
      "type": "string",
      "enum": [
        "pending",
        "in_inspection",
        "passed",
        "failed",
        "quarantined"
      ],
      "example": "pending"
    },
    "defectRate": {
      "type": "number",
      "format": "float",
      "example": 1.25
    },
    "inspectedBy": {
      "type": "string",
      "example": "qa1@company.com"
    },
    "receivedAt": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-15T00:00:00.000Z"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Document metadata and extensions

{
  "openapi": "3.0.0",
  "info": {
    "title": "Purple Suite SCM API",
    "version": "1.0.0",
    "description": "\n\n---\n\n**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`."
  },
  "security": [
    {
      "patAuth": []
    }
  ]
}
Interactive API explorer (requires JavaScript)