API version: 2024-01 | ← Developer Program | Get API Access →

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
Shopify compatibility: IndeCommerce exposes the Shopify Admin API shim at /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 4242 for any payment flows
The sandbox prefix follows the pattern 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

ScopeAccess
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 any YYYY-MM version string; currently served from 2024-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

GET/admin/api/2024-01/products.json — list products
GET/admin/api/2024-01/products/count.json — product count
GET/admin/api/2024-01/products/<id>.json — single product
POST/admin/api/2024-01/products.json — create product
PUT/admin/api/2024-01/products/<id>.json — update product
DELETE/admin/api/2024-01/products/<id>.json — delete product

Query parameters (list)

ParamTypeDescription
idsstringComma-separated list of product IDs
limitintegerMax results (default 50, max 250)
pageintegerPage number
titlestringFilter by title (partial match)
vendorstringFilter by vendor
product_typestringFilter by product type
statusstringactive | draft | archived
published_statusstringpublished | unpublished | any
fieldsstringComma-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

GET/admin/api/2024-01/products/<id>/variants.json
POST/admin/api/2024-01/products/<id>/variants.json
PUT/admin/api/2024-01/variants/<id>.json
DELETE/admin/api/2024-01/products/<product_id>/variants/<id>.json

Variants inherit product-level fields and add price, compare_at_price, sku, barcode, inventory_quantity, weight, option1/2/3.

Orders

GET/admin/api/2024-01/orders.json
GET/admin/api/2024-01/orders/count.json
GET/admin/api/2024-01/orders/<id>.json
POST/admin/api/2024-01/orders.json — create order
PUT/admin/api/2024-01/orders/<id>.json — update status / tracking
POST/admin/api/2024-01/orders/<id>/cancel.json — cancel order
POST/admin/api/2024-01/orders/<id>/close.json
GET/admin/api/2024-01/checkouts.json — abandoned checkouts

Order status values

pendingconfirmedprocessingshippeddeliveredcompleted. Cancellation moves to cancelled.

Customers

GET/admin/api/2024-01/customers.json
GET/admin/api/2024-01/customers/<id>.json
POST/admin/api/2024-01/customers.json
PUT/admin/api/2024-01/customers/<id>.json
DELETE/admin/api/2024-01/customers/<id>.json
GET/admin/api/2024-01/customers/<id>/addresses.json
POST/admin/api/2024-01/customers/<id>/addresses.json
PUT/admin/api/2024-01/customers/<id>/addresses/<addr_id>/default.json
DELETE/admin/api/2024-01/customers/<id>/addresses/<addr_id>.json

Inventory

GET/admin/api/2024-01/inventory_items.json
GET/admin/api/2024-01/inventory_items/<id>.json
GET/admin/api/2024-01/inventory_levels.json
POST/admin/api/2024-01/inventory_levels/adjust.json — adjust by delta
POST/admin/api/2024-01/inventory_levels/set.json — set absolute level
GET/admin/api/2024-01/locations.json

Example: adjust inventory

POST /admin/api/2024-01/inventory_levels/adjust.json
{
  "inventory_item_id": 808950810,
  "location_id": 487838322,
  "available_adjustment": -5
}

Collections

GET/admin/api/2024-01/custom_collections.json
GET/admin/api/2024-01/custom_collections/<id>.json
POST/admin/api/2024-01/custom_collections.json
PUT/admin/api/2024-01/custom_collections/<id>.json
DELETE/admin/api/2024-01/custom_collections/<id>.json
GET/admin/api/2024-01/collects.json — product-collection memberships
POST/admin/api/2024-01/collects.json — add product to collection
DELETE/admin/api/2024-01/collects/<id>.json

URL Redirects

GET/admin/api/2024-01/redirects.json
GET/admin/api/2024-01/redirects/count.json
GET/admin/api/2024-01/redirects/<id>.json
POST/admin/api/2024-01/redirects.json
PUT/admin/api/2024-01/redirects/<id>.json
DELETE/admin/api/2024-01/redirects/<id>.json

Storefront Access Tokens

Storefront tokens authenticate requests to the public Storefront GraphQL API.

GET/admin/api/2024-01/storefront_access_tokens.json
POST/admin/api/2024-01/storefront_access_tokens.json
DELETE/admin/api/2024-01/storefront_access_tokens/<id>.json

Metafields

Attach arbitrary key-value metadata to products, orders, or customers.

GET/admin/api/2024-01/metafields.json?metafield[owner_resource]=product&metafield[owner_id]=<id>
POST/admin/api/2024-01/metafields.json
PUT/admin/api/2024-01/metafields/<id>.json
DELETE/admin/api/2024-01/metafields/<id>.json

Example: 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 metadata
  • products(first, after, query) — paginated product list
  • product(id, handle) — single product with variants
  • collections(first) / collection(id, handle)
  • cart(id) — cart by ID
  • customer — authenticated customer (requires customer token)

Cart & Checkout (Storefront)

Available mutations

  • cartCreate — create a new cart
  • cartLinesAdd / cartLinesUpdate / cartLinesRemove
  • cartBuyerIdentityUpdate — attach customer identity to cart
  • checkoutCreate — convert cart to a checkout
  • checkoutLineItemsAdd / checkoutLineItemsUpdate / checkoutLineItemsRemove
  • checkoutShippingAddressUpdateV2
  • checkoutEmailUpdateV2
  • checkoutCompleteFree — complete a zero-total checkout
  • customerCreate / customerUpdate
  • customerAccessTokenCreate / customerAccessTokenDelete

Webhook Subscriptions

Register an HTTPS endpoint to receive real-time event notifications when things happen in a merchant's store.

GET/admin/api/2024-01/webhooks.json
POST/admin/api/2024-01/webhooks.json
GET/admin/api/2024-01/webhooks/<id>.json
DELETE/admin/api/2024-01/webhooks/<id>.json

Example: 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

TopicFired 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)
Always verify the signature before processing webhook payloads. Reject any request where the signature does not match.

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:

  1. Your app creates a charge via the API → receives a confirmation_url
  2. Redirect the merchant to the confirmation_url
  3. Merchant accepts or declines on the IndeCommerce confirmation page
  4. Merchant is redirected to your return_url with ?charge_id=<id>
  5. Your app calls the activate endpoint to finalize the charge

One-time Charges

GET/admin/api/2024-01/application_charges.json
GET/admin/api/2024-01/application_charges/<id>.json
POST/admin/api/2024-01/application_charges.json
POST/admin/api/2024-01/application_charges/<id>/activate.json

Example: 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

GET/admin/api/2024-01/recurring_application_charges.json
GET/admin/api/2024-01/recurring_application_charges/<id>.json
POST/admin/api/2024-01/recurring_application_charges.json
POST/admin/api/2024-01/recurring_application_charges/<id>/activate.json
DELETE/admin/api/2024-01/recurring_application_charges/<id>.json — cancel

Example: 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.

GET/admin/api/2024-01/recurring_application_charges/<charge_id>/usage_charges.json
POST/admin/api/2024-01/recurring_application_charges/<charge_id>/usage_charges.json

Example: 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.

HeaderDescription
X-Shopify-Shop-Api-Call-LimitCurrent usage, e.g. 32/40
Retry-AfterSeconds to wait when rate-limited (HTTP 429)

Errors

StatusMeaning
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)