home / skills / microck / ordinary-claude-skills / shopify-api

shopify-api skill

/skills_all/shopify-api

This skill guides Shopify API integrations across GraphQL, REST, Storefront, and OAuth to fetch data and automate store operations.

This is most likely a fork of the shopify-api skill from henkisdabro
npx playbooks add skill microck/ordinary-claude-skills --skill shopify-api

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
17.0 KB
---
name: shopify-api
description: 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.
---

# Shopify API Integration

Expert guidance for all Shopify APIs including GraphQL Admin API, REST Admin API, Storefront API, Ajax API, authentication, and webhooks.

## When to Use This Skill

Invoke this skill when:

- Making GraphQL or REST API calls to Shopify
- Implementing OAuth 2.0 authentication for apps
- Fetching product, collection, order, or customer data programmatically
- Using Storefront API for headless commerce
- Implementing Ajax cart operations in themes
- Setting up webhooks for event handling
- Working with API version 2025-10
- Handling rate limiting and API errors
- Building custom integrations with Shopify
- Using Shopify Admin API from Node.js, Python, or other languages

## Core Capabilities

### 1. GraphQL Admin API

Modern API for Shopify Admin operations with efficient data fetching.

**Endpoint:**
```
POST https://{store}.myshopify.com/admin/api/2025-10/graphql.json
```

**Headers:**
```javascript
{
  'X-Shopify-Access-Token': 'shpat_...',
  'Content-Type': 'application/json'
}
```

**Basic Query:**
```graphql
query 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:**
```javascript
async 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:
```graphql
mutation CreateProduct($input: ProductInput!) {
  productCreate(input: $input) {
    product {
      id
      title
      handle
    }
    userErrors {
      field
      message
    }
  }
}
```

Update product:
```graphql
mutation UpdateProduct($input: ProductInput!) {
  productUpdate(input: $input) {
    product {
      id
      title
      status
    }
    userErrors {
      field
      message
    }
  }
}
```

Set metafield:
```graphql
mutation SetMetafield($input: MetafieldInput!) {
  metafieldSet(input: $input) {
    metafield {
      id
      namespace
      key
      value
      type
    }
    userErrors {
      field
      message
    }
  }
}
```

### 2. REST Admin API

Traditional REST API for Shopify Admin operations.

**Base URL:**
```
https://{store}.myshopify.com/admin/api/2025-10/
```

**Authentication:**
```javascript
headers: {
  'X-Shopify-Access-Token': 'shpat_...'
}
```

**Common Endpoints:**

Get products:
```javascript
GET /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:
```javascript
GET /admin/api/2025-10/products/{product_id}.json
```

Create product:
```javascript
POST /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:
```javascript
PUT /admin/api/2025-10/products/{product_id}.json

// Body
{
  "product": {
    "id": 123456789,
    "title": "Updated Title"
  }
}
```

Get orders:
```javascript
GET /admin/api/2025-10/orders.json?status=any&limit=50
```

Get customers:
```javascript
GET /admin/api/2025-10/customers.json?limit=50
```

### 3. OAuth 2.0 Authentication

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:**
```javascript
const 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**
```javascript
POST 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 };
}
```

### 4. Rate Limiting

GraphQL uses points-based rate limiting.

**Rate Limits:**
- 50 cost points per second maximum
- Bucket refills at 50 points/second
- Each query has a calculated cost

**Check Rate Limit:**
```javascript
const 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:**
```javascript
async 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));
}
```

### 5. Storefront API

Public API for headless/custom storefronts.

**Endpoint:**
```
POST https://{store}.myshopify.com/api/2025-10/graphql.json
```

**Headers (Public Access):**
```javascript
{
  'Content-Type': 'application/json',
  'X-Shopify-Storefront-Access-Token': '{public_token}' // Optional for public stores
}
```

**Query Products:**
```graphql
query 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:
```graphql
mutation 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:
```graphql
mutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!) {
  cartLinesAdd(cartId: $cartId, lines: $lines) {
    cart {
      id
      lines(first: 10) {
        edges {
          node {
            id
            quantity
          }
        }
      }
    }
  }
}
```

### 6. Ajax API (Theme-Only)

JavaScript API for cart operations in themes.

