shopify-admin-api について
Shopifyアプリやストア向け社内ツールを開発する人のためのスキルです。Admin APIによるデータ操作を、GraphQL、REST、バルク処理、在庫更新、エラー対応とともに説明します。
主な機能
- 01GraphQLでのデータ取得と変更
- 02簡単な検索でのREST利用
- 03非同期バルクエクスポート
- 04注文と在庫の更新
- 05権限・レート制限・エラーへの対応
活用シーン
- 01Shopifyの顧客情報とCRMの同期
- 02外部システムからのフルフィルメント更新
- 03商品や注文の一括エクスポート
- 04倉庫データに基づく在庫調整
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill shopify-admin-apiインストール後、Claude Code で/shopify-admin-apiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
tile.jsonevals/bulk-product-export-with-gid-ids/criteria.jsontask.mdevals/customer-tag-management-and-rate-limit-h/criteria.jsontask.mdevals/order-fulfillment-and-usererrors-handlin/criteria.jsontask.md詳細
Shopify Admin API
概要
Shopify Admin API を使うと、アプリからマーチャントのストアデータ(商品、バリエーション、注文、顧客、在庫、メタフィールドなど)に完全にアクセスできます。GraphQL(推奨)と REST の両方を利用でき、GraphQL では必要なフィールドを正確に選択できるほか、一括操作や計算コストシステムによる優れたレート制限を利用できます。@shopify/shopify-api Node.js ライブラリを使うか、Admin API アクセストークンを使って HTTP リクエストを直接送信してください。
このスキルを使用する場面
- 商品カタログデータ(タイトル、バリエーション、価格、画像、在庫)の読み取りや書き込みを行う場合
- 外部システムからプログラムで注文のフルフィルメントや更新を行う場合
- Shopify と CRM または ERP の間で顧客レコードを同期する場合
- Bulk Operations を使ってデータを一括エクスポートまたはインポートする場合
- Custom App トークンを使ってマーチャントのストアにアクセスする内部ツールを構築する場合
- 倉庫管理システムから在庫調整を自動化する場合
基本手順
-
Admin API アクセストークンを取得する
カスタムアプリ(単一ストア)の場合は、Admin → Settings → Apps and Sales Channels → Develop apps で作成します。公開アプリ/パートナーアプリの場合、OAuth 後にトークンを取得します(
@shopify-app-developmentを参照)。# .env SHOPIFY_ADMIN_API_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx SHOPIFY_SHOP=mystore.myshopify.com SHOPIFY_API_VERSION=2025-01 -
Admin API クライアントを初期化する
// lib/shopify-admin.ts import { shopifyApi, ApiVersion, Session } from "@shopify/shopify-api"; import "@shopify/shopify-api/adapters/node"; const shopify = shopifyApi({ apiKey: process.env.SHOPIFY_API_KEY!, apiSecretKey: process.env.SHOPIFY_API_SECRET!, scopes: ["read_products", "write_products", "read_orders", "write_orders"], hostName: process.env.SHOPIFY_APP_URL!, apiVersion: ApiVersion.January25, isEmbeddedApp: false, // true for merchant-facing embedded apps }); // For custom apps with a static token const session = new Session({ id: `offline_${process.env.SHOPIFY_SHOP}`, shop: process.env.SHOPIFY_SHOP!, state: "", isOnline: false, accessToken: process.env.SHOPIFY_ADMIN_API_TOKEN, }); export const adminClient = new shopify.clients.Graphql({ session }); export const restClient = new shopify.clients.Rest({ session }); -
GraphQL で商品をクエリする
// Fetch products with variants and inventory export async function getProducts(cursor?: string) { const response = await adminClient.request(` query GetProducts($cursor: String) { products(first: 50, after: $cursor) { pageInfo { hasNextPage endCursor } edges { node { id title status variants(first: 100) { edges { node { id sku price inventoryQuantity inventoryItem { id } } } } } } } } `, { variables: { cursor } }); return response.data.products; } // Update a product's price via mutation export async function updateVariantPrice(variantId: string, price: string) { const response = await adminClient.request(` mutation UpdateVariantPrice($id: ID!, $price: Money!) { productVariantUpdate(input: { id: $id, price: $price }) { productVariant { id price } userErrors { field message } } } `, { variables: { id: variantId, price } }); const { userErrors } = response.data.productVariantUpdate; if (userErrors.length > 0) throw new Error(userErrors[0].message); return response.data.productVariantUpdate.productVariant; } -
注文を取得、更新する
// Query unfulfilled orders export async function getUnfulfilledOrders() { const response = await adminClient.request(` query { orders(first: 50, query: "fulfillment_status:unfulfilled financial_status:paid") { edges { node { id name email createdAt lineItems(first: 50) { edges { node { title quantity variant { id sku } } } } shippingAddress { firstName lastName address1 city province zip country } } } } } `); return response.data.orders.edges.map(({ node }: any) => node); } // Mark an order as fulfilled export async function fulfillOrder(orderId: string, trackingNumber: string, trackingCompany: string) { // First get fulfillment order ID const orderResponse = await adminClient.request(` query GetFulfillmentOrders($id: ID!) { order(id: $id) { fulfillmentOrders(first: 5) { edges { node { id status lineItems(first: 20) { edges { node { id remainingQuantity } } } } } } } } `, { variables: { id: orderId } }); const fulfillmentOrder = orderResponse.data.order.fulfillmentOrders.edges[0]?.node; if (!fulfillmentOrder) throw new Error("No fulfillment order found"); const response = await adminClient.request(` mutation FulfillOrder($fulfillment: FulfillmentInput!) { fulfillmentCreate(fulfillment: $fulfillment) { fulfillment { id status } userErrors { field message } } } `, { variables: { fulfillment: { lineItemsByFulfillmentOrder: [{ fulfillmentOrderId: fulfillmentOrder.id }], trackingInfo: { number: trackingNumber, company: trackingCompany }, notifyCustomer: true, }, }, }); return response.data.fulfillmentCreate; } -
大規模なデータセットに Bulk Operations を実行する
数千件の商品や注文をエクスポートする場合は、Bulk Operations(GraphQL のみ)を使用してください。非同期で実行され、JSONL ファイルの URL が返されます。
// Start a bulk operation export async function startBulkProductExport() { const response = await adminClient.request(` mutation { bulkOperationRunQuery( query: """ { products { edges { node { id title status variants { edges { node { id sku price inventoryQuantity } } } } } } } """ ) { bulkOperation { id status } userErrors { field message } } } `); return response.data.bulkOperationRunQuery.bulkOperation; } // Poll for completion and download URL export async function getBulkOperationStatus() { const response = await adminClient.request(` query { currentBulkOperation { id status errorCode objectCount url # JSONL download URL — available when status is COMPLETED } } `); return response.data.currentBulkOperation; }
例
REST API による顧客の検索と更新
// REST is still valid for simple lookups where GraphQL overhead isn't worth it
export async function searchCustomers(email: string) {
const response = await restClient.get({
path: "customers/search",
query: { query: `email:${email}` },
});
return response.body.customers;
}
export async function tagCustomer(customerId: number, tags: string[]) {
const response = await restClient.put({
path: `customers/${customerId}`,
data: { customer: { id: customerId, tags: tags.join(",") } },
});
return response.body.customer;
}
在庫調整
export async function adjustInventory(inventoryItemId: string, locationId: string, delta: number) {
const response = await adminClient.request(`
mutation AdjustInventory($input: InventoryAdjustQuantitiesInput!) {
inventoryAdjustQuantities(input: $input) {
inventoryAdjustmentGroup {
changes {
name
delta
item { id }
location { name }
}
}
userErrors { field message }
}
}
`, {
variables: {
input: {
name: "available",
reason: "correction",
changes: [
{
inventoryItemId,
locationId,
delta,
},
],
},
},
});
return response.data.inventoryAdjustQuantities;
}
ベストプラクティス
- REST より GraphQL を優先する — GraphQL のコストベースのレート制限(毎秒 1000 コスト単位)は、REST の毎秒 40 リクエストより余裕があり、過剰なデータ取得も防げます
- 250 件を超えるデータのエクスポートには Bulk Operations を使用する — 数千件のレコードを手動でページングしないでください。Bulk Operations なら、単一の非同期ジョブで数百万件まで処理できます
- ミューテーションでは必ず
userErrorsを処理する — HTTP 200 のレスポンスが成功を意味するとは限りません。ミューテーションを成功として扱う前に、userErrors配列を確認してください - ID には
gid://shopify/Product/123形式を使用する — Admin API GraphQL ではグローバル ID を使用します。GID プレフィックスなしで数値 ID を送信しないでください - 指数バックオフを実装する —
Retry-Afterヘッダーを尊重し、429 および 503 のレスポンスに対してバックオフを実装してください - 変更されない商品データをキャッシュする — 商品タイトルやハンドルはほとんど変更されません。API 呼び出しを減らすため、適切な TTL を設定してキャッシュしてください
- 権限は必要最小限にする — 要求するスコープを減らすと、インストール時にマーチャントが感じる抵抗を軽減できます
よくある問題
| 問題 | 解決策 |
|---|---|
GraphQL エラー Cost exceeds bucket size | 接続の first: 引数を小さくする(250 ではなく 50 を使用する)か、深くネストした接続を避けるようクエリを再構成します |
| 数値 ID と GID 形式の不一致 | REST の数値 ID は必ず GID 形式に変換します:gid://shopify/Product/${numericId} |
| Bulk Operation の URL が 403 を返す | JSONL の URL は有効期限付きの署名済み S3 URL です。COMPLETED ステータスを確認したら、直ちにダウンロードしてください |
FULFILLMENT_ORDER_NOT_FOUND で注文のフルフィルメントに失敗する | API バージョン 2022-07 以降、注文のフルフィルメントには従来の Fulfillments API ではなく Fulfillment Orders API を使用する必要があります |
| Webhook イベントによって重複処理が発生する | Webhook ペイロードの admin_graphql_api_id を使用し、冪等性キーを実装してください |
| 顧客の更新時に既存のタグが消去される | タグを更新する際は、必ず現在のタグを取得してから追加してください。API はタグを統合せず、置き換えます |
関連スキル
- @shopify-app-development
- @shopify-webhooks
- @shopify-metafields
- @shopify-storefront-api
- @bulk-data-operations