---
name: gentic-shopify
description: "Give your AI agent read-only access to your Shopify store. Inspect orders, products, and customers, export revenue CSVs that join cleanly against ad spend, and run Admin GraphQL queries — all through the Model Context Protocol."
license: MIT
metadata:
  author: gentic
  version: "1.0.0"
---

# Gentic Shopify

Connect any AI agent to a Shopify store for orders, products, customers, and bulk revenue exports — all through the Model Context Protocol. Read-only by design: list and inspect data, export CSVs for joins against your ad spend, and run free-form Admin GraphQL when the typed tools don't cover what you need.

## When to apply

- User wants to inspect or analyze Shopify orders, products, or customers in plain English.
- User wants to compute ROAS, attributed revenue, or CAC by joining Shopify orders against ad spend from Meta or Google Ads.
- User wants a CSV export of orders filtered by date, financial status, or channel (source_name).
- User wants the top customers by lifetime value, or to investigate a specific customer's order history.
- User wants to look up a single order, product, or customer by ID, GID, or product handle.
- User wants to run an arbitrary Admin GraphQL query against the Shopify store (read-only).
- User wants to know which Shopify store is connected, or check the store's currency, timezone, or plan.

## Tools

| Tool | Description | Cost |
|------|-------------|------|
| `export_orders_report` | Export Shopify orders to a CSV (1-hour signed URL) AND persist to MotherDuck `shopify_orders` for SQL-level joins with `meta_ad_insights`. Filters: `processed_at_min`/`max` (preferred for ROAS attribution), `created_at_min`/`max`, `financial_status`, `source_name`, `extra_query`. Paginates up to 10000 orders per call — narrow the date window if you hit the cap. The row schema matches `SHOPIFY_ORDERS_SCHEMA` exactly so the CSV and the warehouse table stay aligned. Pre-computes `net_revenue` (= `current_total_price` − `total_refunded`) and `processed_date` (DATE part of `processed_at`) so the join query against `meta_ad_insights` is a single equality predicate. $1.00 per call. | 100¢ / call |
| `get_customer` | Fetch a single Shopify customer by ID. Accepts a numeric ID or full GID like 'gid://shopify/Customer/123456789'. Returns the customer profile plus the 10 most recent orders inline (useful for LTV / churn / cohort analysis without a second roundtrip). Free. | Free |
| `get_order` | Fetch a single Shopify order by ID. Accepts either a numeric ID (from a Shopify admin URL) or a full GID like 'gid://shopify/Order/123456789'. Returns the full order detail including all line items (up to 250), refunds, fulfillments + tracking, addresses, and customer summary. Free. | Free |
| `get_product` | Fetch a single Shopify product by ID or handle. Provide exactly one of `id` (numeric or full GID) or `handle` (the url-safe slug). Returns full product detail including up to 20 images and 100 variants with prices, SKUs, and inventory quantities. By default the response carries the plain-text `description` only — set `include_html: true` to also pull `descriptionHtml` (roughly doubles the token cost on products with long marketing copy). Free. | Free |
| `get_shop_info` | Get basic information about the connected Shopify store: name, email, primary domain, currency, IANA timezone, weight unit, and plan. Free. Useful as a connection sanity check and to discover the currency/timezone needed for downstream order/revenue analysis. | Free |
| `list_customers` | List Shopify customers. Cursor-paginated. Typed filters: `email` (exact match), `tag` (exact tag match), `total_spent_min`/`max`, `orders_count_min`, `created_at_min`/`max`. Free-form Shopify search via `extra_query` (auto-wrapped in parens when combined with typed filters). Sort by `total_spent` (default, descending — top customers first, ideal for LTV analysis), `created_at`, `updated_at`, or `name`. $0.05 per call. `resultCount` reflects the page size only — paginate via `next_cursor`. | 5¢ / call |
| `list_orders` | List Shopify orders. Cursor-paginated. Filter by date range (`created_at_min/max`, `processed_at_min/max`, `updated_at_min/max`), `financial_status`, `fulfillment_status`, and `source_name` (channel — workhorse for ROAS attribution). Sort defaults to `processed_at` descending — that's the right key for revenue attribution because `processed_at` reflects when the order was actually placed; `created_at` can include the draft-order timestamp which lags. Returns flattened orders with shopMoney totals for analytics consistency. Each order carries `line_items_truncated: true` when more than 50 line items exist on it — follow up with `get_order` (250-item cap) for the full breakdown. $0.05 per call. `resultCount` reflects the page size only — paginate via `next_cursor`. For very large pull-downs (>1k orders) prefer `export_orders_report` (shipping in PR 3) which bulk-exports to CSV + MotherDuck. | 5¢ / call |
| `list_products` | List Shopify products, most recently updated first. Cursor-paginated. Filter by `status` (ACTIVE / ARCHIVED / DRAFT), `vendor`, `product_type`, and `title` (substring match). Free-form Shopify search via `extra_query` (auto-wrapped in parens when combined with typed filters). Returns summary fields including price range and total inventory — for variants and full descriptions, follow up with `get_product`. $0.05 per call. `resultCount` reflects the page size only — paginate via `next_cursor`. | 5¢ / call |
| `shopify_connection_status` | Check whether the calling organization has connected a Shopify store. Returns `{ connected, shop_domain, updated_at }` when connected, or `{ connected: false }` otherwise. Free. Call this before any other Shopify tool to give the user actionable guidance when the integration is missing. | Free |
| `shopify_graphql_query` | Execute a read-only GraphQL query against the Shopify Admin API (version 2026-04). Use this as an escape hatch when the typed Shopify tools don't cover what you need. Mutations and subscriptions are rejected — Gentic's v1 Shopify surface is analytics-only and will not modify the store. Prefer the typed tools (list_orders, list_products, etc.) when they exist — they handle pagination, field selection, and error formatting consistently. Returns the raw GraphQL response plus rate-limit cost info. $0.10 per call. | 10¢ / call |

