Pular para o conteúdo
Documentação API Reference FAQ Changelog Status
Otimizações API Magento 2 Adobe Commerce

Endpoints de API para integrações que tenham mais volume de alteração de preços e estoques (com MSI)

PDF

Endpoints recomendados para ERPs integrarem preço e estoque em lojas Magento 2 com Multi-Source Inventory (MSI), com batch, delta e múltiplos sources.

S Sidenei Steinbach 22/05/2026 Atualizado 22/05/2026 10 min de leitura

Orientações de Integração — API Magento 2

Leitura e atualização de Preços e Estoque (lojas com MSI)

Documento de referência para integrações ERP ↔ Magento 2 em lojas que operam com Multi-Source Inventory (MSI) — múltiplos depósitos/fontes, com estoque vendável calculado por stock MSI. Cobre tanto os endpoints customizados de leitura dos módulos MageShop_PriceApi e MageShop_StockApi quanto os endpoints nativos de escrita do Magento.


1. Contexto

Em integrações ERP ↔ Magento, é comum observar volume excessivo de requisições nos endpoints de atualização de estoque e preços. Quando isso ocorre, a aplicação sofre com:

  • Concorrência elevada no PHP-FPM e contenção em locks de tabela.
  • Reindexação MSI acionada repetidamente.
  • Aumento de timeouts/5xx e degradação para o cliente final da loja.

Este documento orienta o time de integração sobre:

  1. Como ler preços e estoque em massa de forma eficiente (módulos MageShop).
  2. Como escrever preços e estoque MSI com o menor custo possível (Magento core).
  3. Boas práticas de paralelismo, delta e retry.

ℹ️ As orientações assumem que a loja utiliza MSI (módulo Magento_Inventory habilitado). Endpoints específicos de estoque legacy (sem MSI) são mencionados como alternativa, mas não são o caminho recomendado.


2. Convenções e Autenticação

2.1. Convenções

  • Base URL: https://<dominio>/rest/<store_code>store_code é o código da loja (ex.: default). Operações globais podem ser chamadas em /rest/V1/....
  • Verbos: GET para leitura (módulos MageShop), POST para escrita (Magento nativo).
  • Content-Type: application/json em request e response, UTF-8.
  • Numéricos retornados como string — comportamento padrão do Magento. O cliente deve converter (parseFloat / Decimal) conforme necessário.

2.2. Autenticação

ACLs exigidas:

  • Magento_Catalog::products para preços e leitura de estoque.
  • Magento_InventoryApi::source_items para escrita de estoque MSI.

Token de admin (uso interno/scripts)

