TL;DR
Magento 2 (Adobe Commerce) exposes REST, SOAP, and GraphQL APIs for products, orders, customers, inventory, and store configuration. Use token-based authentication or OAuth 1.0a, choose the API style that fits your integration, handle configurable rate limits, and build reliable sync, order-processing, and inventory workflows.
Introduction
Magento powers more than 250,000 e-commerce stores and supports large-scale commerce operations. If you are building an ERP connector, marketplace sync, fulfillment workflow, or mobile app, the Magento API is the integration layer for moving catalog, order, inventory, and customer data.
A practical integration usually automates these workflows:
- Synchronize products and prices from a PIM or ERP
- Import paid orders into fulfillment or accounting systems
- Update stock quantities to reduce overselling
- Create customers and manage customer data
- Query storefront data efficiently through GraphQL
This guide shows how to authenticate, call Magento REST endpoints, manage products and orders, update inventory, handle asynchronous workflows, and prepare an integration for production.
💡 Apidog simplifies API integration testing. Test Magento endpoints, validate authentication flows, inspect API responses, and debug integration issues in one workspace. Import API specifications, mock responses, and share test scenarios with your team.
What Is the Magento 2 API?
Magento 2 provides three API styles:
- REST API: JSON-based endpoints for integrations, admin workflows, web apps, and mobile apps
- SOAP API: XML-based APIs commonly used by enterprise systems
- GraphQL: Query-based API optimized for storefront and PWA data fetching
You can use the API to work with:
- Products, categories, and inventory
- Orders, invoices, and shipments
- Customers and customer groups
- Shopping carts and checkout
- Promotions and pricing rules
- CMS pages and blocks
- Store configuration
Key Features
| Feature | Description |
|---|---|
| Multiple protocols | REST, SOAP, GraphQL |
| OAuth 1.0a | Secure third-party access |
| Token authentication | Admin, integration, and customer tokens |
| Webhooks | Async operations through queues and extensions |
| Rate limiting | Configurable per installation |
| Custom endpoints | Extend Magento with custom APIs |
| Multi-store support | One API can serve multiple store views |
API Comparison
| API Type | Protocol | Best Use Case |
|---|---|---|
| REST | JSON | Mobile apps and back-office integrations |
| SOAP | XML | Enterprise systems such as SAP or Oracle |
| GraphQL | GraphQL | Storefronts and PWAs |
Magento Versions
| Version | Status | End of Support |
|---|---|---|
| Magento 2.4.x | Current | Active |
| Adobe Commerce 2.4.x | Current | Active |
| Magento 1.x | EOL | June 2020 — do not use |
Getting Started: Authentication Setup
Step 1: Create an Admin User or Integration
Before making API calls, create credentials with only the permissions your integration needs.
For an admin user:
- Sign in to the Magento Admin panel.
- Go to System > Permissions > All Users.
- Create an admin user for API access.
For a third-party integration:
- Go to System > Extensions > Integrations.
- Create and activate an integration.
- Assign only the required API resources.
Step 2: Choose an Authentication Method
| Method | Best For | Token Lifetime |
|---|---|---|
| Admin token | Internal integrations | Configurable; default is 4 hours |
| Integration token | Third-party apps | Until revoked |
| OAuth 1.0a | Public marketplace apps | Until revoked |
| Customer token | Customer-facing apps | Configurable |
Use integration tokens rather than admin credentials in production whenever possible.
Step 3: Get an Admin Token
For internal integrations, request an admin token from /V1/integration/admin/token.
const MAGENTO_BASE_URL = process.env.MAGENTO_BASE_URL;
const MAGENTO_ADMIN_USERNAME = process.env.MAGENTO_ADMIN_USERNAME;
const MAGENTO_ADMIN_PASSWORD = process.env.MAGENTO_ADMIN_PASSWORD;
const getAdminToken = async () => {
const response = await fetch(
`${MAGENTO_BASE_URL}/rest/V1/integration/admin/token`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: MAGENTO_ADMIN_USERNAME,
password: MAGENTO_ADMIN_PASSWORD
})
}
);
if (!response.ok) {
throw new Error('Invalid admin credentials');
}
// Magento returns a plain token string, not a JSON object.
return response.text();
};
// Usage
const token = await getAdminToken();
console.log(`Admin token: ${token}`);
Store credentials and tokens outside source control:
# .env
MAGENTO_BASE_URL="https://store.example.com"
MAGENTO_ADMIN_USERNAME="api_user"
MAGENTO_ADMIN_PASSWORD="secure_password_here"
MAGENTO_ACCESS_TOKEN="obtained_via_auth"
Step 4: Create an Integration for Third-Party Access
Create an integration from the Admin panel:
- Go to System > Extensions > Integrations.
- Click Add New Integration.
- Enter the integration name and email.
- Add a callback URL and identity link URL if you use OAuth.
- Under API, select the minimum required resources:
- Products
- Orders
- Customers
- Inventory
- Save the integration.
- Click Activate.
- Copy the access token and token secret.
Keep the access token and secret in your deployment secret store or encrypted database.
Step 5: Get a Customer Token
Use a customer token for customer-facing workflows.
const getCustomerToken = async (email, password) => {
const response = await fetch(
`${MAGENTO_BASE_URL}/rest/V1/integration/customer/token`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: email,
password
})
}
);
if (!response.ok) {
throw new Error('Invalid customer credentials');
}
return response.text();
};
// Usage
const customerToken = await getCustomerToken(
'customer@example.com',
'password123'
);
Step 6: Create a Reusable REST Client
Centralize authorization and error handling before adding endpoint-specific functions.
const magentoRequest = async (endpoint, options = {}) => {
const token = await getAdminToken(); // Or retrieve a securely stored token
const response = await fetch(`${MAGENTO_BASE_URL}/rest${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Magento API Error: ${error.message}`);
}
return response.json();
};
// Usage
const products = await magentoRequest('/V1/products');
console.log(`Found ${products.items.length} products`);
Product Management
Get Products with Search Criteria
Magento REST list endpoints use searchCriteria query parameters. Build them programmatically instead of concatenating query strings manually.
const getProducts = async (filters = {}) => {
const params = new URLSearchParams();
if (filters.search) {
params.append(
'searchCriteria[filterGroups][0][filters][0][field]',
'sku'
);
params.append(
'searchCriteria[filterGroups][0][filters][0][value]',
`%${filters.search}%`
);
params.append(
'searchCriteria[filterGroups][0][filters][0][conditionType]',
'like'
);
}
if (filters.priceFrom) {
params.append(
'searchCriteria[filterGroups][1][filters][0][field]',
'price'
);
params.append(
'searchCriteria[filterGroups][1][filters][0][value]',
filters.priceFrom
);
params.append(
'searchCriteria[filterGroups][1][filters][0][conditionType]',
'gteq'
);
}
params.append('searchCriteria[pageSize]', filters.limit || 20);
params.append('searchCriteria[currentPage]', filters.page || 1);
return magentoRequest(`/V1/products?${params.toString()}`);
};
// Usage
const products = await getProducts({
search: 'shirt',
priceFrom: 20,
limit: 50
});
products.items.forEach((product) => {
console.log(`${product.sku}: ${product.name} - $${product.price}`);
});
Get a Product by SKU
const getProduct = async (sku) => {
return magentoRequest(`/V1/products/${sku}`);
};
// Usage
const product = await getProduct('TSHIRT-001');
console.log(`Name: ${product.name}`);
console.log(`Price: $${product.price}`);
console.log(`Stock: ${product.extension_attributes?.stock_item?.qty}`);
Create a Simple Product
Send product data in a product wrapper.
const createProduct = async (productData) => {
const product = {
product: {
sku: productData.sku,
name: productData.name,
attribute_set_id: productData.attributeSetId || 4,
type_id: 'simple',
price: productData.price,
status: productData.status || 1,
visibility: productData.visibility || 4,
weight: productData.weight || 1,
extension_attributes: {
stock_item: {
qty: productData.qty || 0,
is_in_stock: productData.qty > 0
}
},
custom_attributes: [
{
attribute_code: 'description',
value: productData.description
},
{
attribute_code: 'short_description',
value: productData.shortDescription
},
{
attribute_code: 'color',
value: productData.color
},
{
attribute_code: 'size',
value: productData.size
}
]
}
};
return magentoRequest('/V1/products', {
method: 'POST',
body: JSON.stringify(product)
});
};
// Usage
const newProduct = await createProduct({
sku: 'TSHIRT-NEW-001',
name: 'Premium Cotton T-Shirt',
price: 29.99,
qty: 100,
description: 'High-quality cotton t-shirt',
shortDescription: 'Premium cotton tee',
color: 'Blue',
size: 'M'
});
console.log(`Product created: ${newProduct.id}`);
Update a Product
Use PUT /V1/products/{sku} to update product attributes.
const updateProduct = async (sku, updates) => {
return magentoRequest(`/V1/products/${sku}`, {
method: 'PUT',
body: JSON.stringify({
product: {
sku,
...updates
}
})
});
};
// Usage: update price and stock
await updateProduct('TSHIRT-001', {
price: 24.99,
extension_attributes: {
stock_item: {
qty: 150,
is_in_stock: true
}
}
});
Delete a Product
const deleteProduct = async (sku) => {
await magentoRequest(`/V1/products/${sku}`, {
method: 'DELETE'
});
console.log(`Product ${sku} deleted`);
};
Product Types
| Type | Description | Use Case |
|---|---|---|
| Simple | Single SKU with no variations | Standard products |
| Configurable | Parent product with child variations | Size or color options |
| Grouped | Collection of simple products | Product bundles |
| Virtual | Non-physical product | Services and downloads |
| Bundle | Customizable product bundle | Build-your-own kits |
| Downloadable | Digital product | E-books and software |
Order Management
Get Orders
Use the same searchCriteria format to filter orders by status and creation date.
const getOrders = async (filters = {}) => {
const params = new URLSearchParams();
if (filters.status) {
params.append(
'searchCriteria[filterGroups][0][filters][0][field]',
'status'
);
params.append(
'searchCriteria[filterGroups][0][filters][0][value]',
filters.status
);
params.append(
'searchCriteria[filterGroups][0][filters][0][conditionType]',
'eq'
);
}
if (filters.dateFrom) {
params.append(
'searchCriteria[filterGroups][1][filters][0][field]',
'created_at'
);
params.append(
'searchCriteria[filterGroups][1][filters][0][value]',
filters.dateFrom
);
params.append(
'searchCriteria[filterGroups][1][filters][0][conditionType]',
'gteq'
);
}
params.append('searchCriteria[pageSize]', filters.limit || 20);
params.append('searchCriteria[currentPage]', filters.page || 1);
return magentoRequest(`/V1/orders?${params.toString()}`);
};
// Usage: get pending orders from the last 7 days
const orders = await getOrders({
status: 'pending',
dateFrom: '2026-03-18 00:00:00',
limit: 50
});
orders.items.forEach((order) => {
console.log(
`Order #${order.increment_id}: ${order.customer_email} - $${order.grand_total}`
);
});
Get a Single Order
const getOrder = async (orderId) => {
return magentoRequest(`/V1/orders/${orderId}`);
};
// Usage
const order = await getOrder(12345);
console.log(`Order #${order.increment_id}`);
console.log(`Status: ${order.status}`);
console.log(`Total: $${order.grand_total}`);
console.log('Items:');
order.items.forEach((item) => {
console.log(` - ${item.name} x ${item.qty_ordered}`);
});
Order Status Flow
pending → processing → complete
→ canceled
→ on_hold
→ payment_review
Cancel, Hold, or Unhold an Order
Direct status updates require a custom endpoint. Use the built-in order workflow endpoints for supported actions.
const cancelOrder = async (orderId) => {
return magentoRequest(`/V1/orders/${orderId}/cancel`, {
method: 'POST'
});
};
const holdOrder = async (orderId) => {
return magentoRequest(`/V1/orders/${orderId}/hold`, {
method: 'POST'
});
};
const unholdOrder = async (orderId) => {
return magentoRequest(`/V1/orders/${orderId}/unhold`, {
method: 'POST'
});
};
Create an Invoice
const createInvoice = async (
orderId,
items = [],
notify = true,
appendComment = false,
comment = null
) => {
const invoice = {
capture: true,
last: true,
items
};
if (comment) {
invoice.comment = comment;
invoice.notify_customer = notify ? 1 : 0;
invoice.append_comment = appendComment ? 1 : 0;
}
return magentoRequest(`/V1/order/${orderId}/invoice`, {
method: 'POST',
body: JSON.stringify(invoice)
});
};
// Usage: invoice and capture the full order
const invoiceId = await createInvoice(
12345,
[],
true,
false,
'Thank you for your order!'
);
console.log(`Invoice created: ${invoiceId}`);
Create a Shipment
const createShipment = async (
orderId,
items = [],
notify = true,
appendComment = false,
comment = null,
tracks = []
) => {
const shipment = {
items,
notify: notify ? 1 : 0,
append_comment: appendComment ? 1 : 0,
comment,
tracks
};
return magentoRequest(`/V1/order/${orderId}/ship`, {
method: 'POST',
body: JSON.stringify(shipment)
});
};
// Usage: ship with tracking information
const shipmentId = await createShipment(
12345,
[],
true,
false,
'Your order has shipped!',
[
{
track_number: '1Z999AA10123456784',
title: 'Tracking Number',
carrier_code: 'ups'
}
]
);
console.log(`Shipment created: ${shipmentId}`);
Customer Management
Get Customers
const getCustomers = async (filters = {}) => {
const params = new URLSearchParams();
if (filters.email) {
params.append(
'searchCriteria[filterGroups][0][filters][0][field]',
'email'
);
params.append(
'searchCriteria[filterGroups][0][filters][0][value]',
filters.email
);
params.append(
'searchCriteria[filterGroups][0][filters][0][conditionType]',
'eq'
);
}
params.append('searchCriteria[pageSize]', filters.limit || 20);
return magentoRequest(`/V1/customers/search?${params.toString()}`);
};
// Usage
const customers = await getCustomers({
email: 'customer@example.com'
});
customers.items.forEach((customer) => {
console.log(
`${customer.firstname} ${customer.lastname} - ${customer.email}`
);
});
Create a Customer
const createCustomer = async (customerData) => {
const customer = {
customer: {
websiteId: customerData.websiteId || 1,
email: customerData.email,
firstname: customerData.firstname,
lastname: customerData.lastname,
middlename: customerData.middlename || '',
gender: customerData.gender || 0,
store_id: customerData.storeId || 0,
extension_attributes: {
is_subscribed: customerData.subscribed || false
}
},
password: customerData.password
};
return magentoRequest('/V1/customers', {
method: 'POST',
body: JSON.stringify(customer)
});
};
// Usage
const newCustomer = await createCustomer({
email: 'newcustomer@example.com',
firstname: 'John',
lastname: 'Doe',
password: 'SecurePass123!',
subscribed: true
});
console.log(`Customer created: ID ${newCustomer.id}`);
Inventory Management (MSI)
Check Stock Status
const getStockStatus = async (sku) => {
return magentoRequest(`/V1/products/${sku}/stockItems/1`);
};
// Usage
const stock = await getStockStatus('TSHIRT-001');
console.log(`Qty: ${stock.qty}`);
console.log(`In Stock: ${stock.is_in_stock}`);
console.log(`Min Qty: ${stock.min_qty}`);
Update Stock Quantity
const updateStock = async (sku, qty, isInStock = null) => {
return magentoRequest(`/V1/products/${sku}/stockItems/1`, {
method: 'PUT',
body: JSON.stringify({
stockItem: {
qty,
is_in_stock: isInStock !== null ? isInStock : qty > 0
}
})
});
};
// Usage
await updateStock('TSHIRT-001', 100, true);
Webhooks and Async Operations
Magento does not provide native webhooks for order events. Use one of these approaches based on your deployment and reliability requirements.
Option 1: Poll for New Orders
Store the last processed order ID or timestamp, then query orders on a schedule.
const pollNewOrders = async (lastOrderId) => {
const orders = await getOrders({
dateFrom: new Date().toISOString()
});
return orders.items.filter((order) => order.id > lastOrderId);
};
Option 2: Use Adobe I/O Events
Adobe Commerce users can configure events through the Adobe Developer Console.
Option 3: Build a Custom Webhook Module
Magento message queues can be used as the basis for custom asynchronous integrations.
See the Magento message queue documentation:
https://devdocs.magento.com/guides/v2.4/extension-dev-guide/message-queues/message-queues.html
Rate Limiting
Understand the Installation Limits
Magento API rate limits are configurable per installation:
- Default: no limit configured
- Recommended: 100–1000 requests per minute
Configure limits from:
Stores > Configuration > Services > Web API > Security
Add Retry Handling
Use exponential backoff when a request is rate-limited.
const makeRateLimitedRequest = async (
endpoint,
options = {},
maxRetries = 3
) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await magentoRequest(endpoint, options);
} catch (error) {
const isLastAttempt = attempt === maxRetries;
if (error.message.includes('429') && !isLastAttempt) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
};
Production Deployment Checklist
Before going live, verify the following:
- [ ] Use integration tokens instead of admin credentials in production
- [ ] Store tokens in an encrypted database or secret manager
- [ ] Implement rate limiting and request queuing
- [ ] Add comprehensive error handling
- [ ] Log API requests and responses appropriately
- [ ] Implement a webhook alternative using polling, Adobe I/O Events, or a custom module
- [ ] Test against production-scale data volume
- [ ] Add retry logic for transient failures
- [ ] Scope API permissions to only the required resources
- [ ] Paginate catalog, order, and customer sync jobs
Real-World Use Cases
ERP Integration
A manufacturer synchronizes inventory between an ERP and Magento:
- Challenge: Manual stock updates between systems
- Implementation: Run a bi-directional API sync every 15 minutes
- Result: Near-real-time inventory updates and reduced overselling
Mobile App
A retailer builds a shopping app:
- Challenge: The storefront needs a native mobile experience
- Implementation: Use GraphQL for product browsing and REST for checkout workflows
- Result: A more efficient frontend data-fetching model
The platform API integration patterns used here also transfer to other ecosystems. LinkedIn's API follows similar OAuth 2.0 and REST patterns and is worth adding to your integration toolkit.
If your stack runs on a different platform, BigCommerce exposes a comparable REST API with its own OAuth model, catalog endpoints, and webhook system worth mapping before you build.
Conclusion
Magento 2 provides the APIs needed for product, order, customer, and inventory integrations.
Key implementation takeaways:
- Use REST for most back-office CRUD integrations.
- Use GraphQL for efficient storefront queries.
- Prefer integration tokens for production services.
- Build reusable request handling for authorization, errors, pagination, and retries.
- Use MSI stock endpoints for inventory workflows.
- Plan for polling, Adobe I/O Events, or a custom module because Magento does not provide native webhooks.
- Use Apidog to test authentication, validate request payloads, inspect responses, and share API test scenarios.
FAQ
How do I authenticate with the Magento API?
Use an admin token for internal integrations, or create an integration under System > Extensions > Integrations for OAuth-based access. Use customer tokens for customer-facing applications.
What is the difference between REST and GraphQL in Magento?
REST supports full CRUD operations and is suitable for integrations. GraphQL is optimized for frontend queries and can reduce unnecessary data fetching.
How do I create a product through the API?
Send a POST request to /V1/products with a product object that includes at least the SKU, name, price, and stock data in extension_attributes.stock_item.
Can I receive webhooks for new orders?
Magento does not provide native webhooks. Use polling, Adobe I/O Events for Adobe Commerce, or implement a custom module.
How do I update stock quantities?
Send a PUT request to /V1/products/{sku}/stockItems/1 with qty and is_in_stock in the stockItem payload.
Top comments (0)