## Workflow

### 1. Check connection with `shopify_connection_status`

Free call — returns whether the org has connected a Shopify store, plus the `shop_domain` and `updated_at` timestamp when connected. If not connected, direct the user to the Gentic dashboard → Integrations → Shopify to paste a shop subdomain and Admin API access token. At least one of `read_orders` / `read_products` / `read_customers` must be granted on the token; individual tools that need a specific scope will surface their own error at call time if it's missing.

### 2. Grab store basics with `get_shop_info`

Free call — returns name, email, primary domain, currency, IANA timezone, weight unit, and plan. Worth calling once per session: the currency drives how you format revenue numbers, and the timezone matters for any date-bucketed analysis (`processed_at` comes back in store-local time).

### 3. List orders with `list_orders` (5¢/call)

Cursor-paginated. Sort defaults to `processed_at` descending — the right key for revenue attribution, since `processed_at` is when the order was actually placed (`created_at` can include draft-order timestamps that lag). Filter by date range (`processed_at_min`/`max` for ROAS, `created_at_min`/`max` for funnel analysis), `financial_status`, `fulfillment_status`, and `source_name` (the channel — workhorse for ROAS attribution). Watch for `line_items_truncated: true` on orders with >50 line items — follow up with `get_order` for the full breakdown.

### 4. Drill into a single record (free)

`get_order`, `get_product`, and `get_customer` are all free. `get_order` returns full line items (up to 250), refunds, fulfillments + tracking, addresses, and customer summary. `get_product` accepts either `id` or `handle` and returns up to 20 images + 100 variants with prices, SKUs, and inventory quantities — set `include_html: true` to also pull `descriptionHtml`. `get_customer` returns the customer profile plus the 10 most recent orders inline (handy for LTV / churn / cohort analysis without a second roundtrip).

### 5. List products with `list_products` (5¢/call)

Most recently updated first. Filter by `status` (ACTIVE / ARCHIVED / DRAFT), `vendor`, `product_type`, and `title` (substring match). Returns summary fields including price range and total inventory. For variants, SKUs, and full descriptions, follow up with `get_product`. Use `extra_query` for any Shopify search syntax the typed filters don't cover.

### 6. List customers with `list_customers` (5¢/call)

Sorts by `total_spent` descending by default — top customers first, the right order for LTV analysis. Filter by `email` (exact), `tag` (exact), `total_spent_min`/`max`, `orders_count_min`, `created_at_min`/`max`. `extra_query` is the escape hatch for free-form Shopify search. Paginate via `next_cursor` — `resultCount` reflects the page only, not the total.

### 7. Export revenue for joins with `export_orders_report` ($1/call)

The ROAS workhorse. Returns a CSV download URL that expires in **1 hour** AND persists the rows to MotherDuck `shopify_orders` with a schema that matches the row layout exactly. Pre-computes `net_revenue` (= `current_total_price` − `total_refunded`) and `processed_date` (DATE part of `processed_at`) so the join against `meta_ad_insights` is a single equality predicate, not a CASE-laden CTE. Filter by `processed_at_min`/`max` (preferred for ROAS attribution), `created_at_min`/`max`, `financial_status`, `source_name`, or `extra_query`. Caps at 10,000 orders per call — narrow the date window if you hit it. Tell the user the 1-hour expiry up front.