TOKEN=$(curl -s -X POST https://loja.com/rest/V1/integration/admin/token \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"senha"}' | tr -d '"')

curl -H "Authorization: Bearer $TOKEN" https://loja.com/rest/V1/product-prices

Token de integration (recomendado para ERPs/terceiros)

  1. Admin → System → Extensions → Integrations → Add New Integration.
  2. Em API → Resource Access selecione Catalog → Inventory → Products.
  3. Salve, ative e copie o Access Token gerado.
curl -H "Authorization: Bearer <access_token>" https://loja.com/rest/V1/product-prices

Tokens de integration não expiram automaticamente — revogue pelo admin quando deixar de usar.


3. Leitura para conferência (módulos MageShop)

Os módulos MageShop_PriceApi e MageShop_StockApi expõem endpoints customizados de extração em massa. São otimizados para conferência e reconciliação de catálogo, sem o custo do endpoint genérico /V1/products.

3.1. Paginação

Paginação é opt-in. Se page_size não for enviado, a resposta traz todos os registros (legado).

Parâmetro Tipo Default Máximo (price/legacy) Máximo (msi)
page_size int 10.000 5.000
current_page int 1
  • Valores acima do máximo são clampeados silenciosamente.
  • A resposta é sempre um array JSON; não há total_count. Para detectar fim das páginas, itere até receber [].

Tamanhos recomendados por endpoint

Endpoint page_size sugerido Por quê
/V1/product-prices 1000 Linha pequena (4 campos).
/V1/product-stocks/legacy 1000 Linha pequena.
/V1/product-stocks/msi 500 Linha carrega array sources[] aninhado, payload 3–10× maior.

3.2. GET /V1/product-prices (MageShop_PriceApi)

Lista preços base (price) de produtos simples, expandidos por store_id.

Parâmetros (query string)

Nome Tipo Descrição
website_id int Restringe ao website informado.
sku_like string Filtro LIKE SQL no SKU. Use % como wildcard (URL-encode → %25).
page_size int Ativa paginação. Máx. 10.000.
current_page int Página (1-based). Só usado com page_size.

Exemplo

curl -H "Authorization: Bearer $TOKEN" \
  'https://loja.com/rest/V1/product-prices?website_id=1&sku_like=ABC-%25&page_size=1000&current_page=1'

Resposta

[
  { "sku": "ABC-001", "store_id": "1", "attribute_code": "price", "price": "199.9000" },
  { "sku": "ABC-001", "store_id": "2", "attribute_code": "price", "price": "189.9000" }
]

Notas

  • price aplica COALESCE(override_da_loja, default) — se a loja tem preço sobrescrito, vem o da loja; caso contrário, o global.
  • Não inclui special_price, tier_price ou cost. Para promoções, ver seção 5.3.
  • Ordenação: sku ASC, attribute_code ASC, store_id ASC.

3.3. GET /V1/product-stocks/msi (MageShop_StockApi)

Lista estoque MSI, expandindo cada stock_id pelas stores do website e detalhando sources. Este é o endpoint recomendado para lojas com MSI.

Parâmetros

Nome Tipo Descrição
website_id int Restringe ao website.
stock_id int Restringe a um stock MSI específico.
sku_like string Filtro LIKE no SKU.
page_size int Ativa paginação. Máx. 5.000.
current_page int Página (1-based).

Exemplo

curl -H "Authorization: Bearer $TOKEN" \
  'https://loja.com/rest/V1/product-stocks/msi?stock_id=2&page_size=500'

Resposta

[
  {
    "sku":         "ABC-001",
    "store_id":    "1",
    "website_id":  "1",
    "stock_id":    "2",
    "salable_qty": "10.0000",
    "is_salable":  "1",
    "sources": [
      { "source_code": "default",      "qty": 5, "status": 1 },
      { "source_code": "warehouse_sp", "qty": 5, "status": 1 }
    ]
  }
]

Notas

  • salable_qty vem da view MSI inventory_stock_{stock_id} — é a quantidade efetivamente vendável (considera reservations).
  • sources[] é o detalhamento por source (depósito físico): qty é a quantidade bruta da fonte; status: 1 em estoque, 0 fora.
  • Se o módulo Magento_Inventory estiver desabilitado: HTTP 400 com "MSI is not enabled on this instance."
  • Se a view inventory_stock_{N} não existir (indexer nunca executado): HTTP 400 com instrução para rodar bin/magento indexer:reindex inventory.
  • Ordenação: sku ASC, store_id ASC.

3.4. GET /V1/product-stocks/legacy (alternativa)

Lê de cataloginventory_stock_status — útil para reconciliação rápida sem o detalhamento de sources[].

curl -H "Authorization: Bearer $TOKEN" \
  'https://loja.com/rest/V1/product-stocks/legacy?website_id=1&page_size=1000'

Resposta

[
  {
    "sku":         "ABC-001",
    "store_id":    "1",
    "website_id":  "1",
    "stock_id":    "1",
    "salable_qty": "12.0000",
    "is_salable":  "1"
  }
]

ℹ️ Mesmo em lojas com MSI, este endpoint funciona porque o Magento mantém cataloginventory_stock_status sincronizado. Não substitui o endpoint MSI: não traz sources[] e o stock_id é sempre 1 (formato legacy). Use só para conferência simplificada.


4. Escrita — Estoque MSI

4.1. Endpoint

POST /rest/V1/inventory/source-items
Content-Type: application/json
Authorization: Bearer {token}

Atualiza quantidades e status de SKUs por source (depósito) em lote.

ACL: Magento_InventoryApi::source_items

Payload

{
  "sourceItems": [
    { "sku": "ABC-001", "source_code": "default",      "quantity": 10, "status": 1 },
    { "sku": "ABC-001", "source_code": "warehouse_sp", "quantity": 5,  "status": 1 },
    { "sku": "ABC-002", "source_code": "default",      "quantity": 0,  "status": 0 }
  ]
}
Campo Tipo Obrigatório Descrição
sku string sim SKU do produto.
source_code string sim Código do source (depósito). Deve existir em inventory_source.
quantity decimal sim Quantidade disponível na fonte.
status int sim 1 = em estoque, 0 = esgotado.

Resposta

  • Sucesso total: []
  • Falha parcial:
[
  { "message": "Source \"%1\" does not exist.", "parameters": ["deposito_xx"] }
]

4.2. Recomendações

💡 O endpoint aceita batch real. A otimização vem de consolidar SKUs por chamada + delta + paralelismo controlado.

  1. Lotes consolidados — 100 a 200 itens por requisição

    • Cada par sku + source_code conta como um item no array sourceItems.
    • Um SKU em 3 sources consome 3 posições do array. Considere isso ao dimensionar o lote.
    • Sweet spot: 100–200 itens. Lotes muito grandes (>500) começam a atingir timeouts de proxy/PHP.
  2. Enviar apenas o delta

    • Atualizar somente combinações sku + source_code cuja quantidade ou status mudou.
    • Manter no ERP um snapshot da última carga enviada e comparar antes de disparar.
    • Isoladamente, esta é a otimização de maior impacto.
  3. Paralelismo controlado

    • Máximo 1 a 2 requisições simultâneas ao endpoint.
    • O Magento serializa internamente operações sobre as mesmas linhas — paralelismo maior gera contenção em locks.
  4. Intervalo entre lotes

    • 200–500 ms entre chamadas sequenciais para evitar acúmulo na fila do PHP-FPM.
  5. Retry com backoff exponencial

    • Em 5xx/timeout, não retentar imediatamente.
    • Backoff sugerido: 1s → 2s → 4s → 8s, máximo 3–4 tentativas.
  6. Janela de sincronização

    • Preferir sincronização incremental contínua (delta a cada N minutos) em vez de cargas massivas em horários cheios.
    • Cargas full (reconciliação diária) → agendar fora dos horários de pico.

4.3. Notas operacionais sobre MSI

  • salable_qty é assíncronosalable_qty consultado via /V1/product-stocks/msi só reflete a mudança após o indexer MSI rodar. Geralmente isso é automático em segundos. Para forçar: bin/magento indexer:reindex inventory.
  • quantity = 0 não remove o source item — apenas zera a quantidade. Para marcar como indisponível, use status = 0. Para remover de fato, há um endpoint específico (/V1/inventory/source-items-delete), raramente necessário.
  • Sempre inspecione a resposta — HTTP 200 pode conter falhas parciais por item (seção 6.2).

5. Escrita — Preços

5.1. Preço base — POST /V1/products/base-prices

⚠️ Não usar PUT /V1/products/{sku} apenas para atualizar preço. Esse endpoint processa reindexação, EAV, mídia e categorias — gera carga desnecessária.

Use o endpoint dedicado, que aceita lote real e é independente de MSI:

POST /rest/V1/products/base-prices
Content-Type: application/json
Authorization: Bearer {token}

Payload

{
  "prices": [
    { "price": 27.34, "store_id": 0, "sku": "123" },
    { "price": 49.90, "store_id": 0, "sku": "124" },
    { "price": 99.00, "store_id": 0, "sku": "125" }
  ]
}
Campo Tipo Obrigatório Descrição
sku string sim SKU do produto.
price decimal sim Novo preço base.
store_id int sim 0 = global (default), >0 = override por loja.

Sobre store_id em lojas com uma única store view

Use sempre store_id = 0 — representa o escopo global e aplica o preço a todas as views. Não é necessário replicar a linha para store_id 1, 2 etc.; isso apenas multiplica o tamanho do payload.

Boas práticas

  • Lotes: 100 a 200 SKUs por requisição.
  • Delta apenas: enviar somente os SKUs cujo preço mudou.
  • Paralelismo: máximo 1 a 2 requisições simultâneas.
  • Intervalo entre lotes: 200–500 ms entre chamadas sequenciais.
  • Retry com backoff exponencial em 5xx/timeout.
  • Uma entrada por par sku + store_id no mesmo payload — múltiplas linhas para a mesma chave geram comportamento "última ganha" não garantido.

5.2. Preço promocional — POST /V1/products/special-price

Cria ou atualiza special_price com janela de vigência opcional.

Payload

{
  "prices": [
    {
      "sku":        "411",
      "price":      13,
      "store_id":   0,
      "price_from": "2025-11-19 00:00:00",
      "price_to":   ""
    }
  ]
}
Campo Tipo Obrigatório Descrição
sku string sim SKU.
price decimal sim Valor da promoção.
store_id int sim 0 = global.
price_from string recomendado Início (YYYY-MM-DD HH:MM:SS).
price_to string opcional Fim. "" ou omitido = promoção sem data fim.

Notas

  • O special_price só é exibido se price_from <= now <= price_to (quando price_to está definido).
  • O Magento não valida que special_price < price base; a vitrine simplesmente exibe o menor.
  • Evite enviar null para price_to na criação — alguns clientes serializam como string "null". Use "" ou omita.

5.3. Consultar preços promocionais — POST /V1/products/special-price-information

Apesar do verbo POST, é leitura — o POST é exigido porque o payload tem array.

{ "skus": ["411"] }

Resposta

[
  {
    "price":      13,
    "store_id":   0,
    "sku":        "411",
    "price_from": "2025-11-19 00:00:00",
    "price_to":   null
  }
]
  • Array vazio [] = nenhum SKU consultado tem promoção configurada.
  • Um SKU pode aparecer múltiplas vezes (uma por store_id com promoção distinta).

5.4. Remover preços promocionais — POST /V1/products/special-price-delete

A correspondência é feita por sku + store_id + price + price_from + price_totodos precisam bater com o registro existente, caso contrário a remoção é silenciosamente ignorada.

Payload

{
  "prices": [
    {
      "sku":        "411",
      "price":      13,
      "store_id":   0,
      "price_from": "2025-11-19 00:00:00",
      "price_to":   null
    }
  ]
}

💡 Fluxo recomendado: chamar special-price-information primeiro para obter os campos exatos, depois passar o objeto retornado como payload de special-price-delete. É idempotente — não erra se o registro não existir.


6. Erros e códigos HTTP

6.1. Códigos

Código Quando Corpo (resumido)
200 Sucesso (mesmo se array vazio). [] ou [ {...}, ... ]
401 Token ausente ou inválido. { "message": "Consumer is not authorized..." }
403 Token sem a ACL exigida. { "message": "..." }
400 MSI desabilitado, view ausente, payload inválido. { "message": "..." }
500 Erro inesperado. { "message": "..." }

6.2. Erro global

{ "message": "MSI is not enabled on this instance." }

6.3. Erros parciais (endpoints de escrita em lote)

Endpoints de escrita (source-items, base-prices, special-price etc.) retornam HTTP 200 mesmo quando alguns itens falham. O resultado é um array por item:

[
  {
    "message": "Invalid attribute value. Row ID: SKU = %1, Store ID: %2",
    "parameters": ["411", "99"]
  },
  {
    "message": "Source \"%1\" does not exist.",
    "parameters": ["deposito_xx"]
  }
]
  • Array vazio [] → todos os itens foram aceitos.
  • Array com objetos → cada objeto descreve um item que falhou.
  • Sempre inspecione o tamanho do array nos endpoints de escrita — HTTP 200 não significa sucesso total.

7. Receitas práticas

7.1. Extração completa paginada (Python)

import requests

TOKEN = "..."
BASE  = "https://loja.com/rest/V1/product-prices"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}

