Shopify
目次
- skill の情報
- 参考: SKILL.md 全文
- 事前に用意するもの
- API の基本
- ストアの状態を調べる
- ストアの情報と現在の API バージョン
- 使える API バージョンを一覧する
- 商品
- 商品を検索する(条件に合う最初の 20 件)
- 商品をページごとに取り出す(カーソル)
- 商品をバリエーションとメタフィールドごと取り出す
- バリエーションを 1 つ持つ商品を作る
- 価格や SKU を更新する
- 注文
- 最近の注文を一覧する(read_all_orders が無いと直近 30 件です)
- 注文を 1 件、配送先ごと取り出す
- 顧客
- 在庫
- メタフィールドとメタオブジェクト
- Storefront API(公開向けの読み取り専用)
- 一括処理
- Webhook
- つまずきやすいところ
- 安全のために
curl で Shopify の Admin / Storefront GraphQL API を使います。
skill の情報
| 提供元 | 追加で入れるもの — hermes skills install official/productivity/shopify で導入します |
| パス | optional-skills/productivity/shopify |
| バージョン | 1.0.0 |
| 作者 | community |
| ライセンス | MIT |
| 対応プラットフォーム | linux, macos, windows |
| タグ | Shopify, E-commerce, Commerce, API, GraphQL |
| 関連 skill | airtable, xurl |
参考: SKILL.md 全文
Shopify — Admin と Storefront の GraphQL API
curl から Shopify のストアを直接あつかいます。商品の一覧、在庫の管理、注文の取り出し、顧客の更新、メタフィールドの読み取りができます。SDK もアプリの枠組みも要りません。GraphQL のエンドポイントと、カスタムアプリのアクセストークンだけで動きます。
REST の Admin API は 2024-04 から旧方式の扱いになり、セキュリティ修正しか入りません。管理まわりの作業には GraphQL Admin を使ってください。お客さま向けの読み取り専用の問い合わせ(商品、コレクション、カート)には Storefront GraphQL を使います。
事前に用意するもの
- Shopify の管理画面で Settings → Apps and sales channels → Develop apps → Create an app と進みます。
- Configure Admin API scopes を押して、必要なスコープ(例は下にあります)を選んで保存します。
- Install app を押すと、Admin API のアクセストークンが 1 度だけ表示されます。すぐに控えてください。Shopify は 2 度と見せてくれません。トークンは
shpat_で始まります。 ${HERMES_HOME:-~/.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/...のような形です。この文字列をそのまま渡してください。前の部分を削ってはいけません。 - レート制限: 問い合わせのコストで計算します(リーキーバケット方式)。応答ごとに
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:active、tag:、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バリエーションを 1 つ持つ商品を作る
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"}]}'注文
最近の注文を一覧する(read_all_orders が無いと直近 30 件です)
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 です。
注文を 1 件、配送先ごと取り出す
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"]}}'在庫
在庫はバリエーションに紐づく 在庫アイテム が持っていて、数量は ロケーション ごとに管理されます。
# 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 年分の注文すべて)には次を使います。
# 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.jsonlJSONL は 1 行が 1 つのノードで、入れ子になった接続は __parentId を持つ別の行として出てきます。必要なら手元で組み直してください。
Webhook
出来事を受け取れるようにしておけば、こちらから問い合わせ続けずに済みます。
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 scopes を設定し直してから、入れ直してトークンを作り直してください。 userErrorsが空でも成功とは限りません。data.<mutation>.<resource>が null でないことも確かめてください。どちらにも出ない失敗もあるので、応答の全体を見てください。- GID と数字の ID は別物です。 旧来の REST は数字の ID を返していましたが、GraphQL は GID の文字列を求めます。変換は
gid://shopify/Product/<numeric>の形にします。 - レート制限は不意に来ます。
products(first: 250)を深く入れ子にしただけで 1000 ポイント以上かかり、標準プランのストアではすぐ絞られます。まずは狭く始めて、extensions.costを見ながら調整してください。 - 並び順に注意します。
products(first: N, reverse: true)はcreated_atではなくid DESCで並びます。「新しい順」にしたいときはsortKey: CREATED_AT, reverse: trueを使ってください。 - 過去の注文には
read_all_ordersが要ります。 これが無いとorders(...)は黙って直近 60 日ぶんに絞られます。エラーは出ず、思ったより件数が少ないだけです。注文の多い Shopify Plus の店舗では、アプリの保護データ設定からこのスコープを申請してください。 - 金額は文字列です。
49.0ではなく"49.00"で返ります。桁の揃え方が大事な場面で、そのままjq tonumberにかけないでください。 - 複数通貨の Money 項目 には
shopMoney(ストアの通貨)とpresentmentMoney(お客さまの通貨)の両方があります。どちらを使うかは決めてそろえてください。
安全のために
Shopify のミューテーションは本番に効きます。商品を作り、返金を通し、注文を取り消し、発送を確定します。productDelete、orderCancel、refundCreate、一括のミューテーションを実行する前に、どのストアの何をどう変えるのかをはっきり伝えて、相手に確認を取ってください。別に開発用ストアを持っている場合を除き、本番データの控えはどこにもありません。