跳到主要內容

Shopify

通過 curl 使用 Shopify Admin 和 Storefront GraphQL API。涵蓋商品、訂單、客戶、庫存、元字段。

技能元數據

來源可選 — 使用 hermes skills install official/productivity/shopify 安裝
路徑optional-skills/productivity/shopify
版本1.0.0
作者community
許可證MIT
平臺linux, macos, windows
標籤Shopify, E-commerce, Commerce, API, GraphQL
相關技能airtable, xurl

參考:完整 SKILL.md

信息

以下是 Hermes 在觸發此技能時加載的完整技能定義。這是技能激活時代理所看到的指令。

Shopify — Admin & Storefront GraphQL APIs

直接通過 curl 操作 Shopify 店鋪:列出商品、管理庫存、拉取訂單、更新客戶、讀取元字段。無需 SDK,無需應用框架——只需 GraphQL 端點和自定義應用訪問令牌。

REST Admin API 自 2024-04 起已成為遺留版本,僅接收安全修復。所有管理工作請使用 GraphQL AdminStorefront GraphQL 用於面向客戶的只讀查詢(商品、集合、購物車)。

前提條件

  1. 在 Shopify 後臺:Settings → Apps and sales channels → Develop apps → Create an app
  2. 點擊 Configure Admin API scopes,選擇所需權限(見下方示例),保存。
  3. Install app → Admin API 訪問令牌僅顯示一次。請立即複製——Shopify 永遠不會再次顯示它。令牌以 shpat_ 開頭。
  4. 保存至 ~/.hermes/.env
    SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxx
    SHOPIFY_STORE_DOMAIN=my-store.myshopify.com
    SHOPIFY_API_VERSION=2026-01

注意: 截至 2026 年 1 月 1 日,在 Shopify 後臺創建的新“遺留自定義應用”已不再可用。新設置應使用 Dev Dashboard (shopify.dev/docs/apps/build/dev-dashboard)。現有在後臺創建的應用仍可正常工作。如果用戶的店鋪沒有現有的自定義應用且日期在 2026-01-01 之後,請引導他們使用 Dev Dashboard 而非後臺流程。

常見任務所需的權限範圍:

  • 商品 / 集合:read_products, write_products
  • 庫存:read_inventory, write_inventory, read_locations
  • 訂單:read_orders, write_orders(若無 read_all_orders,僅限最近 30 個)
  • 客戶:read_customers, write_customers
  • 草稿訂單:read_draft_orders, write_draft_orders
  • 發貨:read_fulfillments, write_fulfillments
  • 元字段 / 元對象:由匹配的資源權限範圍覆蓋

API 基礎

  • 端點: https://$SHOPIFY_STORE_DOMAIN/admin/api/$SHOPIFY_API_VERSION/graphql.json
  • 認證頭: X-Shopify-Access-Token: $SHOPIFY_ACCESS_TOKEN Authorization: Bearer
  • 方法: 始終為 POST,始終使用 Content-Type: application/json,請求體為 {"query": "...", "variables": {...}}
  • HTTP 200 不代表成功。 GraphQL 會在頂層 errors 數組和每字段的 userErrors 中返回錯誤。務必檢查兩者。
  • ID 為 GID 字符串: gid://shopify/Product/10079467700516, gid://shopify/Variant/..., gid://shopify/Order/...。原樣傳遞這些 ID——不要去除前綴。
  • 速率限制: 通過查詢成本計算(漏桶算法)。每個響應包含 extensions.cost,其中有 requestedQueryCost, actualQueryCost, throttleStatus.{currentlyAvailable, maximumAvailable, restoreRate}。當 currentlyAvailable 低於下一次查詢的成本時,請退避。標準店鋪 = 100 點桶容量,50/秒恢復速率;Plus 店鋪 = 1000/100。

基礎 curl 模式(可複用):

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}')"
}

通過管道傳遞給 jq 以獲得可讀輸出。-sS 保持錯誤可見但隱藏進度條。

發現

店鋪信息 + 當前 API 版本

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

列出所有支持的 API 版本

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

商品

搜索商品(前 20 個匹配項)

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

查詢語法支持 title:sku:vendor:product_type:status:activetag:created_at:>2025-01-01。完整語法:https://shopify.dev/docs/api/usage/search-syntax