page = 1
out  = []
while True:
    r = requests.get(BASE, headers=HEADERS,
                     params={"page_size": 1000, "current_page": page})
    r.raise_for_status()
    chunk = r.json()
    if not chunk:
        break
    out.extend(chunk)
    page += 1

print(f"Total: {len(out)} registros")

7.2. Atualização em massa de estoque MSI (Python)

import requests, csv

TOKEN = "..."
URL   = "https://loja.com/rest/default/V1/inventory/source-items"
HDRS  = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

# Carrega CSV: sku,source_code,quantity,status
items = []
with open("estoque.csv") as f:
    for row in csv.DictReader(f):
        items.append({
            "sku":         row["sku"],
            "source_code": row["source_code"],
            "quantity":    float(row["quantity"]),
            "status":      int(row["status"]),
        })

# Envia em lotes de 200
for i in range(0, len(items), 200):
    batch = items[i:i+200]
    r = requests.post(URL, headers=HDRS, json={"sourceItems": batch}, timeout=120)
    r.raise_for_status()
    errors = r.json()
    if errors:
        print(f"Lote {i}: {len(errors)} erros")
        for e in errors:
            print(" -", e["message"], e.get("parameters"))

7.3. Sync incremental por prefixo de SKU (Bash)

for prefix in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9; do
  curl -s -H "Authorization: Bearer $TOKEN" \
    "https://loja.com/rest/V1/product-stocks/msi?sku_like=${prefix}%25" \
    > "stock_msi_${prefix}.json"