### 8. Use `shopify_graphql_query` as an escape hatch (10¢/call)

When the typed tools don't cover a specific field, drop into raw Admin GraphQL (API version `2026-04`). Mutations and subscriptions are rejected server-side — Gentic's Shopify surface is analytics-only and won't modify the store. Returns the raw GraphQL response plus rate-limit cost info. Prefer the typed tools when they exist: they paginate, normalize money fields, and surface consistent error messages.

### 9. Present results clearly

For orders: lead with the headline (period revenue, order count, AOV), show a ranked table by the metric the user cares about, and call out outliers (huge orders, refunds, unusual channels). For customers: surface lifetime value, order count, and the most recent order date prominently. For exports: show the download URL **and the 1-hour expiry** clearly so the user doesn't lose access to the file. For ROAS workflows: after running `export_orders_report`, hand the analysis to the Data MCP server to join `shopify_orders.processed_date` against `meta_ad_insights.date` and compute ROAS = `net_revenue / spend`.

## Notes

- **Read-only by design.** Every tool in v1 is read-only; `shopify_graphql_query` rejects mutations and subscriptions server-side.
- **Scopes are not all-or-nothing.** Connection succeeds as long as at least one of `read_orders` / `read_products` / `read_customers` is granted. A ROAS-focused user can connect with `read_orders` alone; tools that need a different scope error at call time with a clear message naming the scope to add.
- **`processed_at` vs `created_at`:** `processed_at` is when the order was actually placed and is the right key for revenue attribution. `created_at` can include draft-order timestamps that lag. Default sort and the export's pre-computed `processed_date` both use `processed_at`.
- `shop_domain` is the subdomain only (e.g. `my-shop` for `my-shop.myshopify.com`) and is stored as plaintext — it's a tenant identifier, not a secret. The Admin API access token is encrypted at rest.
- `list_*` tools cap each page at 50 records and paginate via `next_cursor`. `resultCount` is the page size, not the total — there's no cheap total count on Shopify, by design.
- `get_order` returns up to 250 line items; if an order has more (rare), use `shopify_graphql_query` for the rest.
- `get_product` accepts either `id` (numeric or full GID) or `handle` (the url-safe slug) — exactly one, not both.
- **`export_orders_report` CSV links expire in 1 hour.** The MotherDuck `shopify_orders` table persists, so you don't lose the data — re-export only if you need a fresh CSV file.
- `export_orders_report` caps at 10,000 orders per call. Narrow the date window if you hit it; the warehouse table is appended/upserted in chunks so multiple calls compose cleanly.
- Shopify Admin API version pinned to `2026-04`. Bumps are coordinated with the gentic-web validator's `SHOPIFY_API_VERSION` constant.
- All tools are organization-scoped — users only see their own Shopify store.

## Tool details

- `export_orders_report` — Export Shopify orders to a CSV (1-hour signed URL) AND persist to MotherDuck `shopify_orders` for SQL-level joins with `meta_ad_insights`. Filters: `processed_at_min`/`max` (preferred for ROAS attribution), `created_at_min`/`max`, `financial_status`, `source_name`, `extra_query`. Paginates up to 10000 orders per call — narrow the date window if you hit the cap. The row schema matches `SHOPIFY_ORDERS_SCHEMA` exactly so the CSV and the warehouse table stay aligned. Pre-computes `net_revenue` (= `current_total_price` − `total_refunded`) and `processed_date` (DATE part of `processed_at`) so the join query against `meta_ad_insights` is a single equality predicate. $1.00 per call.
  - `processed_at_min` (string) — ISO-8601 lower bound on `processed_at`. Strongly recommended for ROAS analytics — `processed_at` is when payment actually happened, which matches how ad-spend dates work.
  - `processed_at_max` (string)
  - `created_at_min` (string) — ISO-8601 lower bound on `created_at`. Use when the analysis tracks order initiation including drafts.
  - `created_at_max` (string)
  - `financial_status` (string) — Filter to a financial status (e.g. 'paid'). Common practice for ROAS analysis is `paid` only.
  - `source_name` (string) — Filter by Shopify channel/source name.
  - `extra_query` (string) — Optional raw Shopify search-syntax clauses appended to the compiled query (parens-wrapped when combined with typed filters).