分頁獲取商品(遊標)

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

獲取包含變體和元字段的單個商品

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

創建含一個變體的商品

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"]}}'

在最近版本中,變體現在擁有自己的突變操作:

# 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}}]}'

更新價格 / 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"}]}'

訂單

列出最近訂單(默認最近 30 個,若無 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

有用的訂單查詢過濾器:financial_status:paid|pending|refunded, fulfillment_status:unfulfilled|fulfilled, created_at:>2025-01-01, tag:gift, email:foo@example.com

獲取包含收貨地址的單個訂單

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

客戶

# 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 items) 上,數量按 地點 (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/..."}'

調整庫存(增量)— 使用 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/..."}]
}
}'

設置絕對庫存(非增量)— 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}]}}'

元字段與元對象

元字段將自定義數據附加到資源(商品、客戶、訂單、店鋪)。

# 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(公開只讀)

不同的端點,不同的令牌,用於面向客戶的應用程序或 Hydrogen 風格的無頭架構。請求頭有所不同:

  • 端點: https://$SHOPIFY_STORE_DOMAIN/api/$SHOPIFY_API_VERSION/graphql.json
  • 認證頭(公開): X-Shopify-Storefront-Access-Token: <public token> — 可嵌入瀏覽器
  • 認證頭(私有): Shopify-Storefront-Private-Token: <private token> — 僅限服務器端使用
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

批量操作

適用於超過速率限制允許的大規模數據導出(如完整商品目錄、全年所有訂單):

# 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

每行 JSONL 是一個節點,嵌套的連接關係作為單獨的行發出,並帶有 __parentId。如有需要,可在客戶端重新組裝。

Webhooks

訂閱事件以避免輪詢:

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"}}'

使用應用的客戶端密鑰(而非訪問令牌)驗證傳入 webhook 的 HMAC:

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

常見陷阱

  • REST 端點仍然存在但已凍結。 不要針對 /admin/api/.../products.json 編寫新的集成代碼。請使用 GraphQL。
  • 令牌格式檢查。 Admin 令牌以 shpat_ 開頭。Storefront 公開令牌以 shpua_ 開頭。如果你持有其中一種令牌卻使用了錯誤的請求頭,每個請求都會返回 401,且沒有有用的錯誤正文。
  • 有效令牌返回 403 = 缺少權限範圍。 Shopify 返回 {"errors":[{"message":"Access denied for ..."}]}。請在應用中重新配置 Admin API 權限範圍,然後重新安裝以生成新令牌。
  • userErrors 為空 ≠ 成功。 還需檢查 data.<mutation>.<resource> 是否非空。某些失敗情況兩者均不會填充 — 請檢查整個響應。
  • GID 與數字 ID。 舊版 REST 提供數字 ID;GraphQL 需要完整的 GID 字符串。轉換方法:gid://shopify/Product/<numeric>
  • 速率限制意外。 單個 products(first: 250) 若包含深層嵌套,可能消耗 1000+ 點數,並在標準計劃店鋪上立即觸發限流。應從窄範圍開始,讀取 extensions.cost,再進行調整。
  • 分頁排序。 products(first: N, reverse: true)id DESC 排序,而非 created_at。若需“最新優先”,請使用 sortKey: CREATED_AT, reverse: true
  • read_all_orders 用於歷史數據。 若無此權限,orders(...) 會靜默限制在 60 天窗口內。你不會收到錯誤,只是結果少於預期。對於擁有大量訂單的 Shopify Plus 商家,請通過應用的受保護數據設置請求此權限範圍。
  • 貨幣為字符串。 金額返回形式為 "49.00" 而非 49.0。若關心零填充,請勿盲目使用 jq tonumber
  • 多幣種 Money 字段 同時包含 shopMoney(店鋪貨幣)和 presentmentMoney(客戶貨幣)。請始終一致地選擇其一。

安全提示

Shopify 中的突變操作是真實生效的 — 它們會創建商品、處理退款、取消訂單、發貨履約。在執行 productDeleteorderCancelrefundCreate 或任何批量突變之前:明確說明變更內容、涉及哪家店鋪,並與用戶確認。除非用戶擁有獨立的開發店鋪,否則不存在生產數據的暫存克隆環境。