跳到正文
cccskills
免费在 GitHub 公开

shopify

Read and update Shopify store data through GraphQL, an API format for requesting specific fields, using curl. The skill covers products, orders, customers, inventory adjustments, custom fields, and bulk data exports, with checks for API errors.

中文简介正在准备中。当前显示英语简介。

原文查看原文说明

Query Shopify Admin/Storefront GraphQL APIs via curl.

集成工具Shopify
查看安装方法

适用场景

  • Find products and update prices
  • Retrieve order and customer details
  • Check and adjust inventory by location
  • Export a large product catalog

包含的文件(1)

  • SKILL.md14.8 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Shopify — Admin & Storefront GraphQL APIs

Work with Shopify stores directly through curl: list products, manage inventory, pull orders, update customers, read metafields. No SDK, no app framework — just the GraphQL endpoint and a custom-app access token.

The REST Admin API is legacy since 2024-04 and only receives security fixes. Use GraphQL Admin for all admin work. Use Storefront GraphQL for read-only customer-facing queries (products, collections, cart).

Prerequisites

  1. In Shopify admin: Settings → Apps and sales channels → Develop apps → Create an app.
  2. Click Configure Admin API scopes, select what you need (examples below), save.
  3. Install app → the Admin API access token appears ONCE. Copy it immediately — Shopify will never show it again. Tokens start with shpat_.
  4. Save to ${HERMES_HOME:-~/.hermes}/.env:
    SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxx
    SHOPIFY_STORE_DOMAIN=my-store.myshopify.com
    SHOPIFY_API_VERSION=2026-01
    

Heads up: As of January 1, 2026, new "legacy custom apps" created in the Shopify admin are gone. New setups should use the Dev Dashboard (shopify.dev/docs/apps/build/dev-dashboard). Existing admin-created apps keep working. If the user's shop has no existing custom app and it's after 2026-01-01, direct them to Dev Dashboard instead of the admin flow.

Common scopes by task:

  • Products / collections: read_products, write_products
  • Inventory: read_inventory, write_inventory, read_locations
  • Orders: read_orders, write_orders (30 most recent without read_all_orders)
  • Customers: read_customers, write_customers
  • Draft orders: read_draft_orders, write_draft_orders
  • Fulfillments: read_fulfillments, write_fulfillments
  • Metafields / metaobjects: covered by the matching resource scopes

API Basics

  • Endpoint: https://$SHOPIFY_STORE_DOMAIN/admin/api/$SHOPIFY_API_VERSION/graphql.json
  • Auth header: X-Shopify-Access-Token: $SHOPIFY_ACCESS_TOKEN (NOT Authorization: Bearer)
  • Method: always POST, always Content-Type: application/json, body is {"query": "...", "variables": {...}}
  • HTTP 200 does not mean success. GraphQL returns errors in a top-level errors array and per-field userErrors. Always check both.
  • IDs are GID strings: gid://shopify/Product/10079467700516, gid://shopify/Variant/..., gid://shopify/Order/.... Pass these verbatim — don't strip the prefix.
  • Rate limit: calculated via query cost (leaky bucket). Each response has extensions.cost with requestedQueryCost, actualQueryCost, throttleStatus.{currentlyAvailable, maximumAvailable, restoreRate}. Back off when currentlyAvailable drops below your next query's cost. Standard shops = 100 points bucket, 50/s restore; Plus = 1000/100.

Base curl pattern (reusable):

shop_gql() {
  local query="$1"
  local variables="${2:-{}}"
  curl -sS -X POST \
    "https://${SHOPIFY_STORE_DOMAIN}/admin/api/${SHOPIFY_API_VERSION:-2026-01}/graphql.json" \
    -H "Content-Type: application/json" \
    -H "X-Shopify-Access-Token: ${SHOPIFY_ACCESS_TOKEN}" \
    --data "$(jq -nc --arg q "$query" --argjson v "$variables" '{query: $q, variables: $v}')"
}

Pipe through jq for readable output. -sS keeps errors visible but hides the progress bar.

Discovery

Shop info + current API version

shop_gql '{ shop { name myshopifyDomain primaryDomain { url } currencyCode plan { displayName } } }' | jq

List all supported API versions

shop_gql '{ publicApiVersions { handle supported } }' | jq '.data.publicApiVersions[] | select(.supported)'