- `get_customer` — Fetch a single Shopify customer by ID. Accepts a numeric ID or full GID like 'gid://shopify/Customer/123456789'. Returns the customer profile plus the 10 most recent orders inline (useful for LTV / churn / cohort analysis without a second roundtrip). Free.
  - `id` (string, required) — Customer ID. Either a numeric string ('1234567890') or full GID ('gid://shopify/Customer/1234567890').
- `get_order` — Fetch a single Shopify order by ID. Accepts either a numeric ID (from a Shopify admin URL) or a full GID like 'gid://shopify/Order/123456789'. Returns the full order detail including all line items (up to 250), refunds, fulfillments + tracking, addresses, and customer summary. Free.
  - `id` (string, required) — Order ID. Either a numeric string ('1234567890') or a full GID ('gid://shopify/Order/1234567890').
- `get_product` — Fetch a single Shopify product by ID or handle. Provide exactly one of `id` (numeric or full GID) or `handle` (the url-safe slug). Returns full product detail including up to 20 images and 100 variants with prices, SKUs, and inventory quantities. By default the response carries the plain-text `description` only — set `include_html: true` to also pull `descriptionHtml` (roughly doubles the token cost on products with long marketing copy). Free.
  - `id` (string) — Product ID — numeric string ('1234567890') or full GID ('gid://shopify/Product/1234567890'). Mutually exclusive with `handle`.
  - `handle` (string) — Product handle (url slug, e.g. 'cool-tshirt'). Mutually exclusive with `id`.
  - `include_html` (boolean) — Include `descriptionHtml` (the rich-text form) in the response. Defaults to false.
- `get_shop_info` — Get basic information about the connected Shopify store: name, email, primary domain, currency, IANA timezone, weight unit, and plan. Free. Useful as a connection sanity check and to discover the currency/timezone needed for downstream order/revenue analysis.
- `list_customers` — List Shopify customers. Cursor-paginated. Typed filters: `email` (exact match), `tag` (exact tag match), `total_spent_min`/`max`, `orders_count_min`, `created_at_min`/`max`. Free-form Shopify search via `extra_query` (auto-wrapped in parens when combined with typed filters). Sort by `total_spent` (default, descending — top customers first, ideal for LTV analysis), `created_at`, `updated_at`, or `name`. $0.05 per call. `resultCount` reflects the page size only — paginate via `next_cursor`.
  - `email` (string) — Exact-match email filter (Shopify also supports wildcards like 'foo@*.com' via `extra_query`).
  - `tag` (string) — Exact-match customer tag filter.
  - `total_spent_min` (number) — Lower bound on lifetime total_spent (in shop currency). Use to filter to whale / VIP cohorts.
  - `total_spent_max` (number) — Upper bound on lifetime total_spent.
  - `orders_count_min` (integer) — Lower bound on lifetime order count. Use to filter to repeat-buyer cohorts.
  - `created_at_min` (string) — ISO-8601 lower bound on customer `created_at` (signup date). Use for cohort analysis.
  - `created_at_max` (string)
  - `extra_query` (string) — Optional raw Shopify customer search-syntax clauses appended to the compiled query. Examples: 'email:*@gmail.com', 'last_order_id:>0'. Auto-wrapped in parens when combined with typed filters.
  - `sort_by` (string, enum: `total_spent` | `created_at` | `updated_at` | `name`) — Sort key. Defaults to 'total_spent' (descending) — most valuable customers first.
  - `reverse` (boolean) — Reverse the sort direction. Default true when sorting by spend or recency (descending); false for name (alphabetical).
  - `limit` (integer, default: `50`, required) — Max results per page (1–250). Default 50. Shopify caps this at 250 even if a higher value is requested.
  - `cursor` (string) — Opaque cursor from a prior call's `next_cursor`. Omit to start at the beginning.