done

7.4. Atualização de preço base seguindo extração

# 1) Extrai preços atuais
curl -s -H "Authorization: Bearer $TOKEN" \
  'https://loja.com/rest/V1/product-prices?sku_like=ABC-%25' > current.json

# 2) Gera payload com aumento de 10% (jq)
jq '{ prices: [ .[] | { sku, store_id: (.store_id|tonumber), price: ((.price|tonumber) * 1.10) } ] }' \
  current.json > new_prices.json

# 3) Aplica
curl -X POST 'https://loja.com/rest/V1/products/base-prices' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $TOKEN" \
  --data @new_prices.json

7.5. Remover promoção a partir da consulta

# 1) Consulta promoções ativas
INFO=$(curl -s -X POST 'https://loja.com/rest/V1/products/special-price-information' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $TOKEN" \
  --data '{ "skus": ["411"] }')

# 2) Usa o próprio retorno como payload de delete
curl -X POST 'https://loja.com/rest/V1/products/special-price-delete' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $TOKEN" \
  --data "{ \"prices\": $INFO }"

7.6. Comparar estoque legacy vs MSI no mesmo SKU

Útil em auditoria para detectar divergências entre as duas views.

SKU="ABC-001"
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://loja.com/rest/V1/product-stocks/legacy?sku_like=${SKU}" > legacy.json
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://loja.com/rest/V1/product-stocks/msi?sku_like=${SKU}" > msi.json
diff <(jq '.[] | {sku,store_id,salable_qty}' legacy.json) \
     <(jq '.[] | {sku,store_id,salable_qty}' msi.json)

8. Resumo das recomendações

Item Forma ineficiente Forma recomendada
Leitura de preços em massa GET /V1/products (pesado) GET /V1/product-prices (MageShop)
Leitura de estoque em massa GET /V1/products ou /V1/stockItems/{sku} por SKU GET /V1/product-stocks/msi (MageShop)
Endpoint de escrita de estoque PUT /V1/products/{sku}/stockItems/{itemId} (legacy, sem batch) POST /V1/inventory/source-items (MSI, em lote)
Endpoint de escrita de preço PUT /V1/products/{sku} POST /V1/products/base-prices (lote)
store_id em preço Repetido em várias views Somente store_id = 0 (loja com store view única)
SKUs enviados Todos, sempre Apenas os que mudaram (delta)
Lotes (estoque e preço) Várias requisições, poucos itens 100 a 200 itens por requisição
Paralelismo (estoque) Não controlado Máx. 1–2 requisições simultâneas
Paralelismo (preços) Não controlado Máx. 1–2 requisições simultâneas
Intervalo entre lotes Sem controle 200–500 ms
Retry Imediato Backoff exponencial (1s→2s→4s→8s)

9. Boas práticas e resultado esperado

Leitura

  • Sempre pagine em produção — catálogos com 50k+ SKUs × N stores × M sources podem estourar memória do PHP-FPM e timeout de proxy. O endpoint MSI é especialmente sensível por causa do array sources[].
  • Use filtros agressivamente (website_id, stock_id, sku_like) para reduzir o custo SQL no banco. O MSI faz UNION ALL por stock — filtrar é importante.
  • URL-encode wildcards% precisa virar %25 na URL (ABC-%25ABC-%).
  • Evite polling intenso — endpoints são read-only, mas executam SQL pesado.

Escrita

  • Inspecione sempre a resposta — HTTP 200 pode conter falhas parciais (seção 6.3).
  • Uma entrada por chave única no payload: sku + source_code em estoque, sku + store_id em preços.
  • Reindex após escritas em massa — após carga grande em source-items, o salable_qty só reflete depois do indexer MSI rodar (geralmente automático). Em produção pode levar segundos.
  • Token de integration, não de admin, para clientes externos persistentes.
  • Idempotência — reenviar o mesmo payload não duplica registros. special-price-delete é totalmente idempotente.

Resultado esperado da adoção das recomendações

  • Redução significativa do número total de requisições por minuto (via batch + delta).
  • Menor consumo de CPU e banco de dados na aplicação Magento.
  • Menor risco de timeouts, bloqueios e degradação para o cliente final.
  • Sincronizações de catálogo mais rápidas e previsíveis.
  • Reconciliação ERP ↔ Magento simplificada pelos endpoints de leitura em massa.

Em caso de dúvidas sobre a implementação ou sobre limites adequados de paralelismo para o ambiente, fica à disposição o time de integração.

Copiar para usar com IA

Este artigo foi útil?

Obrigado pelo seu feedback!