Adaptateur REST API Shopify
Compatible Admin API
Dernière mise à jour : 5 September 2026
Une REST API pour STOAR compatible sans modification, qui reproduit l'Admin REST API 2024-01 de Shopify — mêmes chemins d'URL (/admin/api/2024-01/...), même syntaxe de paramètres de requête à plat, même structure d'enveloppe JSON ({ "order": {…} }, { "orders": [...] }), même en-tête d'authentification, même pagination par curseur Link: <…>; rel="next". Les bibliothèques clientes Shopify existantes (shopify_api Ruby gem, @shopify/shopify-api-node, ShopifySharp, python-shopify-api) dialoguent avec STOAR sans être modifiées.
L'adaptateur est la troisième implémentation du framework d'adaptateurs API extensible de STOAR (après Magento et WooCommerce). Il illustre une déclinaison d'enveloppe encore différente — des wrappers nommés au premier niveau et une pagination par curseur — aux côtés de Magento et WC, sur la même couche de données indépendante de tout éditeur.
Sommaire #
- Démarrage rapide
- Authentification
- Points de terminaison
- Paramètres de requête
- Pagination par curseur
- Correspondance des champs (Shopify ↔ STOAR)
- Traduction des statuts
- Structure de la réponse
- Exemple réel
- Structure des erreurs
- Limitation de débit
Démarrage rapide #
TOKEN="4|abcdef…" # Sanctum personal-access token with shopify:admin ability
# Discover shop currency / locale (real Shopify clients call this first)
curl -H "X-Shopify-Access-Token: $TOKEN" \
"https://app.stoar.ai/admin/api/2024-01/shop.json" | jq '.shop | {name, currency}'
# List the most recent 5 orders
curl -H "X-Shopify-Access-Token: $TOKEN" \
"https://app.stoar.ai/admin/api/2024-01/orders.json?limit=5" | jq '.orders[] | {id, financial_status, total_price}'
# Fetch a single order
curl -H "X-Shopify-Access-Token: $TOKEN" \
"https://app.stoar.ai/admin/api/2024-01/orders/10126.json"
# Cancel a pending order
curl -X POST -H "X-Shopify-Access-Token: $TOKEN" \
"https://app.stoar.ai/admin/api/2024-01/orders/123/cancel.json" \
-H "Content-Type: application/json" -d '{"reason":"customer"}'
L'authentification Bearer fonctionne également — -H "Authorization: Bearer $TOKEN" est interchangeable avec l'en-tête natif Shopify.
Authentification #
L'en-tête d'authentification canonique de Shopify est X-Shopify-Access-Token: <token>. L'adaptateur STOAR l'accepte, avec un repli Bearer :
| Méthode | Format | Cas d'usage |
|---|---|---|
| X-Shopify-Access-Token | X-Shopify-Access-Token: <sanctum-token> |
Par défaut pour toutes les bibliothèques clientes Shopify |
| Bearer (extension STOAR) | Authorization: Bearer <sanctum-token> |
Sanctum natif — interchangeable avec l'authentification de l'adaptateur Magento |
Les jetons sont émis depuis la page /manager/api-tokens de STOAR, par programme via AdminUser::createToken('label', ['shopify:admin']) ou — pour les flux internes à STOAR — via le point de terminaison Magento /rest/V1/integration/admin/token, l'ability shopify:admin étant ensuite accordée au même enregistrement.
Correspondance avec Sanctum. Le AccessTokenMiddleware s'exécute avant auth:sanctum et promeut X-Shopify-Access-Token en en-tête Authorization: Bearer …. Du point de vue de STOAR, toute requête Shopify est une requête Sanctum authentifiée classique dont la ligne de jeton porte l'ability shopify:admin. Un même jeton peut porter simultanément les abilities de tous les adaptateurs (shopify:admin + magento:admin + woocommerce:admin + bigcommerce:admin).
OAuth et la vérification HMAC (utilisés par les véritables applications Shopify) ne sont pas pris en charge.
Points de terminaison #
Tous les chemins sont relatifs à /admin/api/2024-01 (namespace de l'API Shopify repris tel quel, version 2024-01 incluse).
GET /admin/api/2024-01/shop.json
Renvoie les métadonnées de la boutique, synthétisées depuis la table Setting de STOAR. Les vrais clients Shopify appellent ce point de terminaison en premier pour connaître la devise et la locale de la boutique avant toute autre opération.
Réponse :
{
"shop": {
"id": 1,
"name": "STOAR",
"domain": "app.stoar.ai",
"myshopify_domain": "app.stoar.ai",
"email": "[email protected]",
"currency": "EUR",
"country_code": "DE",
"country_name": "Germany",
"primary_locale": "en",
"iana_timezone": "UTC",
"weight_unit": "kg",
"plan_name": "stoar",
"plan_display_name":"STOAR",
"shop_owner": "STOAR",
"money_format": "€ {{amount}}",
"checkout_api_supported": true,
"has_storefront": true,
"..."
}
}
GET /admin/api/2024-01/orders.json
Liste les commandes.
Authorization : shopify:admin.
Query : voir Paramètres de requête.
Réponse (200) :
HTTP/1.1 200 OK
Link: <…?page_info=eyJwIjoyLCJzIjo1LCJmIjoiZjE…&limit=5>; rel="next"
Content-Type: application/json
{
"orders": [
{ /* order object — see Response shape */ }
]
}
L'en-tête Link pilote la pagination (pas de compteur ?page=). Voir Pagination par curseur.
GET /admin/api/2024-01/orders/{id}.json
Commande unique par id numérique.
Réponse : enveloppe { "order": {...} }. Voir Structure de la réponse.
Erreurs :
{ "errors": "Not Found" }
POST /admin/api/2024-01/orders/{id}/cancel.json
Annule une commande en attente ou payée.
Authorization : shopify:admin.
Corps (facultatif) :
{ "reason": "customer" }
Codes de motif acceptés par Shopify : customer, inventory, fraud, declined, other. STOAR l'enregistre dans l'OrderStatusLog résultant mais n'en tire aucune autre conséquence.
Réponse (200) : la commande annulée dans l'enveloppe standard.
Erreurs :
404— id inconnu422— la commande est dans un état qui interdit l'annulation (p. ex.delivered,refunded)
GET /admin/api/2024-01/products.json
Liste les produits. Même enveloppe et même pagination par en-tête Link que pour les commandes.
GET /admin/api/2024-01/products/{id}.json
Produit unique. Les variantes sont incluses en ligne (pas seulement les ids, mais les objets variante complets), conformément au contrat exact de Shopify.
Réponse : { "product": {...} }. Voir Structure de la réponse.
Paramètres de requête #
Les points de terminaison de liste REST de Shopify utilisent des paramètres de requête à plat — bien plus simples que le searchCriteria[…] imbriqué de Magento. Le parseur se trouve dans app/Api/Adapters/Shopify/Search/.
Paramètres communs (commandes et produits)
| Paramètre | Défaut | Rôle |
|---|---|---|
limit |
50 |
éléments par page (max. 250) |
page_info |
— | curseur base64 opaque — prend le pas sur tout autre filtre (voir Pagination par curseur) |
since_id |
— | uniquement les éléments dont id > since_id (alternative au curseur) |
ids |
— | liste d'ids en CSV (?ids=1,2,3) |
created_at_min / created_at_max |
— | ISO 8601 |
updated_at_min / updated_at_max |
— | ISO 8601 |
order |
created_at desc |
<field> <direction>. Direction : asc / desc |
Commandes uniquement
| Paramètre | Exemple | Effet |
|---|---|---|
status |
open, closed, cancelled, any |
cycle de vie global — traduit en plusieurs statuts Stoar |
financial_status |
paid, pending, refunded, voided, authorized |
traduit en un seul statut Stoar |
fulfillment_status |
fulfilled, partial, unfulfilled, any |
traduit en statut Stoar |
Produits uniquement
| Paramètre | Exemple | Effet |
|---|---|---|
status |
active, archived, draft |
traduit en active / inactive Stoar |
title |
widget |
sous-chaîne (LIKE) sur le name du produit |
handle |
widget-pro |
correspondance exacte sur slug |
vendor / product_type |
— | acceptés mais ignorés — Stoar n'a pas de champs équivalents |
Les paramètres inconnus sont ignorés. Les valeurs de filtre sans traduction sensée (p. ex. ?financial_status=foo) sont silencieusement écartées.
Pagination par curseur #
Shopify utilise des curseurs base64 opaques plutôt que des compteurs de page. STOAR reproduit le contrat :
- Le client envoie
?limit=N(pas de paramètrepage). - Le serveur renvoie les N premiers éléments et un en-tête
Link::Link: <…/orders.json?page_info=eyJwIjoyLCJzIjo1LCJmIjoiYWJjMTIzIn0&limit=5>; rel="next" - Le client suit l'URL
rel="next"telle quelle — il ne la construit jamais lui-même. - En suivant ce lien, le serveur décode
page_infopour obtenir(page=2, page_size=5, filter_hash=abc123)et renvoie la page suivante.
Le filter_hash est un MD5 stable calculé sur le jeu de filtres d'origine. Si un client tente de réutiliser un curseur sur un autre jeu de résultats (par exemple en passant de ?status=open à ?status=closed), le hash du curseur ne correspond plus et STOAR revient à une page 1 fraîche. Cela correspond au comportement du vrai Shopify, où un changement de paramètre invalide les curseurs.
L'en-tête Link peut contenir à la fois rel="next" et rel="previous" :
Link: <…?page_info=PREV>; rel="previous", <…?page_info=NEXT>; rel="next"
Correspondance des champs (Shopify ↔ STOAR) #
Commande
| Champ Shopify | Source STOAR | Remarques |
|---|---|---|
id |
id |
numérique |
admin_graphql_api_id |
id (formaté) |
gid://shopify/Order/{id} |
name |
id |
#{id} (Shopify affiche #1001 sur les commandes) |
number |
id |
numérique |
order_number |
id + 1000 |
Shopify démarre par défaut à 1000 |
email, contact_email |
customer_email |
|
phone |
— | toujours null (non stocké sur l'Order Stoar) |
currency, presentment_currency |
currency (en majuscules) |
eur → EUR |
financial_status |
status (traduit) |
voir Traduction des statuts |
fulfillment_status |
status (traduit) |
null, partial ou fulfilled |
status (cycle de vie) |
status (traduit) |
open, closed, cancelled |
total_price, current_total_price |
total_amount |
chaîne à 2 décimales |
total_price_set |
wrapper money_set autour de total_amount |
{shop_money, presentment_money} |
subtotal_price, total_line_items_price |
subtotal |
chaîne à 2 décimales + money_set |
total_tax, current_total_tax |
tax_amount |
chaîne à 2 décimales + money_set |
total_shipping_price_set |
wrapper money_set autour de shipping_amount |
|
total_discounts |
discount_amount |
chaîne à 2 décimales |
total_outstanding |
total_amount - sum(succeeded payments) |
montant dû |
total_paid |
non exposé au premier niveau (Shopify le calcule à partir des transactions) | disponible via current_total_price - total_outstanding |
gateway, payment_gateway_names[] |
premier OrderPayment.gateway non archivé |
|
created_at, updated_at, processed_at |
created_at, updated_at |
ISO 8601 |
cancelled_at |
updated_at si status=cancelled |
null sinon |
closed_at |
updated_at si status=delivered/refunded |
null sinon |
cancel_reason |
'other' si annulée |
null sinon |
customer |
modèle Customer embarqué |
null pour les invités |
billing_address |
colonne JSON billing_info |
structure à plat Shopify |
shipping_address |
colonne JSON shipping_info |
structure à plat Shopify |
line_items[] |
Order.items (avec variante + produit) |
voir ci-dessous |
discount_codes[] |
dérivé de coupon_code + discount_amount |
vide sans code promo |
tax_lines[] |
un élément lorsque tax_amount > 0 |
avec un taux de 0, Stoar ne stockant pas le taux au niveau commande |
shipping_lines[] |
dérivé de shipping_method + shipping_amount |
vide sans livraison |
refunds[] |
une entrée synthétique lorsque refunded_amount > 0 |
|
token |
lookup_token |
le jeton magic link de la commande — à garder secret |
order_status_url |
/checkout/success?order_id=…&token=… |
les clients l'utilisent pour le suivi de commande en libre-service |
tags |
"" |
non modélisé |
line_item de commande
| Champ Shopify | Source STOAR |
|---|---|
id |
OrderItem.id |
variant_id |
OrderItem.variant_id |
product_id |
OrderItem.product_id |
title |
OrderItem.name |
variant_title |
Variant.name (sauf si « Default ») |
name |
"{title} - {variant_title}" lorsque la variante a un nom |
sku |
SKU de la variante d'abord, à défaut SKU du produit |
quantity |
OrderItem.quantity |
price |
OrderItem.price (chaîne à 2 décimales) |
price_set |
wrapper money_set |
tax_lines[] |
un élément lorsque tax_amount > 0 |
vendor, properties[] |
toujours vide / null |
fulfillment_service |
'manual' |
fulfillment_status |
null |
gift_card, requires_shipping, taxable |
valeurs par défaut raisonnables |
Produit
| Champ Shopify | Source STOAR | Remarques |
|---|---|---|
id |
id |
|
admin_graphql_api_id |
gid://shopify/Product/{id} |
|
title |
name |
|
handle |
slug |
|
body_html |
description |
transmis tel quel (HTML ou texte brut) |
status |
status (traduit) |
active ↔ active ; inactive ↔ archived |
vendor, product_type |
toujours "" |
non modélisé dans Stoar |
published_at |
created_at si actif |
null si inactif/archivé |
tags |
"" |
non modélisé |
variants[] |
en ligne — objets variante complets | toujours au moins une (Default Title si Stoar n'en a aucune) |
options[] |
dérivé des clés JSON attributes des variantes (max. 3) |
chaque option a name, position, values[] |
images[] |
image_path + tableau gallery_paths |
position commence à 1 |
image |
première image (ou null) | |
template_suffix, published_scope |
null, 'web' |
codé en dur |
Variante de produit
| Champ Shopify | Source STOAR |
|---|---|
id |
Variant.id |
product_id |
Variant.product_id |
title |
Variant.name ("Default Title" lorsque le nom est "Default") |
option1, option2, option3 |
valeurs issues du JSON attributes dans un ordre stable (max. 3 axes) |
price |
Variant.price (chaîne à 2 décimales) |
sku |
Variant.sku |
inventory_quantity |
Variant.stock |
inventory_management |
'shopify' (toujours) |
inventory_policy |
'deny' (toujours) |
weight |
Variant.weight |
weight_unit |
'kg' |
grams |
weight * 1000 (arrondi) |
requires_shipping, taxable |
toujours true |
compare_at_price |
toujours null |
barcode |
toujours null |
Traduction des statuts #
Shopify décompose l'état d'une commande sur trois champs. Stoar les réunit en un seul. Le traducteur gère les deux sens.
Stoar → Shopify (rendu)
status Stoar |
financial_status |
fulfillment_status |
status (cycle de vie) |
|---|---|---|---|
pending |
pending |
null |
open |
paid |
paid |
null |
open |
processing |
paid |
partial |
open |
shipped |
paid |
fulfilled |
open |
delivered |
paid |
fulfilled |
closed |
cancelled |
voided |
null |
cancelled |
refunded |
refunded |
null |
closed |
Filtre Shopify → Stoar (analyse)
| Entrée Shopify | Correspondance Stoar |
|---|---|
?status=open |
pending, paid, processing, shipped |
?status=closed |
delivered, refunded |
?status=cancelled |
cancelled |
?status=any |
(aucun filtre) |
?financial_status=pending |
pending |
?financial_status=paid / =authorized |
paid |
?financial_status=voided |
cancelled |
?financial_status=refunded / =partially_refunded |
refunded |
?fulfillment_status=fulfilled |
shipped |
?fulfillment_status=partial |
processing |
?fulfillment_status=unfulfilled |
paid |
Statut produit
| Shopify | Stoar |
|---|---|
active |
active |
archived, draft |
inactive |
Stoar n'a pas de notion de draft — les produits Shopify archivés et brouillons sont tous deux mappés sur inactive côté Stoar. La sortie utilise archived.
Structure de la réponse #
Commande — exemple annoté
{
"order": {
"id": 12345,
"admin_graphql_api_id": "gid://shopify/Order/12345",
"name": "#12345",
"number": 12345,
"order_number": 13345,
"token": "abc123…",
"email": "[email protected]",
"contact_email": "[email protected]",
"currency": "EUR",
"presentment_currency": "EUR",
"financial_status": "paid",
"fulfillment_status": null,
"status": "open",
"gateway": "stripe",
"payment_gateway_names":["stripe"],
"total_price": "115.00",
"total_price_set": {
"shop_money": { "amount": "115.00", "currency_code": "EUR" },
"presentment_money": { "amount": "115.00", "currency_code": "EUR" }
},
"subtotal_price": "100.00",
"total_tax": "10.00",
"total_shipping_price_set": {
"shop_money": { "amount": "5.00", "currency_code": "EUR" },
"presentment_money": { "amount": "5.00", "currency_code": "EUR" }
},
"total_discounts": "0.00",
"total_outstanding": "0.00",
"total_tip_received": "0.00",
"created_at": "2026-04-10T09:00:00+00:00",
"updated_at": "2026-04-10T09:30:00+00:00",
"processed_at": "2026-04-10T09:00:00+00:00",
"cancelled_at": null,
"closed_at": null,
"cancel_reason": null,
"customer": {
"id": 45,
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe",
"verified_email": true,
"state": "enabled",
"currency": "EUR"
},
"billing_address": {
"first_name": "Jane",
"last_name": "Doe",
"name": "Jane Doe",
"address1": "1 Test St",
"address2": null,
"city": "Berlin",
"province": null,
"country": null,
"country_code": "DE",
"zip": "10115",
"phone": null
},
"shipping_address": { /* same shape as billing */ },
"line_items": [
{
"id": 987,
"variant_id": 12,
"product_id": 456,
"title": "Widget",
"variant_title":"Red",
"name": "Widget - Red",
"sku": "WID-RED",
"quantity": 2,
"price": "50.00",
"price_set": {"shop_money": {"amount": "50.00", "currency_code": "EUR"}, "presentment_money": {"amount": "50.00", "currency_code": "EUR"}},
"fulfillable_quantity": 2,
"fulfillment_service": "manual",
"fulfillment_status": null,
"requires_shipping": true,
"taxable": true,
"tax_lines": []
}
],
"shipping_lines": [
{
"id": 0,
"title": "Standard",
"code": "flat_rate",
"source": "shopify",
"price": "5.00",
"price_set": {"shop_money": {"amount": "5.00", "currency_code": "EUR"}, "presentment_money": {"amount": "5.00", "currency_code": "EUR"}},
"tax_lines": [],
"discount_allocations": []
}
],
"tax_lines": [],
"discount_codes": [],
"discount_applications":[],
"fulfillments": [],
"refunds": []
}
}
Produit — exemple abrégé
{
"product": {
"id": 789,
"admin_graphql_api_id":"gid://shopify/Product/789",
"title": "Widget",
"body_html": "<p>A great widget</p>",
"vendor": "",
"product_type": "",
"handle": "widget",
"status": "active",
"published_at": "2026-04-01T10:00:00+00:00",
"published_scope": "web",
"tags": "",
"variants": [
{
"id": 12,
"admin_graphql_api_id":"gid://shopify/ProductVariant/12",
"product_id": 789,
"title": "Red",
"price": "99.99",
"sku": "WID-RED",
"position": 1,
"inventory_policy": "deny",
"compare_at_price": null,
"fulfillment_service":"manual",
"inventory_management":"shopify",
"option1": "Red",
"option2": null,
"option3": null,
"weight": 0.5,
"weight_unit": "kg",
"grams": 500,
"inventory_quantity": 42
}
],
"options": [
{ "id": 0, "product_id": 789, "name": "Color", "position": 1, "values": ["Red", "Blue"] }
],
"images": [
{
"id": 0,
"admin_graphql_api_id": "gid://shopify/ProductImage/0",
"product_id": 789,
"position": 1,
"alt": null,
"src": "products/widget.webp",
"variant_ids":[]
}
],
"image": { /* same shape as images[0] */ }
}
}
Exemple réel #
GET /admin/api/2024-01/orders/10126.json en production (transformation en direct, au format Shopify, de la commande déjà présentée pour Magento et WooCommerce). Données personnelles anonymisées. La réponse s'intègre sans modification au gem Ruby shopify_api et à @shopify/shopify-api-node.
{
"order": {
"id": 10126,
"admin_graphql_api_id": "gid://shopify/Order/10126",
"name": "#10126",
"number": 10126,
"order_number": 11126,
"token": "REDACTED-FOR-DOCS",
"email": "[email protected]",
"contact_email": "[email protected]",
"currency": "USD",
"presentment_currency": "USD",
"financial_status": "paid",
"fulfillment_status": null,
"status": "open",
"gateway": "payid",
"payment_gateway_names":["payid"],
"total_price": "936.98",
"subtotal_price": "936.98",
"total_tax": "0.00",
"total_outstanding": "936.98",
"total_price_set": { "shop_money": { "amount": "936.98", "currency_code": "USD" }, "presentment_money": { "amount": "936.98", "currency_code": "USD" } },
"created_at": "2025-06-03T04:56:43+00:00",
"updated_at": "2025-06-03T04:56:43+00:00",
"processed_at": "2025-06-03T04:56:43+00:00",
"cancelled_at": null,
"closed_at": null,
"billing_address": {
"first_name": "Jane",
"last_name": "Doe",
"name": "Jane Doe",
"address1": "1 Example Street",
"address2": null,
"city": "Phoenix",
"province": "AZ",
"country": null,
"country_code": "US",
"zip": "85001",
"phone": "+1-555-0100"
},
"shipping_address": { /* same shape */ },
"customer": {
"id": 5794,
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe",
"state": "enabled",
"verified_email": true,
"currency": "USD"
},
"line_items": [
{
"id": 30219,
"variant_id": 95589,
"product_id": 112238,
"title": "Reloop Terminal Mix 8",
"variant_title":null,
"name": "Reloop Terminal Mix 8",
"sku": "RELOOP_TERMINALMIX8_025-DEF",
"quantity": 3,
"price": "299.00",
"fulfillable_quantity": 3,
"fulfillment_service": "manual",
"fulfillment_status": null,
"requires_shipping": true,
"taxable": false,
"tax_lines": []
},
{
"id": 30220,
"variant_id": 33857,
"product_id": 51706,
"title": "Premium Skateboard Socks",
"variant_title":null,
"name": "Premium Skateboard Socks",
"sku": "SK8-SOCK-027-DEF",
"quantity": 2,
"price": "19.99",
"fulfillable_quantity": 2,
"fulfillment_service": "manual",
"fulfillment_status": null,
"requires_shipping": true,
"taxable": false,
"tax_lines": []
}
],
"shipping_lines": [{
"id": 0,
"title": "Free Shipping",
"code": "flat_rate",
"source": "shopify",
"price": "0.00"
}],
"tax_lines": [],
"discount_codes": [],
"discount_applications":[],
"fulfillments": [],
"refunds": []
}
}
Points d'attention pour les intégrateurs
| Champ | Observé | Pourquoi |
|---|---|---|
total_outstanding |
"936.98" pour une commande payée |
Calculé depuis le journal d'audit OrderPayment ; les commandes antérieures à ce journal affichent leur total complet comme dû. |
gateway |
"payid" |
Il s'agit de la clé de passerelle Stoar — pas d'un nom canonique Shopify comme shopify_payments. |
customer_locale, device_id, app_id |
toujours null |
Non modélisé sur l'Order Stoar. |
tax_lines |
[] même lorsque total_tax > 0 |
Stoar suit la taxe au niveau de la commande uniquement, pas par juridiction ; nous synthétiserions une seule tax_line si nécessaire. |
fulfillments, discount_applications |
toujours [] |
Pas de suivi d'expédition ; les codes promo sont modélisés uniquement sous forme de discount_codes à plat. |
tags |
toujours "" |
Pas de système d'étiquettes dans Stoar. |
token |
jeton magic link propre à la commande | Ne jamais l'exposer dans du code client ni dans des journaux — sa seule possession suffit à consulter la commande sans authentification. |
order_number |
id + 1000 |
Cosmétique — Shopify démarre par défaut toutes les boutiques à 1000. |
Structure des erreurs #
Shopify utilise une enveloppe souple :
{ "errors": "Not Found" } // string
{ "errors": { "title": ["can't be blank"] } } // map (validation)
| HTTP | Déclencheur | Corps |
|---|---|---|
| 401 | jeton manquant ou invalide | { "errors": "[API] Invalid API key or access token …" } |
| 403 | jeton avec la mauvaise ability | { "errors": "Forbidden" } |
| 404 | id de ressource inconnu | { "errors": "Not Found" } |
| 422 | précondition non satisfaite (annulation impossible, etc.) | { "errors": "Order N cannot be cancelled in status 'delivered'." } |
| 429 | limite de débit dépassée | { "errors": "Exceeded 2 calls per second for api client. …" } + Retry-After: 2 |
| 500 | erreur serveur inattendue | { "errors": "Internal Server Error" } |
Les erreurs de validation (seul cancel.json accepte un corps) utilisent la structure de map indexée par champ.
Limitation de débit #
Le même throttle api-rest indexé sur le jeton Sanctum qui protège Magento et WooCommerce protège les routes Shopify. Configure les deux globalement sur /manager/api-settings. En cas de déclenchement, la réponse utilise l'enveloppe d'erreur de Shopify :
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Content-Type: application/json
{ "errors": "Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service." }
La valeur de l'en-tête Retry-After correspond au comportement documenté par Shopify. Le vrai Shopify emploie un algorithme de seau percé ; STOAR utilise une fenêtre glissante à la minute — assez proche pour la compatibilité avec les bibliothèques clientes.
Un seul jeton Sanctum utilisé sur les quatre adaptateurs (Magento + WC + Shopify + BC) partage un seul seau — voir la section Limitation de débit de la documentation Magento pour la présentation complète de l'interface de configuration.
Voir aussi #
app/Api/Adapters/Shopify/Support/StatusTranslator.php— correspondance de vocabulaire bidirectionnelleapp/Api/Adapters/Shopify/Support/PageInfoCursor.php— encodage du curseur base64 opaque- Référence de l'Admin REST API Shopify — la spécification amont que reproduit cet adaptateur