MCP tools
Every tool the store MCP server offers — scopes, arguments, metering and what comes back — generated from the tool manifest the server’s own registry produces.
How the tools behave
The store MCP server lives at the URL below and authenticates with the same API key, scopes and tenant boundary as the REST API. Setup for Claude Code and other clients is on Build with AI.
https://api.shopdeskbd.com/ext/mcp/v1- Your key decides the list. A key sees only the tools all of whose scopes it holds — 36 tools with every scope, none of the write tools with read scopes only.
- Structured results. Every tool declares an
outputSchemaand answers withstructuredContent, plus the same value as minified JSON text. - Concise by default. The order-desk tools answer with the fields needed to act; pass
response_format: "detailed"for the full objects. - Shopper-written text is data. Notes, addresses, names, emails and checkout answers come back in labelled fields, at most 500 characters, with links, markup and hidden characters removed. The REST API returns them unchanged.
- Links are downloaded.
create_product,create_category,create_brand,add_product_images_from_urls,attach_product_videodeclareopenWorldHint: true: they fetch the image links you pass, through the same checks and limits as the REST API (see Photos by link). Every other tool declaresfalse. - Candidates, not guesses.When a name a tool was given matches nothing or several rows — an option name, a parent category — the error object carries
candidatesbesidefields, and nothing is created. - Metering.Read tools count against your key’s read limit (the sales and customer summaries as 5 reads); writes against the write limit. See Rate limits.
Read tools
list_products
List products
A cursor page of products with their variants nested — the variant is the sellable unit (SKU, price). Filters: status, category_id (UUID), search (product name or variant SKU), updated_after (ISO 8601) to sync only what changed. Use get_product instead when you already hold an id. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/products
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
status | "draft" | "active" | "archived" | — |
category_id | uuid | Only products in this category. |
updated_after | string | ISO 8601 instant. Only rows changed since then — THE sync filter: it catches rows created earlier but modified since your last poll. |
search | string | Match on product name or variant SKU. |
get_product
Get a product
One product by id (UUID), variants nested. An id outside this key's stores is a plain not-found — indistinguishable from a nonexistent one.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/products/{id}
- Returns
- id, store_id, name, slug, short_description, description, thumbnail, status, delivery_charge, rating_average, rating_count, brand, categories, images, variants, videos, created_at, updated_at
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The product id. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
list_categories
List categories
A cursor page of product categories (id, name, slug, status). search matches name or slug — how a category name resolves to its id. Category ids feed list_products’ category_id filter. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/categories
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
status | "active" | "inactive" | — |
search | string | Match on name or slug (partial, case-insensitive). |
list_brands
List brands
A cursor page of brands (id, name, slug, status). search matches name or slug. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/brands
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
status | "active" | "inactive" | — |
search | string | Match on name or slug (partial, case-insensitive). |
list_orders
List orders
A cursor page of orders, compact by default — pass expand: ['customer','items'] for the full objects (keep pages compact when walking many). Filters: status, payment_status, fulfillment_status, channel (channel SLUG from list_channels, not an id), created_after/created_before, search (order number, customer name or phone — any Bangladeshi phone form), customer_id, assignee_id, follow_up_before/follow_up_after (the call-back schedule) and updated_after (ISO 8601) — THE sync filter: it catches an order placed last month but confirmed this morning, which a created_after poll would miss. For a phone lookup or the call list, find_orders and list_orders_needing_attention answer in one call. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/orders
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
status | "pending" | "follow_up" | "confirmed" | "processing" | "ready_to_ship" | "in_transit" | "delivered" | "returned" | "failed_delivery" | "cancelled" | "hold" | "spam" | — |
payment_status | "unpaid" | "partially_paid" | "paid" | "refunded" | — |
fulfillment_status | "unfulfilled" | "partially_fulfilled" | "fulfilled" | "returned" | — |
channel | string | Sales channel slug (e.g. facebook), not its id. |
created_after | string | ISO 8601 instant. |
created_before | string | ISO 8601 instant. |
updated_after | string | ISO 8601 instant. Only rows changed since then — THE sync filter: it catches rows created earlier but modified since your last poll. |
search | string | Match on order number, customer name or phone. |
customer_id | uuid | Only this customer's orders. |
assignee_id | uuid | Only orders assigned to this staff member. |
follow_up_before | string | ISO 8601. Only orders with a call-back due before it. |
follow_up_after | string | ISO 8601. Only orders with a call-back due after it. |
expand | "customer" | "items"[] | Related objects to include: customer, items. |
get_order
Get an order
One order by id (UUID). Pass expand: ["customer","items"] for the customer and line items; payment ROWS live behind list_order_payments (a separate scope).
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/orders/{id}
- Returns
- id, store_id, number, source, channel, customer_id, location_id, status, payment_status, fulfillment_status, subtotal, discount_amount, shipping_charge, tax_amount, total_amount, paid_amount, due_amount, refunded_amount, shipping_address, coupon, custom_fields, note, follow_up_at, follow_up_note, assignee, delivered_at, created_at, updated_at, items, customer
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The order id. |
expand | "customer" | "items"[] | Related objects to include: customer, items. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
list_order_payments
List an order's payments
Every payment recorded against one order, oldest first: settled, pending and refunded rows (refunds are negative amounts with status refunded). Small and unpaginated. Requires the payments:read scope — orders:read alone is not enough.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/orders/{id}/payments
- Returns
- data
| Argument | Type | Description |
|---|---|---|
order_idrequired | uuid | The order id. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
list_customers
List customers
A cursor page of customer records (name, phone, email, status). search matches name, email or phone (any Bangladeshi phone form). Use get_customer for one record with its addresses, or get_customer_summary for a customer with their order history. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/customers
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
search | string | Match on name, email or phone. |
get_customer
Get a customer
One customer by id (UUID), addresses included — the detail shape, a superset of the list_customers rows. expand: ["stats"] adds their order history in four numbers (needs orders:read too; omitted without it).
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/customers/{id}
- Returns
- id, store_id, full_name, email, phone, status, type, gender, created_at, updated_at, addresses, stats
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The customer id. |
expand | "stats"[] | stats: order count, total spent, last order, returns. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
get_inventory_levels
Get inventory levels
A cursor page of stock levels, one row per variant per location. Filters: warehouse_id, variant_id (both UUIDs), and low_stock: true for only the rows at or below their variant’s low-stock threshold — the reorder question answered server-side. missing_only: true (needs warehouse_id) instead lists the variants that have NEVER had stock set at that location — rows with null id and status: "stock_not_set"; use it to find what is left to stock. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/inventory/levels
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
warehouse_id | uuid | Only levels at this location. |
variant_id | uuid | Only levels for this variant. |
low_stock | boolean | Only rows at/below the low-stock threshold. |
missing_only | boolean | Only variants with no stock level at warehouse_id yet. Requires warehouse_id; not combinable with low_stock. |
list_locations
List locations
A cursor page of warehouses and other stock locations. These are COMPANY-scoped: both key types see the same set and store_id does not narrow them. Location ids feed get_inventory_levels’ warehouse_id filter. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/locations
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
list_channels
List sales channels
A cursor page of sales channels (id, name, slug) — order attribution metadata. The SLUG is what list_orders’ channel filter takes. Returns { data: [...], meta: { has_more, next_cursor } }; pass meta.next_cursor as starting_after to fetch the next page.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/channels
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | Cursor: the previous page's meta.next_cursor. Loop until meta.next_cursor is null. Do not combine with ending_before. |
ending_before | uuid | Cursor for paging backwards: the id of your current first row. |
sort | "created_at" | "-created_at" | -created_at (newest first, the default) or created_at. Use updated_after — not sort — to sync changes. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
get_store_profile
Get the store profile
Who this connection works for: the store (name, currency, timezone, contact, languages, advance-payment plans) and this key's access (company, the stores it reaches, its scopes and per-minute limits). A company-wide key must pass store_id to get a store; without it store is null and access.stores lists the stores to choose from.
- Idempotency key
- not taken
- REST twin
- GET /ext/v1/store, GET /ext/v1/me
- Returns
- store, access
| Argument | Type | Description |
|---|---|---|
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
get_sales_summary
Get a sales summary
Sales for one store over a window of store-local days: orders, net sales, net revenue and average order value, money received, and the cash on delivery still to collect. The figures run on three clocks, stated in basis: sales count orders DELIVERED in the window, money_received counts payments settled in the window, and cod_outstanding is a balance as of today. Name the window with period or from/to (default: this month so far). compare_to: "previous_period" adds the same figures for the window just before (plans with Advanced Reports only). A company-wide key must pass store_id.
- Idempotency key
- not taken
- response_format
- concise or detailed (default concise)
- REST twin
- GET /ext/v1/reports/sales-summary
- Returns
- from, to, orders, gross_sales, net_sales, net_revenue, average_order_value, money_received, store_id, time_zone, channel, basis, cod_outstanding, previous_period
| Argument | Type | Description |
|---|---|---|
period | "today" | "yesterday" | "last_7_days" | "last_30_days" | "this_month" | "last_month" | A named window on the store calendar. |
from | string | First day, YYYY-MM-DD (inclusive). Not with period. |
to | string | Last day, YYYY-MM-DD (inclusive). At most 366 days. |
compare_to | "previous_period" | Adds previous_period. |
channel | string | Only this sales channel (its slug, see list_channels). Money received and COD to collect are then null: payments are not split by channel. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
response_format | "concise" | "detailed" | concise (default): the fields needed to act, names beside ids. detailed: the full objects, as the REST API returns them. |
list_low_stock
List low-stock items
What needs restocking: stock rows at or below their low-stock threshold, with product and variant names, on-hand, reserved and available units. Optionally one location (location_id, see list_locations). Pages like every list: pass meta.next_cursor as starting_after for more.
- Idempotency key
- not taken
- response_format
- concise or detailed (default concise)
- REST twin
- GET /ext/v1/inventory/levels
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
location_id | uuid | Only this location (warehouse). |
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | The previous page's meta.next_cursor, to fetch the next page. Stop when it is null. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
response_format | "concise" | "detailed" | concise (default): the fields needed to act, names beside ids. detailed: the full objects, as the REST API returns them. |
find_orders
Find orders
Finds orders by an order number, a customer phone number or a customer name, newest first. A phone matches in any Bangladeshi form (+8801…, 8801…, 01…). Each row carries the customer, the lines, what is due and the call-back state. Optionally narrowed by status.
- Idempotency key
- not taken
- response_format
- concise or detailed (default concise)
- REST twin
- GET /ext/v1/orders
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
queryrequired | string | An order number, a phone number or a customer name. |
status | "pending" | "follow_up" | "confirmed" | "processing" | "ready_to_ship" | "in_transit" | "delivered" | "returned" | "failed_delivery" | "cancelled" | "hold" | "spam" | — |
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | The previous page's meta.next_cursor, to fetch the next page. Stop when it is null. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
response_format | "concise" | "detailed" | concise (default): the fields needed to act, names beside ids. detailed: the full objects, as the REST API returns them. |
list_orders_needing_attention
List orders needing attention
A work queue, oldest first. unconfirmed: new orders still pending — the confirmation-call list. follow_up_due: orders in follow_up whose call-back time has passed — the same queue the dashboard's overdue follow-ups count. Record each call with record_call_outcome.
- Idempotency key
- not taken
- response_format
- concise or detailed (default concise)
- REST twin
- GET /ext/v1/orders
- Returns
- data, meta
| Argument | Type | Description |
|---|---|---|
kindrequired | "unconfirmed" | "follow_up_due" | Which queue. |
limit | integer | Page size, 1–100 (default 25). |
starting_after | uuid | The previous page's meta.next_cursor, to fetch the next page. Stop when it is null. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
response_format | "concise" | "detailed" | concise (default): the fields needed to act, names beside ids. detailed: the full objects, as the REST API returns them. |
get_customer_summary
Get a customer's history
One customer and their history: order count, total spent on delivered orders, last order, returns, and their latest 5 orders. Name them by customer_id or by phone (any Bangladeshi form). When a phone matches more than one customer (a company-wide key sees several stores), customer is null and candidates lists them — call again with a customer_id.
- Idempotency key
- not taken
- response_format
- concise or detailed (default concise)
- REST twin
- GET /ext/v1/customers, GET /ext/v1/customers/{id}, GET /ext/v1/orders
- Returns
- customer, recent_orders, candidates
| Argument | Type | Description |
|---|---|---|
customer_id | uuid | The customer id. |
phone | string | A Bangladeshi mobile number. Or use customer_id. |
store_id | uuid | Restrict to one store (UUID). Breadth control for company-scoped keys; a store-scoped key may omit it (when sent it must match the key's own store). Narrows the tenant fence, never widens it. |
response_format | "concise" | "detailed" | concise (default): the fields needed to act, names beside ids. detailed: the full objects, as the REST API returns them. |
Write tools
create_order
Create an order
Places an order through the same engine as the merchant’s own staff: stock is reserved, totals are computed server-side, and any coupon is priced by the real coupon engine (an unusable code fails the order rather than mispricing it). Identify the buyer with customer_id OR an inline customer (find-or-create by phone) — never both. Each line needs variant_id or sku, plus a positive quantity. Returns the created order, the same shape get_order returns.
- Idempotency key
- required
- REST twin
- POST /ext/v1/orders
- Returns
- id, store_id, number, source, channel, customer_id, location_id, status, payment_status, fulfillment_status, subtotal, discount_amount, shipping_charge, tax_amount, total_amount, paid_amount, due_amount, refunded_amount, shipping_address, coupon, custom_fields, note, follow_up_at, follow_up_note, assignee, delivered_at, created_at, updated_at, items, customer
| Argument | Type | Description |
|---|---|---|
idempotency_keyrequired | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Required here. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
customer_id | uuid | An existing customer. Provide this OR customer. |
customer | object | Find-or-create by phone. Provide this OR customer_id. |
itemsrequired | object[] | The order lines — at least one. |
channel | string | Sales-channel SLUG for attribution (see list_channels); defaults to the store’s manual channel. |
location_id | uuid | Warehouse to allocate stock from; defaults to the company default location. |
shipping_address | object | Free-form address object, stored verbatim. |
shipping_charge | number | Delivery charge as your system computed it; default 0. |
tax_amount | number | — |
coupon_code | string | — |
note | string | — |
payment | object | An upfront settled payment; omit for an unpaid order and record later payments with record_order_payment. |
update_order_status
Update an order’s status
Moves one order to a new status under the SAME strict transition map the merchant’s staff use — an illegal move is refused as INVALID_STATUS_TRANSITION. A delivered order cannot be moved to returned: returns of delivered orders are made in the dashboard. A returned order keeps its stock held (not for sale) until the parcel is received in the dashboard; on a never-delivered order it keeps up to the delivery charge from an advance only when the parcel's pickup is recorded, else everything paid is owed back and no courier charge is booked. returned is refused as CONFLICT while a courier that reports pickups has not reported picking the parcel up; in_transit is refused while the order’s parcel is cancelled (book it again in the dashboard). note lands on the order’s timeline. Use cancel_order for cancellation. Returns the updated order.
- Idempotency key
- honoured
- REST twin
- PATCH /ext/v1/orders/{id}/status
- Returns
- id, store_id, number, source, channel, customer_id, location_id, status, payment_status, fulfillment_status, subtotal, discount_amount, shipping_charge, tax_amount, total_amount, paid_amount, due_amount, refunded_amount, shipping_address, coupon, custom_fields, note, follow_up_at, follow_up_note, assignee, delivered_at, created_at, updated_at, items, customer
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The order id. |
statusrequired | "pending" | "follow_up" | "confirmed" | "processing" | "ready_to_ship" | "in_transit" | "delivered" | "returned" | "failed_delivery" | "cancelled" | "hold" | "spam" | The target status. |
note | string | — |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
cancel_order
Cancel an order
Cancels one order — sugar for the cancelled transition, so the same transition map decides whether THIS order can still be cancelled. Reserved stock is released by the same lifecycle rules as the desk. Returns the cancelled order.
- Idempotency key
- honoured
- REST twin
- POST /ext/v1/orders/{id}/cancel
- Returns
- id, store_id, number, source, channel, customer_id, location_id, status, payment_status, fulfillment_status, subtotal, discount_amount, shipping_charge, tax_amount, total_amount, paid_amount, due_amount, refunded_amount, shipping_address, coupon, custom_fields, note, follow_up_at, follow_up_note, assignee, delivered_at, created_at, updated_at, items, customer
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The order id. |
note | string | Recorded on the order’s timeline. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
record_order_payment
Record a payment on an order
Records a payment that has ALREADY been received — it moves no money itself and contacts no payment provider. The staff payment path verbatim: the amount is capped at the order’s net due (payment_exceeds_due otherwise), and recording the same transaction_id twice in a store is a conflict (duplicate_transaction_id); a method set to require a transaction id refuses a payment without one (transaction_id_required). When a courier has already collected this order at the door, the collection is posted automatically and the order owes nothing — a payment above the due is then refused as cod_already_collected, naming the courier account the cash is sitting in; do not retry it, and record the courier’s remittance instead when it reaches the bank. Requires the payments:write scope — orders:write alone is not enough. Returns the updated order with its payment rollup.
- Idempotency key
- required
- REST twin
- POST /ext/v1/orders/{id}/payments
- Returns
- id, store_id, number, source, channel, customer_id, location_id, status, payment_status, fulfillment_status, subtotal, discount_amount, shipping_charge, tax_amount, total_amount, paid_amount, due_amount, refunded_amount, shipping_address, coupon, custom_fields, note, follow_up_at, follow_up_note, assignee, delivered_at, created_at, updated_at, items, customer
| Argument | Type | Description |
|---|---|---|
order_idrequired | uuid | The order id. |
amountrequired | number | Settled amount — above 0. |
payment_method_id | uuid | One of the store’s ACTIVE payment methods. |
transaction_id | string | Provider transaction id — unique per store. |
idempotency_keyrequired | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Required here. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
create_customer
Create a customer
Creates one customer record. phone (Bangladeshi mobile, stored canonical) and email are each unique per store — a duplicate is a conflict, not a merge. When placing an order for a possibly-new buyer, prefer create_order’s inline customer (find-or-create) over calling this first. Returns the customer, addresses included.
- Idempotency key
- honoured
- REST twin
- POST /ext/v1/customers
- Returns
- id, store_id, full_name, email, phone, status, type, gender, created_at, updated_at, addresses, stats
| Argument | Type | Description |
|---|---|---|
full_namerequired | string | — |
phone | string | Bangladeshi mobile, any accepted spelling. |
email | string | — |
status | "active" | "inactive" | "blocked" | Default active. |
gender | "male" | "female" | — |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
update_customer
Update a customer
Patches one customer record — only the fields you send change. The same per-store uniqueness rules as create_customer apply to phone and email. Returns the updated customer.
- Idempotency key
- honoured
- REST twin
- PATCH /ext/v1/customers/{id}
- Returns
- id, store_id, full_name, email, phone, status, type, gender, created_at, updated_at, addresses, stats
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The customer id. |
full_name | string | — |
phone | string | — |
email | string | — |
status | "active" | "inactive" | "blocked" | — |
gender | "male" | "female" | — |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
adjust_inventory
Adjust stock
Sets stock for one variant at one location, with a required audit reason. Name the variant by variant_id or by sku, and the quantity by quantity_change (a delta) or set_on_hand (the resulting total — prefer this when you know the count, and pass expected_on_hand to be refused rather than overwrite a number that moved). quantity_change is never zero, and cannot take on-hand below zero or below reservations. type picks the ledger category: purchase ADDS stock at a required unit_cost, damage must REMOVE stock, return can only ADD stock, default adjustment. A decrease filed as adjustment or damage is booked as a loss on the P&L at the current average cost. Returns the resulting stock level, the same row shape get_inventory_levels pages.
- Idempotency key
- required
- REST twin
- POST /ext/v1/inventory/adjustments
- Returns
- id, store_id, location_id, variant_id, sku, on_hand, reserved, available, low_stock_threshold, is_low_stock, status, location, variant, created_at, updated_at
| Argument | Type | Description |
|---|---|---|
location_idrequired | uuid | The warehouse to adjust. |
variant_id | uuid | The variant being adjusted. Or resolve by sku. |
sku | string | Resolve the variant by SKU instead of id. |
quantity_change | integer | Signed units: positive adds, negative removes. Or use set_on_hand to state the resulting total instead. |
set_on_hand | integer | The absolute on-hand this location should end at — what a stock-take says. The delta is computed under the row lock. |
expected_on_hand | integer | The on-hand you believe is there; a mismatch refuses the write with CONFLICT instead of applying against a moved number. |
type | "purchase" | "return" | "adjustment" | "damage" | Ledger category; default adjustment. |
reasonrequired | string | Stored on the ledger entry. Required — always. |
unit_cost | number | Per-unit cost. REQUIRED for purchase and refused on any other type; write-only, never appears in any response. |
low_stock_threshold | integer | Optionally update the level’s low-stock threshold. |
idempotency_keyrequired | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Required here. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
create_product
Create a product
Creates one product with at least one variant (the sellable unit: SKU + price) — optionally with its photos and each variant’s options, in one call. images: up to 10 https links, which Shopdesk downloads, checks and stores before answering; one link that cannot be used fails the whole call and nothing is created. A variant’s options name an attribute and a value by name (any case), slug or id, e.g. [{"attribute":"Colour","value":"Red"}]. Nothing is created from a name: an unknown one fails the call with candidates (the store’s variant attributes, or that attribute’s values) — add what is missing with create_attribute. image_index points a variant at a photo by its position in images. slug auto-generates from the name and auto-suffixes when taken. description is stored as sanitized HTML. Stock is NOT set here — new stock enters through adjust_inventory, where the ledger rules live. Returns the product, the same shape get_product returns.
- Idempotency key
- honoured
- REST twin
- GET /ext/v1/attributes, POST /ext/v1/products
- Returns
- id, store_id, name, slug, short_description, description, thumbnail, status, delivery_charge, rating_average, rating_count, brand, categories, images, variants, videos, created_at, updated_at
| Argument | Type | Description |
|---|---|---|
namerequired | string | — |
variantsrequired | object[] | At least one variant. |
slug | string | — |
brand_id | uuid | — |
short_description | string | — |
description | string | Rich description; sanitized server-side. |
status | "draft" | "active" | "archived" | Default draft. |
delivery_charge | number | Per-product override: 0 = free delivery, N = flat charge; omit for the standard shipping rules. |
categories | object[] | — |
images | object[] | The gallery, in display order, by https link. Add more later with add_product_images_from_urls. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
update_product
Update a product
Patches one product — only the fields you send change, with two REPLACE semantics to know: categories (when present) replaces the whole link set, and variants (when present) reconciles the FULL variant set — entries with an id are patched, entries without one are created, and existing variants missing from the list are REMOVED (refused when they carry stock history). A NEW variant takes options by name, like create_product; an existing one keeps its options. Photos are not edited here — use add_product_images_from_urls, and update_variant for a variant’s photo. brand_id: null detaches the brand; delivery_charge: null returns to the standard shipping rules. Returns the updated product.
- Idempotency key
- honoured
- REST twin
- GET /ext/v1/attributes, PATCH /ext/v1/products/{id}
- Returns
- id, store_id, name, slug, short_description, description, thumbnail, status, delivery_charge, rating_average, rating_count, brand, categories, images, variants, videos, created_at, updated_at
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The product id. |
name | string | — |
slug | string | — |
brand_id | uuid | null | Set null to detach the brand. |
short_description | string | — |
description | string | — |
status | "draft" | "active" | "archived" | — |
delivery_charge | number | null | Set null to return to the standard shipping rules. |
categories | object[] | When present, REPLACES the category links. |
variants | object[] | When present, reconciles the FULL variant set — see the tool description before sending a partial list. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
archive_product
Archive a product
Archives one product — it leaves the sellable catalog but its history stays. There is no delete on this surface; archiving is the end state. Returns { id, archived }.
- Idempotency key
- honoured
- REST twin
- POST /ext/v1/products/{id}/archive
- Returns
- id, archived
| Argument | Type | Description |
|---|---|---|
idrequired | uuid | The product id. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
update_order_details
Update an order’s note or call-back
Sets an order’s note and call-back (follow_up_at, follow_up_note) — nothing that changes money or stock, legal in any status. Only the fields you send change; null clears one. note REPLACES the whole note: text these tools show is shortened to 500 characters, so never rebuild a note from a value ending in "…". Returns the updated order.
- Idempotency key
- honoured
- REST twin
- PATCH /ext/v1/orders/{id}
- Returns
- id, store_id, number, source, channel, customer_id, location_id, status, payment_status, fulfillment_status, subtotal, discount_amount, shipping_charge, tax_amount, total_amount, paid_amount, due_amount, refunded_amount, shipping_address, coupon, custom_fields, note, follow_up_at, follow_up_note, assignee, delivered_at, created_at, updated_at, items, customer
| Argument | Type | Description |
|---|---|---|
order_idrequired | uuid | The order id (UUID). Find it with find_orders. |
note | string | null | The order note. null clears it. |
follow_up_at | string | null | When to call back (ISO 8601). null clears it. |
follow_up_note | string | null | Why the call-back is needed. null clears it. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
record_call_outcome
Record a call outcome
Records the result of a confirmation call on one order, using the same status rules as the dashboard. confirmed moves it to confirmed; cancelled cancels it (stock is released). call_back (needs call_back_at) and no_answer (call_back_at optional) move it to follow_up — the dashboard’s call-back queue — and set the call-back time and note as the reason. note also goes on the order timeline with a status change. A status the order cannot move to is refused before anything changes. Repeating the same outcome changes nothing more. Returns the steps taken and the order.
- Idempotency key
- honoured
- response_format
- concise or detailed (default concise)
- REST twin
- GET /ext/v1/orders/{id}, PATCH /ext/v1/orders/{id}/status, POST /ext/v1/orders/{id}/cancel, PATCH /ext/v1/orders/{id}
- Returns
- outcome, steps, order
| Argument | Type | Description |
|---|---|---|
order_idrequired | uuid | The order id (UUID). Find it with find_orders. |
outcomerequired | "confirmed" | "cancelled" | "call_back" | "no_answer" | What the call decided. |
note | string | What was said, in a sentence. |
call_back_at | string | When to call again (ISO 8601). Required for call_back; not allowed with confirmed or cancelled. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
response_format | "concise" | "detailed" | concise (default): the fields needed to act, names beside ids. detailed: the full objects, as the REST API returns them. |
create_attribute
Create an attribute (or add its missing values)
Makes sure an attribute (Colour, Size) exists with the values you list, and returns it with all its values. If the store already has an attribute with this name and type (or this slug), only the values it lacks are added, compared ignoring case — nothing is renamed, changed or removed, so calling it twice adds nothing the second time; added values go after the existing ones unless position places them. Otherwise the attribute is created with the values in order. type: variant (default) for options that define variants (what create_product options name), specification for details that only describe a product. input_type: "color" lets values carry swatch_hex. Returns { attribute, created, added_values }.
- Idempotency key
- honoured
- REST twin
- GET /ext/v1/attributes, POST /ext/v1/attributes, POST /ext/v1/attributes/{id}/values, GET /ext/v1/attributes/{id}
- Returns
- attribute, created, added_values
| Argument | Type | Description |
|---|---|---|
namerequired | string | e.g. Colour. |
slug | string | Look up (and create) by this slug instead of the name: lowercase letters, digits and single hyphens. |
type | "variant" | "specification" | variant (default) or specification. |
input_type | "text" | "color" | "image" | On create only: text (default), color (values take swatch_hex) or image. |
is_filterable | boolean | On create only: offer it as a storefront filter. |
is_required | boolean | On create only. |
values | object[] | The values it must have, in order. Each value once. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
create_category
Create a category
Creates one category, top-level or under parent — the parent category’s name, slug or id. A parent name that matches no category, or several, fails with candidates; send the slug or id. Images come by https link (image_url for listings, banner_url for the category page): Shopdesk downloads and checks them before answering, and a link that cannot be used fails the call with nothing created. Each call creates a new category — check list_categories first. slug derives from the name and is suffixed when taken. Returns the category.
- Idempotency key
- honoured
- REST twin
- GET /ext/v1/categories, POST /ext/v1/categories
- Returns
- id, store_id, parent_id, name, slug, image, banner_image, description, seo, status, position, created_at, updated_at
| Argument | Type | Description |
|---|---|---|
namerequired | string | — |
parent | string | The parent category: name (any case), slug or id. Omit for a top-level category. |
slug | string | — |
description | string | — |
status | "active" | "inactive" | Default active. |
position | integer | Sort order among siblings, lowest first. |
seo | object | — |
image_url | string | Listing image, by https link. Shopdesk downloads it, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB) and stores its own copy. |
banner_url | string | Category-page banner, by https link. Shopdesk downloads it, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB) and stores its own copy. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
create_brand
Create a brand
Creates one brand, with an optional logo (logo_url) and banner (banner_url) by https link: Shopdesk downloads and checks them before answering, and a link that cannot be used fails the call with nothing created. Each call creates a new brand — check list_brands first. slug derives from the name and is suffixed when taken. Returns the brand.
- Idempotency key
- honoured
- REST twin
- POST /ext/v1/brands
- Returns
- id, store_id, name, slug, logo, banner_image, description, seo, status, created_at, updated_at
| Argument | Type | Description |
|---|---|---|
namerequired | string | — |
slug | string | — |
description | string | — |
status | "active" | "inactive" | Default active. |
seo | object | — |
logo_url | string | Brand logo, by https link. Shopdesk downloads it, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB) and stores its own copy. |
banner_url | string | Brand-page banner, by https link. Shopdesk downloads it, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB) and stores its own copy. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
add_product_images_from_urls
Add product photos from links
Adds up to 10 photos to one product’s gallery from https links, in one change. Shopdesk downloads each link, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB and 8000 px) and stores its own copy; the link is not kept. All or nothing: one link that cannot be used fails the call, naming images[i].source_url, and no photo is added. Photos are appended unless position says otherwise; is_thumbnail: true makes one the product thumbnail. A gallery holds at most 30 photos. With an idempotency_key, a retry answers without downloading again. Returns { data }: the new photos in the order sent, with their ids.
- Idempotency key
- honoured
- REST twin
- POST /ext/v1/products/{id}/images
- Returns
- data
| Argument | Type | Description |
|---|---|---|
product_idrequired | uuid | The product id. |
imagesrequired | object[] | The photos, in order. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
update_variant
Update a variant
Edits one variant: price, discount_price, sku, barcode, weight and its photo (image_id, one of the product’s photos from get_product), with the dashboard’s rules — a sale price must be below the price, and the SKU is locked once the variant has stock or orders. Name the variant by id, SKU or barcode. Only the fields you send change; null clears discount_price, barcode, weight or image_id. Options and cost are not edited here. Returns the variant.
- Idempotency key
- honoured
- REST twin
- PATCH /ext/v1/variants/{identifier}
- Returns
- id, store_id, product_id, sku, barcode, name, price, discount_price, weight, dimensions, low_stock_threshold, status, image, options, created_at, updated_at
| Argument | Type | Description |
|---|---|---|
variantrequired | string | The variant’s id, SKU or barcode. |
price | number | — |
discount_price | number | null | Sale price, below the price. null removes it. |
sku | string | — |
barcode | string | null | null removes it. |
weight | number | null | Shipping weight in kg. null removes it. |
image_id | uuid | null | One of the product’s photo ids. null falls back to the product thumbnail. |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
attach_product_video
Attach a video to a product
Puts a video on a product. With video_url (an https link to an H.264 MP4 file — not a YouTube or other player page) Shopdesk imports the file in the background and places it at once: it shows on the product once verified, and video.status / placement.status say where it is (poll with get_product, whose videos[] lists ready videos). idempotency_key is required with video_url. With video_id (a video this key uploaded) it only places it. With neither it changes nothing and returns upload_steps, the calls a script makes to upload a file. option shows the video only for one option value of the product’s active variants, by name (e.g. { "attribute": "Colour", "value": "Red" } — this reads the product, so the key needs products:read too; a name the product does not use answers with candidates), or option_value_id by id. If an import started but the video could not then be placed, the error names the imported video id: call again with that video_id. gallery_slot is how many photos come before it (default 1, after the main photo). Placing a video the product already shows moves it. Returns { video, placement, upload_steps }.
- Idempotency key
- honoured
- REST twin
- GET /ext/v1/products/{id}, POST /ext/v1/videos/import, POST /ext/v1/products/{id}/videos
- Returns
- video, placement, upload_steps
| Argument | Type | Description |
|---|---|---|
product_idrequired | uuid | The product id. |
video_url | string | An https link to the MP4 file itself, at most 200 MB. Shopdesk downloads it and keeps its own copy (the link is not kept). |
video_id | uuid | A video this key already uploaded or imported. |
option | object | Show the video only for this option value of the product’s variants, by name (needs products:read). Omit for every variant. |
option_value_id | uuid | The same by id: a variant’s options[].value_id from get_product. |
title | string | Its accessible name; defaults to the product name. |
gallery_slot | integer | Photos before the video: 0 = first, default 1. |
sort_order | integer | Order among the product's videos (default 0). |
idempotency_key | string | A value unique to this LOGICAL operation (a UUID is ideal) — reuse the SAME value when retrying it, so a lost response can never double-run the write. Optional here, but recommended for automated callers. |
store_id | uuid | Which store to write into (UUID). REQUIRED for a company-scoped key; a store-scoped key may omit it (when sent it must match the key's own store). |
REST operations with no tool
Every REST operation has a tool or a written reason for having none:
GET /ext/v1/variants/{identifier}— get_product returns every variant of the product nested, and list_products searches by variant SKU; a per-variant lookup would be a second tool answering the same question.GET /ext/v1/categories/{id}— list_categories returns the same rows; one category is never worth a tool of its own.GET /ext/v1/brands/{id}— list_brands returns the same rows; one brand is never worth a tool of its own.GET /ext/v1/locations/{id}— list_locations returns the same rows; a store has a handful of locations.GET /ext/v1/inventory/movements— The stock ledger is sync data for ERPs and accounting, read over REST; get_inventory_levels answers the conversational question (how much is there).POST /ext/v1/products/{id}/images/reorder— Reordering photos is install-time catalog work (§5.1: photo delete, reorder and alt-text edits stay REST-only), and it restates the whole gallery, which a model gets wrong too easily.PATCH /ext/v1/products/{id}/images/{imageId}— Photo alt-text, order and main-photo edits are install-time catalog work (§5.1); add_product_images_from_urls sets alt text, position and the main photo when it adds a photo.DELETE /ext/v1/products/{id}/images/{imageId}— A permanent photo delete is kept to REST (§5.1) so a conversation cannot remove catalog media.PATCH /ext/v1/attributes/{id}— Renaming an attribute or changing its type or flags is install-time catalog work; create_attribute never renames or removes anything.PATCH /ext/v1/attributes/{id}/values/{valueId}— Renaming a value relabels every variant that shows it — catalog correction work, kept to REST and the dashboard.PATCH /ext/v1/categories/{id}— Category edits (moves, renames, image swaps) are install-time catalog work; create_category covers the conversational case.PATCH /ext/v1/brands/{id}— Brand edits (renames, logo swaps) are install-time catalog work; create_brand covers the conversational case.POST /ext/v1/videos— Starting an upload session is binary work for a script or the SDK: the caller then PUTs file parts to storage, which a model cannot do (§5.1). attach_product_video takes a finished upload’s video_id.POST /ext/v1/videos/{id}/part-urls— Part URLs exist only to PUT file bytes to storage — binary is not tool-shaped (§5.1).POST /ext/v1/videos/{id}/complete— Completing an upload follows the byte transfer a script makes; it is part of the same upload session (§5.1).GET /ext/v1/videos— Upload-session status for the script doing the upload (§5.1); get_product shows a product’s ready videos.GET /ext/v1/videos/{id}— Upload-session status for the script doing the upload (§5.1); get_product shows a product’s ready videos.DELETE /ext/v1/videos/{id}— Video deletes are kept to REST (§5.1: video posters and deletes), so a conversation cannot remove catalog media.POST /ext/v1/videos/{id}/poster— Video covers are kept to REST (§5.1: video posters and deletes): an uploaded cover is binary, and a product page falls back to the product thumbnail without one.DELETE /ext/v1/products/{id}/videos/{videoId}— Taking a video off a product is a media delete, kept to REST (§5.1: video posters and deletes) so a conversation cannot remove catalog media; attach_product_video only adds or moves a video.GET /ext/v1/reports/top-products— EXPANSION_PLAN §5.1 lists no tool for the ranking: get_sales_summary answers the conversational sales question, and the tool budget is held for the order desk. A BI sync reads the ranking over REST; adding a tool later means moving this line to MCP_REST_TWIN.