- `list_orders` — List Shopify orders. Cursor-paginated. Filter by date range (`created_at_min/max`, `processed_at_min/max`, `updated_at_min/max`), `financial_status`, `fulfillment_status`, and `source_name` (channel — workhorse for ROAS attribution). Sort defaults to `processed_at` descending — that's the right key for revenue attribution because `processed_at` reflects when the order was actually placed; `created_at` can include the draft-order timestamp which lags. Returns flattened orders with shopMoney totals for analytics consistency. Each order carries `line_items_truncated: true` when more than 50 line items exist on it — follow up with `get_order` (250-item cap) for the full breakdown. $0.05 per call. `resultCount` reflects the page size only — paginate via `next_cursor`. For very large pull-downs (>1k orders) prefer `export_orders_report` (shipping in PR 3) which bulk-exports to CSV + MotherDuck.
  - `created_at_min` (string) — ISO-8601 lower bound on `created_at`, inclusive. Use this when the agent's clock tracks when orders were started (drafts included).
  - `created_at_max` (string) — ISO-8601 upper bound on `created_at`, inclusive.
  - `processed_at_min` (string) — ISO-8601 lower bound on `processed_at`, inclusive. Prefer this for ROAS / revenue-attribution polling — `processed_at` is when payment actually happened.
  - `processed_at_max` (string) — ISO-8601 upper bound on `processed_at`, inclusive.
  - `updated_at_min` (string) — ISO-8601 lower bound on `updated_at`. Use for change-data-capture flows that need to catch refunds and edits.
  - `updated_at_max` (string)
  - `financial_status` (string) — Filter by Shopify financial status. Common values: 'paid', 'pending', 'authorized', 'partially_paid', 'refunded', 'partially_refunded', 'voided'.
  - `fulfillment_status` (string) — Filter by fulfillment status. Common values: 'fulfilled', 'unfulfilled', 'partial', 'restocked'.
  - `source_name` (string) — Filter by Shopify channel/source name (e.g. 'web', 'pos', 'shopify_draft_order', or a specific connected-app source name).
  - `extra_query` (string) — Optional raw Shopify search-syntax clauses appended to the compiled query. Auto-wrapped in parens when combined with typed filters so OR clauses scope correctly. See https://shopify.dev/docs/api/usage/search-syntax.
  - `sort_by` (string, enum: `processed_at` | `created_at` | `updated_at`) — Sort key. Defaults to 'processed_at' (descending) which matches revenue-attribution expectations. Set 'created_at' if the polling pattern tracks order-start timestamps including drafts.
  - `reverse` (boolean) — Reverse the sort order. Defaults to `true` (descending — newest first).
  - `limit` (integer, default: `50`, required) — Max results per page (1–250). Default 50. Shopify caps this at 250 even if a higher value is requested.
  - `cursor` (string) — Opaque cursor from a prior call's `next_cursor`. Omit to start at the beginning.
- `list_products` — List Shopify products, most recently updated first. Cursor-paginated. Filter by `status` (ACTIVE / ARCHIVED / DRAFT), `vendor`, `product_type`, and `title` (substring match). Free-form Shopify search via `extra_query` (auto-wrapped in parens when combined with typed filters). Returns summary fields including price range and total inventory — for variants and full descriptions, follow up with `get_product`. $0.05 per call. `resultCount` reflects the page size only — paginate via `next_cursor`.
  - `status` (string, enum: `ACTIVE` | `ARCHIVED` | `DRAFT`) — Product status filter.
  - `vendor` (string) — Vendor name filter (exact match).
  - `product_type` (string) — Product type filter (exact match).
  - `title` (string) — Substring match on product title.
  - `extra_query` (string) — Optional raw Shopify search-syntax clauses appended to the compiled query. Auto-wrapped in parens when combined with typed filters so OR clauses scope correctly.
  - `limit` (integer, default: `50`, required) — Max results per page (1–250). Default 50. Shopify caps this at 250 even if a higher value is requested.
  - `cursor` (string) — Opaque cursor from a prior call's `next_cursor`. Omit to start at the beginning.
- `shopify_connection_status` — Check whether the calling organization has connected a Shopify store. Returns `{ connected, shop_domain, updated_at }` when connected, or `{ connected: false }` otherwise. Free. Call this before any other Shopify tool to give the user actionable guidance when the integration is missing.
- `shopify_graphql_query` — Execute a read-only GraphQL query against the Shopify Admin API (version 2026-04). Use this as an escape hatch when the typed Shopify tools don't cover what you need. Mutations and subscriptions are rejected — Gentic's v1 Shopify surface is analytics-only and will not modify the store. Prefer the typed tools (list_orders, list_products, etc.) when they exist — they handle pagination, field selection, and error formatting consistently. Returns the raw GraphQL response plus rate-limit cost info. $0.10 per call.
  - `query` (string, required) — A complete GraphQL query string (read-only — mutations/subscriptions are rejected). Must end with a closing brace. Example: '{ shop { name } }'. Reference: https://shopify.dev/docs/api/admin-graphql/2026-04
  - `variables` (object) — Optional GraphQL variables object for parameterized queries, e.g. { id: 'gid://shopify/Product/123' }.

---

_This SKILL.md is generated from the live Gentic MCP manifest. Tool names, descriptions, and pricing are always current. Connect Gentic Shopify at https://gentic.co/shopify._