Products

Search products (first 20 matching query)

shop_gql '
query($q: String!) {
  products(first: 20, query: $q) {
    edges { node { id title handle status totalInventory variants(first: 5) { edges { node { id sku price inventoryQuantity } } } } }
    pageInfo { hasNextPage endCursor }
  }
}' '{"q":"hoodie status:active"}' | jq

Query syntax supports title:, sku:, vendor:, product_type:, status:active, tag:, created_at:>2025-01-01. Full grammar: https://shopify.dev/docs/api/usage/search-syntax

Paginate products (cursor)

shop_gql '
query($cursor: String) {
  products(first: 100, after: $cursor) {
    edges { cursor node { id handle } }
    pageInfo { hasNextPage endCursor }
  }
}' '{"cursor":null}'
# subsequent calls: pass the previous endCursor

Get a product with variants + metafields

shop_gql '
query($id: ID!) {
  product(id: $id) {
    id title handle descriptionHtml tags status
    variants(first: 20) { edges { node { id sku price compareAtPrice inventoryQuantity selectedOptions { name value } } } }
    metafields(first: 20) { edges { node { namespace key type value } } }
  }
}' '{"id":"gid://shopify/Product/10079467700516"}' | jq

Create a product with one variant

shop_gql '
mutation($input: ProductCreateInput!) {
  productCreate(product: $input) {
    product { id handle }
    userErrors { field message }
  }
}' '{"input":{"title":"Test Hoodie","status":"DRAFT","vendor":"Hermes","productType":"Apparel","tags":["test"]}}'

Variants now have their own mutations in recent versions:

# Add variants after creating the product
shop_gql '
mutation($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkCreate(productId: $productId, variants: $variants) {
    productVariants { id sku price }
    userErrors { field message }
  }
}' '{"productId":"gid://shopify/Product/...","variants":[{"optionValues":[{"optionName":"Size","name":"M"}],"price":"49.00","inventoryItem":{"sku":"HD-M","tracked":true}}]}'

Update price / SKU

shop_gql '
mutation($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
    productVariants { id sku price }
    userErrors { field message }
  }
}' '{"productId":"gid://shopify/Product/...","variants":[{"id":"gid://shopify/ProductVariant/...","price":"55.00"}]}'

Orders

List recent orders (last 30 by default without read_all_orders)

shop_gql '
{
  orders(first: 20, reverse: true, query: "financial_status:paid") {
    edges { node {
      id name createdAt displayFinancialStatus displayFulfillmentStatus
      totalPriceSet { shopMoney { amount currencyCode } }
      customer { id displayName email }
      lineItems(first: 10) { edges { node { title quantity sku } } }
    } }
  }
}' | jq

Useful order query filters: financial_status:paid|pending|refunded, fulfillment_status:unfulfilled|fulfilled, created_at:>2025-01-01, tag:gift, email:foo@example.com.

Fetch a single order with shipping address

shop_gql '
query($id: ID!) {
  order(id: $id) {
    id name email
    shippingAddress { name address1 address2 city province country zip phone }
    lineItems(first: 50) { edges { node { title quantity variant { sku } originalUnitPriceSet { shopMoney { amount currencyCode } } } } }
    transactions { id kind status amountSet { shopMoney { amount currencyCode } } }
  }
}' '{"id":"gid://shopify/Order/...."}' | jq

Customers

# Search
shop_gql '
{
  customers(first: 10, query: "email:*@example.com") {
    edges { node { id email displayName numberOfOrders amountSpent { amount currencyCode } } }
  }
}'

# Create
shop_gql '
mutation($input: CustomerInput!) {
  customerCreate(input: $input) {
    customer { id email }
    userErrors { field message }
  }
}' '{"input":{"email":"test@example.com","firstName":"Test","lastName":"User","tags":["api-created"]}}'

Inventory

Inventory lives on inventory items tied to variants, quantities tracked per location.

# Get inventory for a variant across all locations
shop_gql '
query($id: ID!) {
  productVariant(id: $id) {
    id sku
    inventoryItem {
      id tracked
      inventoryLevels(first: 10) {
        edges { node { location { id name } quantities(names: ["available","on_hand","committed"]) { name quantity } } }
      }
    }
  }
}' '{"id":"gid://shopify/ProductVariant/..."}'

Adjust stock (delta) — uses inventoryAdjustQuantities:

