Skip to main content
The Subtotal Connector’s tools are grouped into toolsets that match the API reference: most tools have an API endpoint with the same data, linked from each tool.

Ask and metrics

Query your purchase data in plain language and get dashboard metrics.

ask

Ask a natural language question about your purchase data. The server translates it into SQL and returns the results — no SQL knowledge required. Parameters: Example questions:
  • “Show me the top 5 retailers by purchase count this year”
  • “How many active connections do I have?”
  • “What are my highest-value purchases from the last 30 days?”
  • “Which brands appear most frequently in my items?”
Response format:
Constraints:
  • Questions must be plain English — embedded SQL will be rejected
  • Only SELECT queries are generated — your data is never modified
  • Results are limited to 100 rows and 5 MB
  • A 5-second execution timeout is enforced
If the question cannot be answered with the available data, or the generated query fails, the response will contain an error field:
Other possible errors include request timeouts and response size limits.
ask uses an AI model to translate your question into SQL. For best results, be specific about the data you want, time ranges, and how results should be grouped or sorted. To inspect the data model itself (entities, fields, relationships), use get_data_model instead.

get_metric

Get a Subtotal Dashboard metric for a date range, computed the way the dashboard computes it, with a link to that page. Prefer it over ask for these metrics; use ask for everything else. Parameters: Metrics: Example:
Fields:
Metrics that need analytics return an error on plans without it. Windows must start in 2000 or later. Your team’s access scope applies, as it does for ask.

get_data_model

Returns the data model for your purchase data, including entities, fields, types, and relationships. Parameters: None Returns: A plain-text data model describing six entities: connection, purchase, item, retailer, product, and brand.
If you’re using ask, you typically don’t need to call get_data_model — the server already has the data model when answering questions.

Connections

Find connections and read their history.

list_connections

API: GET /connections List your connections, newest first. Every filter is optional. For counts or breakdowns across many connections, use ask instead. Requires the connections:read OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters: Response format:
When has_more is true, call again with starting_after set to the last connection_id you received.
These are the same connections as the public GET /connections API, except email and mobile are always null (use the API for contact details) and simulated test connections are listed only with include_simulated, which needs the simulator:read scope (the API always includes them). Without that scope, include_simulated returns error_category: "insufficient_scope". Deleted connections aren’t listed.

get_connection

API: GET /connections/{connection_id} Get one connection: its customer ID, retailer, status, creation date and tags. Requires the connections:read OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters: Response format: a single connection, in the same shape as one entry of list_connections.
This is the same connection as the public GET /connections/{connection_id} API, except email and mobile are always null. A connection that doesn’t exist, belongs to another account, or has been deleted returns a “not found” error, and so does a simulated test connection unless the connector has the simulator:read scope.

get_connection_activity

API: GET /connections/{connection_id}/activity Get a connection’s whole activity log, newest first: when it was created and activated, when it became unauthenticated or was disconnected, and when its customer ID, email or mobile changed. Each entry says who made the change. Pass a connection_id from list_connections or an ask result, for example after asking “Which of my connections became unauthenticated this week?”. Requires the connections:read OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters: Response format:
Fields:
These are the same entries as the Activity tab in the dashboard and the public GET /connections/{connection_id}/activity API. A connection that doesn’t exist, belongs to another account, or has been deleted returns a “not found” error.

Purchases

Read one purchase and its items.

get_purchase

API: GET /purchases/{purchase_id} Get one purchase with its items: totals, purchase type, status, and each item’s price, quantity and product. Pass a purchase_id from an ask result, for example after asking “What were my 10 largest orders last month?”. Parameters: Response format:
This is the same purchase as the public GET /purchases/{purchase_id} API, filtered by your team’s access scope: items outside it, and sensitive items, are left out. A purchase that doesn’t exist or isn’t visible to your account returns a “not found” error.
ask can also query by status, for example: “How many purchases are shipped?”status is the purchase status the retailer last showed. The value is a string, or null when unknown.The possible string values are:
  • pending
  • shipped
  • ready_for_pickup
  • completed
  • cancelled