**Get Cart:**
```javascript
fetch('/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:**
```javascript
fetch('/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:**
```javascript
fetch('/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:**
```javascript
fetch('/cart/clear.js', { method: 'POST' })
  .then(response => response.json())
  .then(cart => console.log('Cart cleared'));
```

**Update Cart Attributes:**
```javascript
fetch('/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'));
```

### 7. Webhooks

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):**
```graphql
mutation 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):**
```javascript
app.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);
});
```

## Common Patterns

### Pagination (GraphQL)

```javascript
async 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;
}
```

### Error Handling

```javascript
async 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;
  }
}
```

## Best Practices

1. **Always check API version** - Use latest stable (2025-10)
2. **Implement rate limit handling** - Use exponential backoff
3. **Verify webhook HMAC** - Security critical
4. **Use GraphQL over REST** when possible - More efficient
5. **Request only needed fields** - Reduce response size
6. **Handle errors gracefully** - Check `errors` and `userErrors`
7. **Store access tokens securely** - Never expose in client code
8. **Use minimum necessary scopes** - Security best practice
9. **Implement retry logic** - Handle transient failures
10. **Respond to webhooks quickly** - Within 5 seconds

## Detailed References

- **[references/graphql-queries.md](references/graphql-queries.md)** - Complete query examples
- **[references/rest-endpoints.md](references/rest-endpoints.md)** - All REST endpoints
- **[references/webhook-payloads.md](references/webhook-payloads.md)** - Webhook payload structures

## Integration with Other Skills

- **shopify-app-dev** - Use when building custom Shopify apps
- **shopify-liquid** - Use when displaying API data in theme templates
- **shopify-debugging** - Use when troubleshooting API errors
- **shopify-performance** - Use when optimizing API request patterns

## Quick Reference

```javascript
// 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: ... })
```

Overview

This skill is a complete integration guide for Shopify APIs covering GraphQL Admin, REST Admin, Storefront, Ajax, OAuth authentication, rate limiting, and webhooks. It focuses on practical examples, common patterns, and API version 2025-10 to help you build reliable app integrations and headless storefronts. Implementations assume use of fetch or axios for JavaScript and include patterns applicable to Python and other languages.

How this skill works

The guide explains endpoints, required headers, OAuth flows, and example queries/mutations for common Admin and Storefront tasks. It details Ajax endpoints for theme cart operations and webhook registration and verification steps for secure event handling. It also shows rate-limit detection, retry/backoff strategies, and pagination patterns for robust data fetching across APIs.

When to use it

  • Building or authenticating a Shopify app (OAuth 2.0).
  • Fetching or updating products, orders, customers, and metafields programmatically.
  • Implementing a headless storefront with the Storefront API and cart mutations.
  • Adding theme cart behavior using the Ajax API.
  • Registering and verifying webhooks for event-driven workflows.
  • Handling rate limits and implementing retry/backoff for GraphQL and REST.

Best practices

  • Always verify HMAC signatures for OAuth callbacks and incoming webhooks to prevent tampering.
  • Use GraphQL for efficient, shapeable queries and REST for simple, resource-based operations when appropriate.
  • Respect API versioning (2025-10) and test upgrades before switching versions in production.
  • Monitor GraphQL cost headers and implement exponential backoff when approaching limits or receiving 429 responses.
  • Respond to webhooks quickly (within 5 seconds) and offload heavy processing to background jobs.
  • Use cursor-based pagination for GraphQL queries to reliably fetch large datasets.

Example use cases

  • Exchange an OAuth code for a permanent access token to install a custom app and call Admin APIs.
  • Fetch paginated product lists via GraphQL, then create or update product metafields.
  • Build a headless checkout flow using Storefront API cartCreate and cartLinesAdd mutations.
  • Implement theme cart add/update/remove with the Ajax API endpoints (/cart.js, /cart/add.js).
  • Register an orders/create webhook and verify its HMAC, then process orders asynchronously.

FAQ

Which API should I use: GraphQL Admin or REST Admin?

Use GraphQL for flexible, efficient queries and batched mutations. Use REST for simple single-resource calls if you prefer conventional endpoints or need endpoints not yet supported in GraphQL.

How do I avoid hitting rate limits?

Monitor GraphQL cost headers (X-Shopify-GraphQL-Admin-Api-Call-Limit), slow requests when usage is high, and implement exponential backoff and Retry-After handling for 429 responses.