shop_gql '
mutation($input: InventoryAdjustQuantitiesInput!) {
  inventoryAdjustQuantities(input: $input) {
    inventoryAdjustmentGroup { reason changes { name delta } }
    userErrors { field message }
  }
}' '{
  "input": {
    "reason": "correction",
    "name": "available",
    "changes": [{"delta": 5, "inventoryItemId": "gid://shopify/InventoryItem/...", "locationId": "gid://shopify/Location/..."}]
  }
}'

Set absolute stock (not delta) — inventorySetQuantities:

shop_gql '
mutation($input: InventorySetQuantitiesInput!) {
  inventorySetQuantities(input: $input) {
    inventoryAdjustmentGroup { id }
    userErrors { field message }
  }
}' '{"input":{"reason":"correction","name":"available","ignoreCompareQuantity":true,"quantities":[{"inventoryItemId":"gid://shopify/InventoryItem/...","locationId":"gid://shopify/Location/...","quantity":100}]}}'

Metafields & Metaobjects

Metafields attach custom data to resources (products, customers, orders, shop).

# Read
shop_gql '
query($id: ID!) {
  product(id: $id) {
    metafields(first: 10, namespace: "custom") {
      edges { node { key type value } }
    }
  }
}' '{"id":"gid://shopify/Product/..."}'

# Write (works for any owner type)
shop_gql '
mutation($metafields: [MetafieldsSetInput!]!) {
  metafieldsSet(metafields: $metafields) {
    metafields { id key namespace }
    userErrors { field message code }
  }
}' '{"metafields":[{"ownerId":"gid://shopify/Product/...","namespace":"custom","key":"care_instructions","type":"multi_line_text_field","value":"Wash cold. Tumble dry low."}]}'

Storefront API (public read-only)

Different endpoint, different token, used for customer-facing apps/hydrogen-style headless setups. Headers differ:

  • Endpoint: https://$SHOPIFY_STORE_DOMAIN/api/$SHOPIFY_API_VERSION/graphql.json
  • Auth header (public): X-Shopify-Storefront-Access-Token: <public token> — embeddable in browser
  • Auth header (private): Shopify-Storefront-Private-Token: <private token> — server-only
curl -sS -X POST \
  "https://${SHOPIFY_STORE_DOMAIN}/api/${SHOPIFY_API_VERSION:-2026-01}/graphql.json" \
  -H "Content-Type: application/json" \
  -H "X-Shopify-Storefront-Access-Token: ${SHOPIFY_STOREFRONT_TOKEN}" \
  -d '{"query":"{ shop { name } products(first: 5) { edges { node { id title handle } } } }"}' | jq

Bulk Operations

For dumps larger than rate limits allow (full product catalog, all orders for a year):

# 1. Start bulk query
shop_gql '
mutation {
  bulkOperationRunQuery(query: """
    { products { edges { node { id title handle variants { edges { node { sku price } } } } } } }
  """) {
    bulkOperation { id status }
    userErrors { field message }
  }
}'

# 2. Poll status
shop_gql '{ currentBulkOperation { id status errorCode objectCount fileSize url partialDataUrl } }'

# 3. When status=COMPLETED, download the JSONL file
curl -sS "$URL" > products.jsonl

Each JSONL line is a node, and nested connections are emitted as separate lines with __parentId. Reassemble client-side if needed.

Webhooks

Subscribe to events so you don't have to poll:

shop_gql '
mutation($topic: WebhookSubscriptionTopic!, $sub: WebhookSubscriptionInput!) {
  webhookSubscriptionCreate(topic: $topic, webhookSubscription: $sub) {
    webhookSubscription { id topic endpoint { __typename ... on WebhookHttpEndpoint { callbackUrl } } }
    userErrors { field message }
  }
}' '{"topic":"ORDERS_CREATE","sub":{"callbackUrl":"https://example.com/webhook","format":"JSON"}}'

Verify incoming webhook HMAC using the app's client secret (not the access token):

echo -n "$REQUEST_BODY" | openssl dgst -sha256 -hmac "$APP_SECRET" -binary | base64
# Compare to X-Shopify-Hmac-Sha256 header