Status is collected for Amazon, Walmart, and Target. Some purchases from these retailers, and purchases collected before October 2026, have null.completed and cancelled are final. Once a purchase reaches either status, its status does not change again.

Products

Browse and search your brand catalog.

list_products

API: GET /products List or search your brand catalog: every active product of the brands you subscribe to, whether or not consumers have bought it yet, ordered by product_id. These are the same products as the Products page in the dashboard. For sales figures across products, use ask instead. Parameters: Response format:

get_product

API: GET /products/{product_id} Get one product from your brand catalog, in the same shape as a list_products row. Pass a product_id from list_products or an ask result. Parameters:
A product that doesn’t exist, is inactive, or isn’t one of your brands’ products returns a “not found” error.

Product groups

Organise your catalog into groups and change their products.

list_product_groups

API: GET /product-groups List the groups you organise your brand catalog into, ordered by name, each with how many products it holds. These are the same groups as the Products page in the dashboard; each list_products row names its groups in groups. Takes no parameters. To see a group’s products, pass its product_group_id to list_products as group_id. Requires the product_groups:read OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Response format:

create_product_group

API: POST /product-groups Create an empty product group. Returns the group in the same shape as a list_product_groups row. Requires the product_groups:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters:

rename_product_group

API: PATCH /product-groups/{product_group_id} Rename a product group. Its products stay in it. Requires the product_groups:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters:

delete_product_group

API: DELETE /product-groups/{product_group_id} Delete a product group. Its products are not deleted. The tool is marked destructive, so assistants usually ask you to confirm first. Requires the product_groups:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters:

update_product_group_products

API: PUT /product-groups/{product_group_id}/products Add and remove products in a group in one call. Returns the group with its new product_count. Requires the product_groups:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters:
Only products from your brand catalog can be added. If any product to add isn’t in it, nothing changes and the tool returns a “not in your catalogue” error. A group that isn’t yours returns a “not found” error.

Retailers

See and configure the retailers available for linking.

get_retailers

API: GET /retailers List the retailers available to your account as structured rows. By default returns only the retailers enabled for your team (each with a Subtotal Link URL); pass include="all" to list every active retailer in the catalog, annotated with whether it’s enabled for your team. Requires the retailers:read OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters: Response format:
Fields:
This is the same retailer contract returned by the public GET /retailers API. With include="all", link_url is null for retailers not enabled for your team.

update_retailer

API: PATCH /retailers/{retailer_id} Update a retailer for your account: enable or disable it, and set who sees it on the Subtotal Link retailer-selection page. Every argument except retailer_id is optional, and omitted ones are left unchanged, but a call must set at least one of enabled, link_visibility or link_audience. Requires the retailers:write OAuth scope; a connector without it gets error_category: "insufficient_scope".
  • Visibility needs the retailer enabled: either already, or with enabled: true in the same call.
  • link_audience replaces the stored lists. Send both lists to keep entries you want.
  • restricted needs an audience: at least one email or domain, counting lists already stored.
  • Disabling takes no visibility settings: enabled: false can’t be combined with link_visibility or link_audience.
Disabling is destructive: it removes your configuration for the retailer. Links you’ve already shared stop working, and re-enabling issues a new link_url with link_visibility reset to everyone.
Parameters: Example — enable and restrict to one domain in a single call:
Response format:
With enabled: false, link_url, link_visibility and link_audience come back null.
Returns the same row shape as get_retailers and the public PATCH /retailers/{retailer_id} API. Repeating a call returns the current row. These return an error field with error_category: "invalid_input": an unknown retailer_id, no fields to update, visibility settings for a retailer that isn’t enabled, visibility settings with enabled: false, or restricted with no audience. Invalid emails or domains, more than 100 entries in a list, or a link_visibility other than the three values are also rejected.

Event simulator

