Перейти к основному содержимому

Shopify

Shopify Admin & Storefront GraphQL API через curl. Товары, заказы, клиенты, остатки, метаполя.

Метаданные навыка​

ИсточникОпционально — установка: vibeos skills install official/productivity/shopify
Путьoptional-skills/productivity/shopify
Версия1.0.0
Авторcommunity
ЛицензияMIT
Платформыlinux, macos, windows
ТегиShopify, E-commerce, Commerce, API, GraphQL
Связанные навыкиairtable, xurl

Справочник: полный SKILL.md​

к сведению

Ниже приведено полное описание навыка, которое VibeOS загружает при его активации. Агент видит эти инструкции, когда навык активен.

Shopify — Admin & Storefront GraphQL API

Работайте с магазинами Shopify напрямую через curl: список товаров, управление остатками, получение заказов, обновление клиентов, чтение метаполей. Без SDK, без фреймворка — только GraphQL-эндпоинт и токен доступа кастомного приложения.

REST Admin API устарел с апреля 2024 года и получает только исправления безопасности. Используйте GraphQL Admin для всей административной работы. Используйте Storefront GraphQL для публичных запросов только на чтение (товары, коллекции, корзина).

Предварительные требования​

  1. В админке Shopify: Настройки → Приложения и каналы продаж → Разработка приложений → Создать приложение.
  2. Нажмите Настроить области Admin API, выберите нужные (примеры ниже), сохраните.
  3. Установите приложение → токен доступа Admin API отображается ОДИН РАЗ. Скопируйте его немедленно — Shopify больше его не покажет. Токены начинаются с shpat_.
  4. Сохраните в ${VIBEOS_HOME:-~/.vibeos}/.env:
    SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxx
    SHOPIFY_STORE_DOMAIN=my-store.myshopify.com
    SHOPIFY_API_VERSION=2026-01

Внимание: С 1 января 2026 года новые «устаревшие кастомные приложения», созданные в админке 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 (30 последних без read_all_orders)
  • Клиенты: 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}'
# последующие вызовы: передавайте предыдущий 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

Создать товар с одним вариантом​

shop_gql '
mutation($input: ProductCreateInput!) {
productCreate(product: $input) {
product { id handle }
userErrors { field message }
}
}' '{"input":{"title":"Test Hoodie","status":"DRAFT","vendor":"VibeOS","productType":"Apparel","tags":["test"]}}'

В последних версиях у вариантов есть собственные мутации:

# Добавить варианты после создания товара
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"}]}'

Заказы​

Список последних заказов (по умолчанию последние 30 без read_all_orders)​

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.

Получить один заказ с адресом доставки​

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

Клиенты​

# Поиск
shop_gql '
{
customers(first: 10, query: "email:*@example.com") {
edges { node { id email displayName numberOfOrders amountSpent { amount currencyCode } } }
}
}'

# Создание
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"]}}'

Остатки​

Остатки хранятся в элементах остатков, привязанных к вариантам, количества отслеживаются по местоположениям.

# Получить остатки для варианта по всем местоположениям
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}]}}'

Метаполя и Метаобъекты​

Метаполя прикрепляют пользовательские данные к ресурсам (товары, клиенты, заказы, магазин).

# Чтение
shop_gql '
query($id: ID!) {
product(id: $id) {
metafields(first: 10, namespace: "custom") {
edges { node { key type value } }
}
}
}' '{"id":"gid://shopify/Product/..."}'

# Запись (работает для любого типа владельца)
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 (публичный, только чтение)​

Другой эндпоинт, другой токен, используется для клиентских приложений / headless-конфигураций в стиле Hydrogen. Заголовки отличаются:

  • Эндпоинт: https://$SHOPIFY_STORE_DOMAIN/api/$SHOPIFY_API_VERSION/graphql.json
  • Заголовок аутентификации (публичный): X-Shopify-Storefront-Access-Token: <публичный токен> — можно встраивать в браузер
  • Заголовок аутентификации (приватный): Shopify-Storefront-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. Запустить массовый запрос
shop_gql '
mutation {
bulkOperationRunQuery(query: """
{ products { edges { node { id title handle variants { edges { node { sku price } } } } } } }
""") {
bulkOperation { id status }
userErrors { field message }
}
}'

# 2. Проверить статус
shop_gql '{ currentBulkOperation { id status errorCode objectCount fileSize url partialDataUrl } }'

# 3. Когда статус=COMPLETED, скачать JSONL-файл
curl -sS "$URL" > products.jsonl

Каждая строка JSONL — это узел, а вложенные связи выводятся отдельными строками с __parentId. При необходимости соберите на стороне клиента.

Вебхуки​

Подпишитесь на события, чтобы не опрашивать:

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"}}'

Проверьте HMAC входящего вебхука, используя секрет клиента приложения (не токен доступа):

echo -n "$REQUEST_BODY" | openssl dgst -sha256 -hmac "$APP_SECRET" -binary | base64
# Сравните с заголовком X-Shopify-Hmac-Sha256

Подводные камни​

  • REST-эндпоинты всё ещё существуют, но заморожены. Не пишите новые интеграции против /admin/api/.../products.json. Используйте GraphQL.
  • Проверка формата токена. Административные токены начинаются с shpat_. Публичные токены Storefront — с shpua_. Если у вас один токен и неправильный заголовок, каждый запрос будет возвращать 401 без полезного тела ошибки.
  • 403 с валидным токеном = отсутствует область. Shopify возвращает {"errors":[{"message":"Access denied for ..."}]}. Перенастройте области Admin API в приложении, затем переустановите, чтобы сгенерировать новый токен.
  • Пустой userErrors != успех. Также проверьте, что data.<mutation>.<resource> не равен null. Некоторые сбои не заполняют ни то, ни другое — проверьте весь ответ.
  • GID против числового ID. Устаревший REST выдавал числовые ID; GraphQL требует полные GID-строки. Для преобразования: gid://shopify/Product/<числовой>.
  • Неожиданное ограничение скорости. Один запрос products(first: 250) с глубокой вложенностью может стоить 1000+ баллов и мгновенно затормозить магазин на стандартном тарифе. Начинайте с малого, читайте extensions.cost, корректируйте.
  • Порядок пагинации. products(first: N, reverse: true) сортирует по id DESC, а не по created_at. Используйте sortKey: CREATED_AT, reverse: true для сортировки «сначала новые».
  • read_all_orders для исторических данных. Без него orders(...) молча ограничивается 60-дневным окном. Вы не получите ошибку, просто меньше результатов, чем ожидалось. Для продавцов Shopify Plus с большим количеством заказов запросите эту область через настройки защищённых данных приложения.
  • Валюты — это строки. Суммы возвращаются как "49.00", а не 49.0. Не применяйте jq tonumber вслепую, если вам важны нули после запятой.
  • Поля Money для мультивалютности имеют shopMoney (валюта магазина) И presentmentMoney (валюта клиента). Выберите одну последовательно.

Безопасность​

Мутации в Shopify реальны — они создают товары, возвращают платежи, отменяют заказы, отправляют выполнения. Перед запуском productDelete, orderCancel, refundCreate или любой массовой мутации: чётко укажите, что это за изменение, в каком магазине, и подтвердите с пользователем. Не существует промежуточной копии производственных данных, если у пользователя нет отдельного тестового магазина.