Pitfalls

  • REST endpoints still exist but are frozen. Don't write new integrations against /admin/api/.../products.json. Use GraphQL.
  • Token format check. Admin tokens start with shpat_. Storefront public tokens with shpua_. If you have one and the wrong header, every request returns 401 without a useful error body.
  • 403 with a valid token = missing scope. Shopify returns {"errors":[{"message":"Access denied for ..."}]}. Re-configure Admin API scopes on the app, then reinstall to regenerate the token.
  • userErrors is empty != success. Also check data.<mutation>.<resource> is non-null. Some failures populate neither — inspect the whole response.
  • GID vs numeric ID. Legacy REST gave numeric IDs; GraphQL wants full GID strings. To convert: gid://shopify/Product/<numeric>.
  • Rate limit surprise. A single products(first: 250) with deep nesting can cost 1000+ points and throttle immediately on a standard-plan shop. Start narrow, read extensions.cost, adjust.
  • Pagination order. products(first: N, reverse: true) sorts by id DESC, not created_at. Use sortKey: CREATED_AT, reverse: true for "newest first."
  • read_all_orders for historical data. Without it, orders(...) silently caps at the 60-day window. You won't get an error, just fewer results than expected. For Shopify Plus merchants with many orders, request this scope via the app's protected-data settings.
  • Currencies are strings. Amounts come back as "49.00" not 49.0. Don't jq tonumber blindly if you care about zero-padding.
  • Multi-currency Money fields have shopMoney (store's currency) AND presentmentMoney (customer's). Pick one consistently.

Safety

Mutations in Shopify are real — they create products, charge refunds, cancel orders, ship fulfillments. Before running productDelete, orderCancel, refundCreate, or any bulk mutation: state clearly what the change is, on which shop, and confirm with the user. There is no staging clone of production data unless the user has a separate dev store.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

1password

免费有简介AI 评分 100分

通过1Password CLI,将保存的密码和API密钥用于命令及配置模板,完成安装、认证、秘密值读取,并通过环境变量向命令传递凭据。

  • 安装并配置1Password CLI
  • 使用已保存的API密钥运行命令
  • 向配置模板填入秘密值
NousResearch/hermes-agent25.5万2026年10月19日 更新

3-statement-model

免费有简介AI 评分 90分

创建用公式联动利润表、资产负债表和现金流量表的 Excel 模型,分阶段填入历史实际数据与预测假设,比较不同情景,并检查三张报表之间的一致性。

  • 向财务模板填入历史实际数据
  • 建立三张财务报表联动的预测
  • 比较基准、乐观和悲观情景
NousResearch/hermes-agent25.5万2026年10月19日 更新

Adapt existing PyTorch training scripts to use multiple GPUs or machines with limited code changes. The skill guides configuration, launch commands, mixed precision, which uses lower-precision numbers, and DeepSpeed or FSDP settings for distributing training work and memory.

中文简介正在准备中。当前显示英语简介。

  • Move a single-GPU training script to multiple GPUs
  • Configure mixed precision training
  • Set up DeepSpeed or FSDP to reduce memory use
NousResearch/hermes-agent25.5万2026年10月19日 更新

Configure Hermes to run AI models through Actual Computer on your own hardware. Set up either the hosted relay or a local connection, select models, verify responses, and troubleshoot request errors or empty output.

中文简介正在准备中。当前显示英语简介。

  • Connecting Hermes with an Actual inference key
  • Running models through a local Actual daemon
  • Downloading and loading local models
NousResearch/hermes-agent25.5万2026年10月19日 更新

adversarial-ux-test

免费有简介AI 评分 80分

扮演不擅长技术、容易不满的用户测试应用,发现难懂的操作和导致放弃使用的障碍。将实际可改进的问题与个人抱怨分开,整理截图、修复建议和任务单。

  • 在发布或演示前检查易用性
  • 检查新用户的使用体验
  • 查找注册和付费流程中的障碍
NousResearch/hermes-agent25.5万2026年10月19日 更新

agent-merge-conflict-arbiter

免费有简介AI 评分 100分

从中立立场解决两个智能体分别修改代码产生的Git冲突,对照双方差异和意图,记录每处冲突的取舍理由,并通过构建或测试验证合并结果。

  • 解决两个智能体的分支冲突
  • 整合并行工作树中的修改
  • 核对冲突设计决策的理由
NousResearch/hermes-agent25.5万2026年10月19日 更新

查看 NousResearch 的全部技能

报告此技能的问题