Overview
The IndeCommerce API lets you build apps, integrations, and automations for wholesale B2B merchants on the platform. It is intentionally Shopify-compatible — most code written against the Shopify Admin API works against our API with only a hostname change.
- Admin REST API — full CRUD for products, orders, customers, inventory, collections, and more
- Storefront GraphQL API — public-facing catalog browsing and cart/checkout mutations
- Webhooks — real-time event delivery signed with HMAC-SHA256
- App Billing API — charge merchants for your app via one-time charges, subscriptions, and usage-based billing
- OAuth 2.0 — standard authorization-code flow; merchants install your app in one click
/admin/api/<version>/. If your app already talks Shopify, point it at the merchant's
IndeCommerce domain and it will work with no other changes.
Quickstart
1. Create a developer account
Go to /developers/signup and register. Your sandbox store is provisioned instantly.
2. Create an app
From your developer dashboard, click New App and fill in:
- Name — displayed in the App Store listing
- Slug — URL-safe identifier, e.g.
my-inventory-sync - Redirect URIs — comma-separated OAuth callback URLs
- Default scopes — space-separated list of scopes your app needs
3. Install on your sandbox
Use the install URL to trigger the OAuth flow against your sandbox store:
https://<your-domain>/<store-prefix>/apps/install?client_id=<client_id>&scope=read_products,write_orders&redirect_uri=https://yourapp.com/callback
4. Exchange the code for an access token
POST https://<your-domain>/api/v1/apps/oauth/token
Content-Type: application/x-www-form-urlencoded
client_id=<client_id>
&client_secret=<client_secret>
&code=<code_from_callback>
&redirect_uri=https://yourapp.com/callback
Response:
{
"access_token": "ic_access_xxxxxxxxxxxx",
"scope": "read_products write_orders",
"token_type": "bearer"
}
5. Make your first API call
GET /admin/api/2024-01/products.json
X-Shopify-Access-Token: ic_access_xxxxxxxxxxxx
Sandbox Environment
Every developer account gets a dedicated sandbox store — a real IndeCommerce tenant pre-configured for testing. It is accessible from your developer dashboard under "Open sandbox store →".
- No real payment processing — all Stripe interactions use Stripe's test mode
- Isolated from production merchants; cannot appear in the public App Store
- You can install your own apps on it without going through the review process
- Use Stripe test card
4242 4242 4242 4242for any payment flows
sandbox-dev{id}. All API calls scoped to your sandbox use this prefix.
OAuth 2.0
IndeCommerce uses the Authorization Code grant type. Each app install creates a unique access token scoped to one merchant store.
Authorization URL
GET /<store_prefix>/apps/install
?client_id=<your_client_id>
&scope=read_products,write_orders
&redirect_uri=https://yourapp.com/callback
&state=<random_nonce>
Token exchange
POST /api/v1/apps/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&code=CODE
&redirect_uri=REDIRECT_URI
Using the token
Pass the access token in every request using the X-Shopify-Access-Token header (Shopify-compat) or Authorization: Bearer:
X-Shopify-Access-Token: ic_access_xxxxxxxxxxxx
Token scopes
Tokens are valid indefinitely. A merchant can revoke an install from their app settings, which invalidates the token immediately.
API Keys
For server-to-server integrations that don't involve a merchant consent flow, merchants can generate API keys directly from their dashboard under
Settings → API Keys. Keys carry the same permission model as OAuth tokens and are prefixed ic_key_.
curl https://<host>/admin/api/2024-01/products.json \
-H "X-Shopify-Access-Token: ic_key_xxxxxxxxxxxx"
Scopes Reference
| Scope | Access |
|---|---|
read_products |
Read products, variants, images, collections |
write_products |
Create and update products, variants, images |
read_orders |
Read orders and order line items |
write_orders |
Create, update, and cancel orders |
read_customers |
Read customer profiles and addresses |
write_customers |
Create and update customers |
read_inventory |
Read inventory levels and locations |
write_inventory |
Adjust inventory levels |
read_price_rules |
Read discount and price rules |
write_price_rules |
Create and update price rules |
read_analytics |
Read sales and traffic reports |
read_metafields |
Read metafields on any resource |
write_metafields |
Create and update metafields |
read_webhooks |
List webhook subscriptions |
write_webhooks |
Create and delete webhook subscriptions |
read_storefront_tokens |
List storefront access tokens |
write_storefront_tokens |
Create and delete storefront access tokens |
Admin API — Basics & Versioning
The Admin API is available at two equivalent base paths:
- Native:
/api/v1/ - Shopify shim:
/admin/api/<version>/— accepts anyYYYY-MMversion string; currently served from2024-01
All requests must include the X-Shopify-Access-Token header. All responses are JSON. Pagination uses page_info cursors or limit/page params.
GET /admin/api/2024-01/products.json?limit=50&page_info=cursor123
X-Shopify-Access-Token: TOKEN
Products
/admin/api/2024-01/products.json — list products/admin/api/2024-01/products/count.json — product count/admin/api/2024-01/products/<id>.json — single product/admin/api/2024-01/products.json — create product/admin/api/2024-01/products/<id>.json — update product/admin/api/2024-01/products/<id>.json — delete productQuery parameters (list)
| Param | Type | Description |
|---|---|---|
ids | string | Comma-separated list of product IDs |
limit | integer | Max results (default 50, max 250) |
page | integer | Page number |
title | string | Filter by title (partial match) |
vendor | string | Filter by vendor |
product_type | string | Filter by product type |
status | string | active | draft | archived |
published_status | string | published | unpublished | any |
fields | string | Comma-separated fields to return |
Example: create a product
POST /admin/api/2024-01/products.json
{
"product": {
"title": "Ceramic Mug 12oz",
"vendor": "Acme Ceramics",
"product_type": "Mugs",
"variants": [
{ "price": "18.00", "sku": "MUG-12-WHT", "inventory_quantity": 200 }
],
"images": [{ "src": "https://cdn.example.com/mug.jpg" }]
}
}
Variants
/admin/api/2024-01/products/<id>/variants.json/admin/api/2024-01/products/<id>/variants.json/admin/api/2024-01/variants/<id>.json/admin/api/2024-01/products/<product_id>/variants/<id>.jsonVariants inherit product-level fields and add price, compare_at_price, sku, barcode, inventory_quantity, weight, option1/2/3.
Orders
/admin/api/2024-01/orders.json/admin/api/2024-01/orders/count.json/admin/api/2024-01/orders/<id>.json/admin/api/2024-01/orders.json — create order/admin/api/2024-01/orders/<id>.json — update status / tracking/admin/api/2024-01/orders/<id>/cancel.json — cancel order/admin/api/2024-01/orders/<id>/close.json/admin/api/2024-01/checkouts.json — abandoned checkoutsOrder status values
pending → confirmed → processing → shipped → delivered → completed. Cancellation moves to cancelled.
Customers
/admin/api/2024-01/customers.json/admin/api/2024-01/customers/<id>.json/admin/api/2024-01/customers.json/admin/api/2024-01/customers/<id>.json/admin/api/2024-01/customers/<id>.json/admin/api/2024-01/customers/<id>/addresses.json/admin/api/2024-01/customers/<id>/addresses.json/admin/api/2024-01/customers/<id>/addresses/<addr_id>/default.json/admin/api/2024-01/customers/<id>/addresses/<addr_id>.jsonInventory
/admin/api/2024-01/inventory_items.json/admin/api/2024-01/inventory_items/<id>.json/admin/api/2024-01/inventory_levels.json/admin/api/2024-01/inventory_levels/adjust.json — adjust by delta/admin/api/2024-01/inventory_levels/set.json — set absolute level/admin/api/2024-01/locations.jsonExample: adjust inventory
POST /admin/api/2024-01/inventory_levels/adjust.json
{
"inventory_item_id": 808950810,
"location_id": 487838322,
"available_adjustment": -5
}
Collections
/admin/api/2024-01/custom_collections.json/admin/api/2024-01/custom_collections/<id>.json/admin/api/2024-01/custom_collections.json/admin/api/2024-01/custom_collections/<id>.json/admin/api/2024-01/custom_collections/<id>.json/admin/api/2024-01/collects.json — product-collection memberships/admin/api/2024-01/collects.json — add product to collection/admin/api/2024-01/collects/<id>.jsonURL Redirects
/admin/api/2024-01/redirects.json/admin/api/2024-01/redirects/count.json/admin/api/2024-01/redirects/<id>.json/admin/api/2024-01/redirects.json/admin/api/2024-01/redirects/<id>.json/admin/api/2024-01/redirects/<id>.jsonStorefront Access Tokens
Storefront tokens authenticate requests to the public Storefront GraphQL API.
/admin/api/2024-01/storefront_access_tokens.json/admin/api/2024-01/storefront_access_tokens.json/admin/api/2024-01/storefront_access_tokens/<id>.jsonMetafields
Attach arbitrary key-value metadata to products, orders, or customers.
/admin/api/2024-01/metafields.json?metafield[owner_resource]=product&metafield[owner_id]=<id>/admin/api/2024-01/metafields.json/admin/api/2024-01/metafields/<id>.json/admin/api/2024-01/metafields/<id>.jsonExample: set a metafield via GraphQL mutation
mutation {
metafieldSet(metafields: [{
ownerId: "gid://indecommerce/Product/123",
namespace: "custom",
key: "reorder_point",
value: "50",
type: "number_integer"
}]) {
metafields { id key value }
userErrors { field message }
}
}
Storefront GraphQL API
The Storefront API lets your app browse the merchant's public catalog, manage carts, and initiate checkout
without requiring merchant-level credentials. Authenticate with a StorefrontAccessToken.
POST /storefront/api/graphql
X-Shopify-Storefront-Access-Token: <storefront_token>
Content-Type: application/json
{
"query": "{ products(first: 10) { edges { node { id title } } } }"
}
Available queries
shop— store metadataproducts(first, after, query)— paginated product listproduct(id, handle)— single product with variantscollections(first)/collection(id, handle)cart(id)— cart by IDcustomer— authenticated customer (requires customer token)
Cart & Checkout (Storefront)
Available mutations
cartCreate— create a new cartcartLinesAdd/cartLinesUpdate/cartLinesRemovecartBuyerIdentityUpdate— attach customer identity to cartcheckoutCreate— convert cart to a checkoutcheckoutLineItemsAdd/checkoutLineItemsUpdate/checkoutLineItemsRemovecheckoutShippingAddressUpdateV2checkoutEmailUpdateV2checkoutCompleteFree— complete a zero-total checkoutcustomerCreate/customerUpdatecustomerAccessTokenCreate/customerAccessTokenDelete
Webhook Subscriptions
Register an HTTPS endpoint to receive real-time event notifications when things happen in a merchant's store.
/admin/api/2024-01/webhooks.json/admin/api/2024-01/webhooks.json/admin/api/2024-01/webhooks/<id>.json/admin/api/2024-01/webhooks/<id>.jsonExample: subscribe to order creation
POST /admin/api/2024-01/webhooks.json
{
"webhook": {
"topic": "orders/create",
"address": "https://yourapp.com/webhooks/order-created",
"format": "json"
}
}
Webhook Topics Reference
| Topic | Fired when |
|---|---|
orders/create |
A new order is placed |
orders/updated |
An order is updated (status, tracking, etc.) |
orders/paid |
An order transitions to paid status |
orders/fulfilled |
An order is marked fulfilled / shipped |
orders/cancelled |
An order is cancelled |
products/create |
A new product is created |
products/update |
A product or variant is updated |
products/delete |
A product is deleted |
customers/create |
A new customer registers |
customers/update |
A customer profile is updated |
customers/delete |
A customer is deleted |
app/uninstalled |
A merchant uninstalls your app |
Verifying Webhook Payloads
Every webhook request includes an X-Shopify-Hmac-Sha256 header containing a base64-encoded HMAC-SHA256 signature of the raw request body, signed with your app's client secret.
import hmac, hashlib, base64
def verify_webhook(body: bytes, hmac_header: str, secret: str) -> bool:
digest = hmac.new(
secret.encode('utf-8'),
body,
hashlib.sha256
).digest()
computed = base64.b64encode(digest).decode('utf-8')
return hmac.compare_digest(computed, hmac_header)
Webhooks are delivered with a 10-second timeout. Failed deliveries are retried up to 19 times over 48 hours with exponential back-off. An endpoint that consistently fails will be automatically paused.
App Billing — Overview
Monetize your app through three charge types. All billing goes through IndeCommerce's Stripe integration — you receive 85% of each payment; the platform keeps 15%.
- One-time charges — a single payment, e.g. a setup fee or add-on purchase
- Recurring subscriptions — monthly or annual plan billing
- Usage charges — metered charges posted against an active subscription (e.g. per-SMS sent)
All charges must go through a merchant confirmation page before they are activated. The flow:
- Your app creates a charge via the API → receives a
confirmation_url - Redirect the merchant to the
confirmation_url - Merchant accepts or declines on the IndeCommerce confirmation page
- Merchant is redirected to your
return_urlwith?charge_id=<id> - Your app calls the activate endpoint to finalize the charge
One-time Charges
/admin/api/2024-01/application_charges.json/admin/api/2024-01/application_charges/<id>.json/admin/api/2024-01/application_charges.json/admin/api/2024-01/application_charges/<id>/activate.jsonExample: create a one-time charge
POST /admin/api/2024-01/application_charges.json
{
"application_charge": {
"name": "Premium Setup Fee",
"price": "49.99",
"return_url": "https://yourapp.com/billing/complete",
"test": false
}
}
// Response includes confirmation_url — redirect the merchant there
{
"application_charge": {
"id": 1017262353,
"status": "pending",
"confirmation_url": "https://<store>/apps/charges/confirm?token=..."
}
}
Recurring Subscriptions
/admin/api/2024-01/recurring_application_charges.json/admin/api/2024-01/recurring_application_charges/<id>.json/admin/api/2024-01/recurring_application_charges.json/admin/api/2024-01/recurring_application_charges/<id>/activate.json/admin/api/2024-01/recurring_application_charges/<id>.json — cancelExample: create a subscription
POST /admin/api/2024-01/recurring_application_charges.json
{
"recurring_application_charge": {
"name": "Professional Plan",
"price": "29.99",
"return_url": "https://yourapp.com/billing/activate",
"trial_days": 14,
"capped_amount": null
}
}
Usage Charges
Post metered charges against an active recurring subscription. The subscription must have a capped_amount set.
/admin/api/2024-01/recurring_application_charges/<charge_id>/usage_charges.json/admin/api/2024-01/recurring_application_charges/<charge_id>/usage_charges.jsonExample: post a usage charge
POST /admin/api/2024-01/recurring_application_charges/455696195/usage_charges.json
{
"usage_charge": {
"description": "250 SMS messages sent",
"price": "2.50"
}
}
Rate Limits
The API uses a leaky-bucket algorithm. Each store-app pair has a bucket of 40 requests that refills at 2 requests/second.
| Header | Description |
|---|---|
X-Shopify-Shop-Api-Call-Limit | Current usage, e.g. 32/40 |
Retry-After | Seconds to wait when rate-limited (HTTP 429) |
Errors
| Status | Meaning |
|---|---|
200 OK |
Request succeeded |
201 Created |
Resource created |
204 No Content |
Successful delete |
400 Bad Request |
Malformed request or validation error — check errors[] in body |
401 Unauthorized |
Missing or invalid access token |
403 Forbidden |
Token lacks required scope |
404 Not Found |
Resource not found or belongs to a different tenant |
422 Unprocessable Entity |
Business logic validation failed |
429 Too Many Requests |
Rate limit exceeded — respect Retry-After header |
500 Internal Server Error |
Platform error — retry with back-off |
Error bodies follow the Shopify shape:
{ "errors": { "title": ["can't be blank"] } }
// or for top-level errors:
{ "errors": "Not found" }
Changelog
2024-01 (current)
- Initial stable release of the Admin REST API and Storefront GraphQL API
- OAuth 2.0 authorization code flow
- Webhook subscriptions with HMAC-SHA256 signing
- App Billing API: one-time charges, recurring subscriptions, usage charges
- URL Redirects, Storefront Access Tokens, Metafields endpoints
- Abandoned Checkouts API
- Customer address management (create, update, delete, set default)