Create test connections and events to try your webhooks or integration without a real shopper: the Event Simulator dashboard actions. Events go to webhook destinations that accept simulated events, marked is_simulated. Each tool works only on your simulated connections, so real connections never change.

simulate_connection

API: POST /simulated-connections Simulate a shopper linking a retailer account: creates an active simulated connection and sends connection.activated. Every call creates a new connection. Returns the connection in the same shape as get_connection, with is_simulated: true and email and mobile always null. Read it back with get_connection, or list_connections with include_simulated; both need the simulator:read scope. Requires the simulator:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters:
An unknown retailer_id returns an error and creates nothing. To send a purchase.created event, use simulate_purchase.

simulate_purchase

API: POST /simulated-connections/{connection_id}/purchases Simulate a purchase on one of your simulated connections, with random products from the connection’s retailer, dated now. Sends purchase.created. Every call creates a new purchase. Returns its purchase_id; read it with get_purchase. Requires the simulator:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters: Response format:
A real, deleted or unknown connection returns a “not found” error. The purchase includes at least one of your brands’ products when the retailer sells one. If it sells none and your account sees only your brands’ products, get_purchase returns “not found” for it: the purchase was still created and its event sent, so don’t retry.

set_simulated_status

API: PATCH /simulated-connections/{connection_id} Set one of your simulated connections to active or unauthenticated, sending connection.activated or connection.unauthenticated, to test how your integration handles a shopper losing and regaining their retailer login. Setting the status it already has sends the event again. Returns the connection, as simulate_connection does. Requires the simulator:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters:
A real, deleted or unknown connection returns a “not found” error and nothing changes.

delete_simulated_connection

API: DELETE /simulated-connections/{connection_id} Delete one of your simulated connections with its simulated purchases, to clean up test data. It sends no event and can’t be undone. The tool is marked destructive, so assistants usually ask you to confirm first. Returns the deleted connection_id. Requires the simulator:write OAuth scope. Connectors set up before this scope existed need to disconnect and reconnect to grant it; until then the tool returns error_category: "insufficient_scope". Parameters:
Real connections can’t be deleted: a real, deleted or unknown connection returns a “not found” error.

Webhooks

Create and manage the webhooks that receive your events. These tools manage the same webhooks as the Webhooks page of the Subtotal Dashboard and the public webhooks API. Listing needs the webhooks:read OAuth scope; creating, updating and deleting need webhooks:write. Connectors set up before these scopes existed need to disconnect and reconnect to grant them; until then the tools return error_category: "insufficient_scope".
A webhook sends your purchase and connection events to its URL. create_webhook creates it paused (is_active: false) unless is_active is true, and update_webhook can pause it or turn it on. Check that the URL is yours before you turn a webhook on.
Invalid input returns an error field with error_category: "invalid_input": an unknown webhook_id or event type, a name already used by another of your webhooks, a start date after the end date, or a URL that isn’t https on port 443 or doesn’t resolve only to public IP addresses.

list_webhooks

API: GET /webhooks List your webhooks with their subscribed events. The signing secret is never returned. Parameters: None Response format:

create_webhook

API: POST /webhooks Create a webhook subscribed to the given events. It starts paused unless is_active is true. Parameters: Response format: the webhook, in the list_webhooks shape, plus signing_secret.
signing_secret verifies the X-Subtotal-Signature header on every delivery (see Verifying signatures). It’s shown only in this response and can’t be read back. If it’s lost, delete the webhook and create a new one.

update_webhook

API: PATCH /webhooks/{webhook_id} Update a webhook. Every argument except webhook_id is optional and omitted ones are left unchanged, but a call must change at least one field. event_types replaces the subscribed events. Parameters: Response format: the updated webhook, in the list_webhooks shape.
This tool can set purchase date bounds but not clear them. Clear a bound with the Update webhook API. The signing secret doesn’t change.

delete_webhook

API: DELETE /webhooks/{webhook_id} Delete a webhook. It stops receiving events immediately, and this can’t be undone. To stop deliveries temporarily, pause it with update_webhook and is_active: false instead. Parameters: Response format: