Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Complete API integration guide for Shopify including GraphQL Admin API, REST Admin API, Storefront API, Ajax API, OAuth authentication, rate limiting, and webhooks. Use when making API calls to Shopify, authenticating apps, fetching product/order/customer data programmatically, implementing cart operations, handling webhooks, or working with API version 2025-10. Requires fetch or axios for JavaScript implementations.
.claude/skills/microck-shopify-api/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 185% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 333% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 815% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 139% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 587% | 0% |
Expert guidance for all Shopify APIs including GraphQL Admin API, REST Admin API, Storefront API, Ajax API, authentication, and webhooks.
Invoke this skill when:
Modern API for Shopify Admin operations with efficient data fetching.
Endpoint:
POST https://{store}.myshopify.com/admin/api/2025-10/graphql.jsonHeaders:
javascript{ 'X-Shopify-Access-Token': 'shpat_...', 'Content-Type': 'application/json' }
Basic Query:
graphqlquery GetProducts($first: Int!) { products(first: $first) { edges { node { id title handle status vendor productType # Pricing priceRange { minVariantPrice { amount currencyCode } maxVariantPrice { amount currencyCode } } # Images images(first: 5) { edges { node { id url altText } } } # Variants variants(first: 10) { edges { node { id title sku price inventoryQuantity available: availableForSale } } } } } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } } }
Variables:
json{ "first": 10 }
JavaScript Example:
javascriptasync function getProducts(accessToken, store, limit = 10) { const query = ` query GetProducts($first: Int!) { products(first: $first) { edges { node { id title handle priceRange { minVariantPrice { amount currencyCode } } } } pageInfo { hasNextPage endCursor } } } `; const response = await fetch( `https://${store}.myshopify.com/admin/api/2025-10/graphql.json`, { method: 'POST', headers: { 'X-Shopify-Access-Token': accessToken, 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables: { first: limit }, }), } ); const { data, errors } = await response.json(); if (errors) { console.error('GraphQL Errors:', errors); throw new Error(errors[0].message); } return data.products; }
Common Mutations:
Create product:
graphqlmutation CreateProduct($input: ProductInput!) { productCreate(input: $input) { product { id title handle } userErrors { field message } } }
Update product:
graphqlmutation UpdateProduct($input: ProductInput!) { productUpdate(input: $input) { product { id title status } userErrors { field message } } }
Set metafield:
graphqlmutation SetMetafield($input: MetafieldInput!) { metafieldSet(input: $input) { metafield { id namespace key value type } userErrors { field message } } }
Traditional REST API for Shopify Admin operations.
Base URL:
https://{store}.myshopify.com/admin/api/2025-10/Authentication:
javascriptheaders: { 'X-Shopify-Access-Token': 'shpat_...' }
Common Endpoints:
Get products:
javascriptGET /admin/api/2025-10/products.json?limit=50&status=active // JavaScript const response = await fetch( `https://${store}.myshopify.com/admin/api/2025-10/products.json?limit=50`, { headers: { 'X-Shopify-Access-Token': accessToken, }, } ); const { products } = await response.json();
Get single product:
javascriptGET /admin/api/2025-10/products/{product_id}.json
Create product:
javascriptPOST /admin/api/2025-10/products.json // Body { "product": { "title": "New Product", "body_html": "<p>Description</p>", "vendor": "My Vendor", "product_type": "Shoes", "status": "draft" } }
Update product:
javascriptPUT /admin/api/2025-10/products/{product_id}.json // Body { "product": { "id": 123456789, "title": "Updated Title" } }
Get orders:
javascriptGET /admin/api/2025-10/orders.json?status=any&limit=50
Get customers:
javascriptGET /admin/api/2025-10/customers.json?limit=50
Complete OAuth flow for custom apps.
Step 1: Authorization Request
GET https://{shop}.myshopify.com/admin/oauth/authorize?
client_id={api_key}&
redirect_uri={redirect_uri}&
scope={scopes}&
state={random_state}Scopes:
javascriptconst scopes = [ 'read_products', 'write_products', 'read_orders', 'write_orders', 'read_customers', 'write_customers', 'read_inventory', 'write_inventory', 'read_metafields', 'write_metafields', ].join(',');
Step 2: Handle Callback
javascript// User approves, Shopify redirects to: GET {redirect_uri}?code={auth_code}&state={state}&hmac={hmac}&shop={shop} // Verify HMAC for security function verifyHmac(query, secret) { const { hmac, ...params } = query; const message = Object.keys(params) .sort() .map(key => `${key}=${params[key]}`) .join('&'); const hash = crypto .createHmac('sha256', secret) .update(message) .digest('hex'); return hash === hmac; }
Step 3: Exchange Code for Token
javascriptPOST https://{shop}.myshopify.com/admin/oauth/access_token // Body { "client_id": "{api_key}", "client_secret": "{api_secret}", "code": "{auth_code}" } // Response { "access_token": "shpat_...", "scope": "write_products,read_orders" } // Node.js example async function getAccessToken(shop, code, apiKey, apiSecret) { const response = await fetch( `https://${shop}/admin/oauth/access_token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: apiKey, client_secret: apiSecret, code, }), } ); const { access_token, scope } = await response.json(); return { access_token, scope }; }
GraphQL uses points-based rate limiting.
Rate Limits:
Check Rate Limit:
javascriptconst response = await fetch(graphqlEndpoint, options); const rateLimitHeader = response.headers.get('X-Shopify-GraphQL-Admin-Api-Call-Limit'); // Example: "42/50" (42 points used, 50 max) const [used, limit] = rateLimitHeader.split('/').map(Number); if (used > 40) { // Approaching limit, slow down await delay(1000); }
Implement Retry Logic:
javascriptasync function fetchWithRetry(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { const response = await fetch(url, options); if (response.status === 429) { // Rate limited const retryAfter = response.headers.get('Retry-After') || 2; await delay(retryAfter * 1000 * Math.pow(2, i)); // Exponential backoff continue; } return response; } throw new Error('Max retries exceeded'); } function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
Public API for headless/custom storefronts.
Endpoint:
POST https://{store}.myshopify.com/api/2025-10/graphql.jsonHeaders (Public Access):
javascript{ 'Content-Type': 'application/json', 'X-Shopify-Storefront-Access-Token': '{public_token}' // Optional for public stores }
Query Products:
graphqlquery GetProducts($first: Int!) { products(first: $first) { edges { node { id title handle description priceRange { minVariantPrice { amount currencyCode } maxVariantPrice { amount currencyCode } } images(first: 3) { edges { node { url altText } } } variants(first: 10) { edges { node { id title price { amount currencyCode } availableForSale sku } } } } } } }
Cart Operations:
Create cart:
graphqlmutation CreateCart($input: CartInput!) { cartCreate(input: $input) { cart { id checkoutUrl lines(first: 10) { edges { node { id quantity merchandise { ... on ProductVariant { id title price { amount } } } } } } cost { totalAmount { amount currencyCode } subtotalAmount { amount } totalTaxAmount { amount } } } } }
Add to cart:
graphqlmutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!) { cartLinesAdd(cartId: $cartId, lines: $lines) { cart { id lines(first: 10) { edges { node { id quantity } } } } } }
JavaScript API for cart operations in themes.
Get Cart:
javascriptfetch('/cart.js') .then(response => response.json()) .then(cart => { console.log('Cart:', cart); console.log('Item count:', cart.item_count); console.log('Total:', cart.total_price); console.log('Items:', cart.items); });
Add to Cart:
javascriptfetch('/cart/add.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: variantId, // Required: variant ID quantity: 1, // Optional: default 1 properties: { // Optional: custom data 'Gift wrap': 'Yes', 'Note': 'Happy Birthday!' } }) }) .then(response => response.json()) .then(item => { console.log('Added to cart:', item); }) .catch(error => { console.error('Error:', error); });
Update Cart:
javascriptfetch('/cart/change.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ line: 1, // Line item index (1-based) quantity: 2 // New quantity (0 = remove) }) }) .then(response => response.json()) .then(cart => console.log('Updated cart:', cart));
Clear Cart:
javascriptfetch('/cart/clear.js', { method: 'POST' }) .then(response => response.json()) .then(cart => console.log('Cart cleared'));
Update Cart Attributes:
javascriptfetch('/cart/update.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ attributes: { 'gift_wrap': 'true', 'gift_message': 'Happy Birthday!' }, note: 'Please handle with care' }) }) .then(response => response.json()) .then(cart => console.log('Cart updated'));
Event-driven notifications for app integrations.
Common Webhooks:
javascript// Product events 'products/create' 'products/update' 'products/delete' // Order events 'orders/create' 'orders/updated' 'orders/paid' 'orders/fulfilled' 'orders/cancelled' // Customer events 'customers/create' 'customers/update' 'customers/delete' // Cart events 'carts/create' 'carts/update' // Inventory events 'inventory_levels/update' // App events 'app/uninstalled'
Register Webhook (GraphQL):
graphqlmutation CreateWebhook($input: WebhookSubscriptionInput!) { webhookSubscriptionCreate(input: $input) { webhookSubscription { id topic endpoint { __typename ... on WebhookHttpEndpoint { callbackUrl } } } userErrors { field message } } }
Variables:
json{ "input": { "topic": "ORDERS_CREATE", "webhookSubscription": { "callbackUrl": "https://your-app.com/webhooks/orders", "format": "JSON" } } }
Handle Webhook (Node.js/Express):
javascriptapp.post('/webhooks/orders', async (req, res) => { // Verify webhook HMAC const hmac = req.headers['x-shopify-hmac-sha256']; const body = JSON.stringify(req.body); const hash = crypto .createHmac('sha256', SHOPIFY_WEBHOOK_SECRET) .update(body) .digest('base64'); if (hash !== hmac) { return res.status(401).send('Invalid HMAC'); } // Process order const order = req.body; console.log('New order:', order.id, order.email); // Respond quickly (within 5 seconds) res.status(200).send('OK'); // Process in background await processOrder(order); });
javascriptasync function getAllProducts(accessToken, store) { let allProducts = []; let hasNextPage = true; let cursor = null; while (hasNextPage) { const query = ` query GetProducts($first: Int!, $after: String) { products(first: $first, after: $after) { edges { node { id title } } pageInfo { hasNextPage endCursor } } } `; const response = await fetch(endpoint, { method: 'POST', headers: { 'X-Shopify-Access-Token': accessToken, 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables: { first: 50, after: cursor }, }), }); const { data } = await response.json(); allProducts.push(...data.products.edges.map(e => e.node)); hasNextPage = data.products.pageInfo.hasNextPage; cursor = data.products.pageInfo.endCursor; } return allProducts; }
javascriptasync function safeApiCall(query, variables) { try { const response = await fetch(endpoint, { method: 'POST', headers: { 'X-Shopify-Access-Token': accessToken, 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables }), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const { data, errors } = await response.json(); if (errors) { console.error('GraphQL Errors:', errors); throw new Error(errors[0].message); } return data; } catch (error) { console.error('API Error:', error); throw error; } }
errors and userErrorsjavascript// GraphQL Admin API POST https://{store}.myshopify.com/admin/api/2025-10/graphql.json Headers: { 'X-Shopify-Access-Token': 'shpat_...' } // REST Admin API GET https://{store}.myshopify.com/admin/api/2025-10/products.json Headers: { 'X-Shopify-Access-Token': 'shpat_...' } // Storefront API POST https://{store}.myshopify.com/api/2025-10/graphql.json Headers: { 'X-Shopify-Storefront-Access-Token': 'token' } // Ajax API (theme) fetch('/cart.js') fetch('/cart/add.js', { method: 'POST', body: ... }) fetch('/cart/change.js', { method: 'POST', body: ... })
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-13 | pass→pass | 24,389 | 8,648 | -65% | 1 | 1 | 0% | 2,440 | 6,787 | +178% | 0 | 0 | — |
case-11 | pass→pass | 6,760 | 5,157 | -24% | 1 | 1 | 0% | 1,379 | 6,099 | +342% | 0 | 0 | — |
case-12 | pass→pass | 6,465 | 8,823 | +36% | 1 | 1 | 0% | 1,257 | 5,969 | +375% | 0 | 0 | — |
case-01 | fail→pass | 11,732 | 9,134 | -22% | 1 | 1 | 0% | 2,417 | 6,878 | +185% | 0 | 0 | — |
case-02 | fail→pass | 8,184 | 9,781 | +20% | 1 | 1 | 0% | 1,654 | 7,170 | +333% | 0 | 0 | — |
case-03 | pass→pass | 9,434 | 9,822 | +4% | 1 | 1 | 0% | 1,732 | 6,903 | +299% | 0 | 0 | — |
case-04 | pass→pass | 13,036 | 10,454 | -20% | 1 | 1 | 0% | 2,505 | 6,940 | +177% | 0 | 0 | — |
case-10 | fail→pass | 12,247 | 5,773 | -53% | 1 | 1 | 0% | 673 | 6,159 | +815% | 0 | 0 | — |
case-05 | pass→pass | 9,911 | 9,560 | -4% | 1 | 1 | 0% | 1,994 | 6,938 | +248% | 0 | 0 | — |
case-06 | fail→pass | 17,894 | 13,510 | -24% | 1 | 1 | 0% | 3,268 | 7,814 | +139% | 0 | 0 | — |
case-07 | pass→pass | 7,380 | 10,248 | +39% | 1 | 1 | 0% | 1,413 | 7,090 | +402% | 0 | 0 | — |
case-08 | pass→pass | 11,223 | 11,189 | -0% | 1 | 1 | 0% | 2,041 | 7,265 | +256% | 0 | 0 | — |
case-09 | pass→pass | 15,057 | 17,656 | +17% | 1 | 1 | 0% | 2,920 | 8,751 | +200% | 0 | 0 | — |
case-14 | pass→pass | 5,752 | 6,481 | +13% | 1 | 1 | 0% | 1,250 | 6,450 | +416% | 0 | 0 | — |
case-15 | pass→pass | 6,575 | 6,215 | -5% | 1 | 1 | 0% | 1,139 | 6,327 | +455% | 0 | 0 | — |
case-16 | pass→pass | 7,645 | 6,009 | -21% | 1 | 1 | 0% | 1,457 | 6,246 | +329% | 0 | 0 | — |
case-17 | fail→pass | 5,008 | 5,679 | +13% | 1 | 1 | 0% | 890 | 6,116 | +587% | 0 | 0 | — |
case-18 | pass→pass | 7,538 | 4,489 | -40% | 1 | 1 | 0% | 1,560 | 5,851 | +275% | 0 | 0 | — |
case-19 | pass→pass | 8,884 | 4,487 | -49% | 1 | 1 | 0% | 1,826 | 5,835 | +220% | 0 | 0 | — |
case-20 | pass→pass | 7,843 | 8,348 | +6% | 1 | 1 | 0% | 1,481 | 6,511 | +340% | 0 | 0 | — |
case-21 | pass→pass | 13,583 | 10,091 | -26% | 1 | 1 | 0% | 2,807 | 6,936 | +147% | 0 | 0 | — |
case-22 | pass→pass | 13,506 | 10,722 | -21% | 1 | 1 | 0% | 2,998 | 7,276 | +143% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +23 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.