Skip to main content
Query Shopify Admin/Storefront GraphQL APIs via curl.

Skill metadata

Reference: full SKILL.md

The following is the complete skill definition that Mibyan loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.

Shopify — Admin & Storefront GraphQL APIs

Work with Shopify stores directly through curl: list products, manage inventory, pull orders, update customers, read metafields. No SDK, no app framework — just the GraphQL endpoint and a custom-app access token. The REST Admin API is legacy since 2024-04 and only receives security fixes. Use GraphQL Admin for all admin work. Use Storefront GraphQL for read-only customer-facing queries (products, collections, cart).

Prerequisites

  1. In Shopify admin: Settings → Apps and sales channels → Develop apps → Create an app.
  2. Click Configure Admin API scopes, select what you need (examples below), save.
  3. Install app → the Admin API access token appears ONCE. Copy it immediately — Shopify will never show it again. Tokens start with shpat_.
  4. Save to ${mibyan_HOME:-~/.mibyan}/.env:
Heads up: As of January 1, 2026, new “legacy custom apps” created in the Shopify admin are gone. New setups should use the Dev Dashboard (shopify.dev/docs/apps/build/dev-dashboard). Existing admin-created apps keep working. If the user’s shop has no existing custom app and it’s after 2026-01-01, direct them to Dev Dashboard instead of the admin flow.
Common scopes by task:
  • Products / collections: read_products, write_products
  • Inventory: read_inventory, write_inventory, read_locations
  • Orders: read_orders, write_orders (30 most recent without read_all_orders)
  • Customers: read_customers, write_customers
  • Draft orders: read_draft_orders, write_draft_orders
  • Fulfillments: read_fulfillments, write_fulfillments
  • Metafields / metaobjects: covered by the matching resource scopes

API Basics

  • Endpoint: https://$SHOPIFY_STORE_DOMAIN/admin/api/$SHOPIFY_API_VERSION/graphql.json
  • Auth header: X-Shopify-Access-Token: $SHOPIFY_ACCESS_TOKEN (NOT Authorization: Bearer)
  • Method: always POST, always Content-Type: application/json, body is {"query": "...", "variables": {...}}
  • HTTP 200 does not mean success. GraphQL returns errors in a top-level errors array and per-field userErrors. Always check both.
  • IDs are GID strings: gid://shopify/Product/10079467700516, gid://shopify/Variant/..., gid://shopify/Order/.... Pass these verbatim — don’t strip the prefix.
  • Rate limit: calculated via query cost (leaky bucket). Each response has extensions.cost with requestedQueryCost, actualQueryCost, throttleStatus.{currentlyAvailable, maximumAvailable, restoreRate}. Back off when currentlyAvailable drops below your next query’s cost. Standard shops = 100 points bucket, 50/s restore; Plus = 1000/100.
Base curl pattern (reusable):
Pipe through jq for readable output. -sS keeps errors visible but hides the progress bar.

Discovery

Shop info + current API version

List all supported API versions

Products

Search products (first 20 matching query)

Query syntax supports title:, sku:, vendor:, product_type:, status:active, tag:, created_at:>2025-01-01. Full grammar: https://shopify.dev/docs/api/usage/search-syntax

Paginate products (cursor)

Get a product with variants + metafields

Create a product with one variant

Variants now have their own mutations in recent versions:

Update price / SKU

Orders

List recent orders (last 30 by default without read_all_orders)

Useful order query filters: financial_status:paid|pending|refunded, fulfillment_status:unfulfilled|fulfilled, created_at:>2025-01-01, tag:gift, email:foo@example.com.

Fetch a single order with shipping address

Customers

Inventory

Inventory lives on inventory items tied to variants, quantities tracked per location.
Adjust stock (delta) — uses inventoryAdjustQuantities:
Set absolute stock (not delta) — inventorySetQuantities:

Metafields & Metaobjects

Metafields attach custom data to resources (products, customers, orders, shop).

Storefront API (public read-only)

Different endpoint, different token, used for customer-facing apps/hydrogen-style headless setups. Headers differ:
  • Endpoint: https://$SHOPIFY_STORE_DOMAIN/api/$SHOPIFY_API_VERSION/graphql.json
  • Auth header (public): X-Shopify-Storefront-Access-Token: <public token> — embeddable in browser
  • Auth header (private): Shopify-Storefront-Private-Token: <private token> — server-only

Bulk Operations

For dumps larger than rate limits allow (full product catalog, all orders for a year):
Each JSONL line is a node, and nested connections are emitted as separate lines with __parentId. Reassemble client-side if needed.

Webhooks

Subscribe to events so you don’t have to poll:
Verify incoming webhook HMAC using the app’s client secret (not the access token):

Pitfalls

  • REST endpoints still exist but are frozen. Don’t write new integrations against /admin/api/.../products.json. Use GraphQL.
  • Token format check. Admin tokens start with shpat_. Storefront public tokens with shpua_. If you have one and the wrong header, every request returns 401 without a useful error body.
  • 403 with a valid token = missing scope. Shopify returns {"errors":[{"message":"Access denied for ..."}]}. Re-configure Admin API scopes on the app, then reinstall to regenerate the token.
  • userErrors is empty != success. Also check data.<mutation>.<resource> is non-null. Some failures populate neither — inspect the whole response.
  • GID vs numeric ID. Legacy REST gave numeric IDs; GraphQL wants full GID strings. To convert: gid://shopify/Product/<numeric>.
  • Rate limit surprise. A single products(first: 250) with deep nesting can cost 1000+ points and throttle immediately on a standard-plan shop. Start narrow, read extensions.cost, adjust.
  • Pagination order. products(first: N, reverse: true) sorts by id DESC, not created_at. Use sortKey: CREATED_AT, reverse: true for “newest first.”
  • read_all_orders for historical data. Without it, orders(...) silently caps at the 60-day window. You won’t get an error, just fewer results than expected. For Shopify Plus merchants with many orders, request this scope via the app’s protected-data settings.
  • Currencies are strings. Amounts come back as "49.00" not 49.0. Don’t jq tonumber blindly if you care about zero-padding.
  • Multi-currency Money fields have shopMoney (store’s currency) AND presentmentMoney (customer’s). Pick one consistently.

Safety

Mutations in Shopify are real — they create products, charge refunds, cancel orders, ship fulfillments. Before running productDelete, orderCancel, refundCreate, or any bulk mutation: state clearly what the change is, on which shop, and confirm with the user. There is no staging clone of production data unless the user has a separate dev store.