---
**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`.
---
**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`.
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"
}
}
}
}
}
}