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 REST API
A lightweight JSON REST API for cart management — no authentication required for buyer-side calls.
All endpoints are scoped to a merchant's store prefix (/<store_prefix>).
Ideal for headless storefronts, custom themes, and apps like the Advanced Cart Drawer.
cart_session_id session value (guests). No cart ID is needed — the server resolves the active cart automatically.
GET /<store_prefix>/api/cart
Return the current buyer's cart contents as JSON. Returns an empty cart object if no cart exists yet.
GET /mystore/api/cart
Cookie: session=...
// Response 200
{
"success": true,
"cart": {
"item_count": 2,
"subtotal": "49.98",
"currency": "USD",
"checkout_url": "/mystore/checkout",
"cart_url": "/mystore/cart",
"items": [
{
"id": 84,
"product_id": 12,
"variant_id": null,
"product_name": "Widget Pro",
"variant_name": null,
"sku": "WGT-001",
"quantity": 2,
"price": "24.99",
"line_total": "49.98",
"image_url": "https://cdn.example.com/widget.jpg",
"product_url": "/mystore/products/widget-pro"
}
]
}
}
POST /<store_prefix>/api/cart/add
Add an item to the cart. Accepts JSON or form data. If the same product+variant is already in the cart, the quantity is incremented.
POST /mystore/api/cart/add
Content-Type: application/json
{
"product_id": 12,
"variant_id": 31, // optional — omit for products with no variants
"quantity": 2
}
// Success 200
{ "success": true, "message": "Widget Pro added to cart!" }
// Error 400
{ "success": false, "message": "Only 5 items available in stock." }
Business rules enforced
- Product must be active and belong to the store
- External/affiliate products return 400 and cannot be added
- Inventory check: quantity must not exceed
inventory_quantity(unlessallow_backordersis true) - Minimum order quantity per product and per buyer group are both enforced
- If
require_login_for_pricesis on, guests receive 403
POST /<store_prefix>/api/cart/update
Update a cart item's quantity. Setting quantity to 0 removes the item.
POST /mystore/api/cart/update
Content-Type: application/json
{ "item_id": 84, "quantity": 3 }
// Success 200
{ "success": true, "item_count": 3 }
// Remove item (quantity 0)
{ "item_id": 84, "quantity": 0 }
// → { "success": true, "item_count": 0 }
POST /<store_prefix>/api/cart/remove
Remove an item from the cart entirely.
POST /mystore/api/cart/remove
Content-Type: application/json
{ "item_id": 84 }
// Success 200
{ "success": true, "item_count": 0 }
GET /<store_prefix>/api/cart/count
Lightweight endpoint for refreshing a cart badge. Returns the total item quantity across all line items.
GET /mystore/api/cart/count
{ "success": true, "count": 5 }
Error shape (all endpoints)
{ "success": false, "message": "Human-readable reason" }
Cart GraphQL API
The Storefront GraphQL API follows the Shopify Storefront API schema and is available at
POST /storefront/api/graphql with a X-Shopify-Storefront-Access-Token header.
It uses Global IDs (gid://indecommerce/Cart/1) for all resources.
StorefrontCart) from the REST cart.
It is designed for headless storefronts where you need to manage cart state client-side with a persistent cart ID.
For theme-extension apps (like the Cart Drawer) the REST API is simpler and uses the server session.
cartCreate — create a new cart
mutation {
cartCreate(input: {
lines: [
{ merchandiseId: "gid://indecommerce/ProductVariant/31", quantity: 2 }
]
}) {
cart {
id
checkoutUrl
cost { subtotalAmount { amount currencyCode } }
lines(first: 10) {
nodes {
id quantity
merchandise {
... on ProductVariant {
id title price { amount }
product { title featuredImage { url } }
}
}
}
}
}
userErrors { field message }
}
}
cartLinesAdd — add items to an existing cart
mutation {
cartLinesAdd(
cartId: "gid://indecommerce/Cart/7"
lines: [
{ merchandiseId: "gid://indecommerce/ProductVariant/31", quantity: 1 }
]
) {
cart { id lines(first:10) { nodes { id quantity } } }
userErrors { field message }
}
}
cartLinesUpdate — change quantity of an existing line
mutation {
cartLinesUpdate(
cartId: "gid://indecommerce/Cart/7"
lines: [
{ id: "gid://indecommerce/CartLine/84", quantity: 3 }
]
) {
cart { id cost { subtotalAmount { amount currencyCode } } }
userErrors { field message }
}
}
cartLinesRemove — delete lines from the cart
mutation {
cartLinesRemove(
cartId: "gid://indecommerce/Cart/7"
lineIds: ["gid://indecommerce/CartLine/84"]
) {
cart { id lines(first:10) { nodes { id quantity } } }
userErrors { field message }
}
}
cart — query a cart by ID
query {
cart(id: "gid://indecommerce/Cart/7") {
id
checkoutUrl
createdAt updatedAt
cost {
subtotalAmount { amount currencyCode }
totalAmount { amount currencyCode }
}
buyerIdentity { email phone }
lines(first: 50) {
nodes {
id quantity
cost {
amountPerQuantity { amount currencyCode }
totalAmount { amount currencyCode }
}
merchandise {
... on ProductVariant {
id title sku
price { amount currencyCode }
image { url altText }
product { id title handle featuredImage { url altText } }
}
}
}
}
}
}
cartBuyerIdentityUpdate — attach customer identity
mutation {
cartBuyerIdentityUpdate(
cartId: "gid://indecommerce/Cart/7"
buyerIdentity: { email: "[email protected]", phone: "+15550001234" }
) {
cart { id buyerIdentity { email phone } }
userErrors { field message }
}
}
Cart type reference
| Field | Type | Description |
|---|---|---|
id |
ID! | Global ID — gid://indecommerce/Cart/{n} |
checkoutUrl |
String! | URL to the checkout page for this cart |
lines |
CartLineConnection! | Paginated list of cart line items |
cost.subtotalAmount |
MoneyV2! | Sum of all line totals before tax/shipping |
cost.totalAmount |
MoneyV2! | Same as subtotal (tax calculated at checkout) |
buyerIdentity.email |
String | Email attached to cart |
buyerIdentity.phone |
String | Phone attached to cart |
createdAt |
DateTime! | ISO 8601 creation timestamp |
updatedAt |
DateTime | ISO 8601 last-modified timestamp |
note |
String | Buyer note (visible to merchant) |
Checkout GraphQL API
Convert a cart (or build directly) into a checkout, attach a shipping address and email, then complete it.
All checkout mutations return a Checkout object and a checkoutUserErrors array.
checkoutCreate — create a checkout
mutation {
checkoutCreate(input: {
email: "[email protected]"
lineItems: [
{ variantId: "gid://indecommerce/ProductVariant/31", quantity: 2 }
]
shippingAddress: {
firstName: "Jane", lastName: "Smith"
address1: "123 Main St", city: "Austin"
province: "TX", country: "US", zip: "78701"
}
note: "Leave at the dock"
}) {
checkout {
id webUrl
subtotalPrice { amount currencyCode }
totalPrice { amount currencyCode }
lineItems(first: 10) { nodes { title quantity unitPrice { amount } } }
}
checkoutUserErrors { field message code }
}
}
checkoutLineItemsAdd
mutation {
checkoutLineItemsAdd(
checkoutId: "gid://indecommerce/Checkout/abc123"
lineItems: [{ variantId: "gid://indecommerce/ProductVariant/45", quantity: 1 }]
) {
checkout { id lineItems(first:10) { nodes { id title quantity } } }
checkoutUserErrors { field message }
}
}
checkoutLineItemsUpdate
mutation {
checkoutLineItemsUpdate(
checkoutId: "gid://indecommerce/Checkout/abc123"
lineItems: [{ id: "gid://indecommerce/CheckoutLineItem/77", quantity: 5 }]
) {
checkout { id subtotalPrice { amount } }
checkoutUserErrors { field message }
}
}
checkoutLineItemsRemove
mutation {
checkoutLineItemsRemove(
checkoutId: "gid://indecommerce/Checkout/abc123"
lineItemIds: ["gid://indecommerce/CheckoutLineItem/77"]
) {
checkout { id lineItems(first:10) { nodes { id } } }
checkoutUserErrors { field message }
}
}
checkoutShippingAddressUpdateV2
mutation {
checkoutShippingAddressUpdateV2(
checkoutId: "gid://indecommerce/Checkout/abc123"
shippingAddress: {
firstName: "Jane", lastName: "Smith"
address1: "456 Warehouse Blvd", city: "Austin"
province: "TX", country: "US", zip: "78702"
}
) {
checkout { id shippingAddress { address1 city country } }
checkoutUserErrors { field message }
}
}
checkoutEmailUpdateV2
mutation {
checkoutEmailUpdateV2(
checkoutId: "gid://indecommerce/Checkout/abc123"
email: "[email protected]"
) {
checkout { id email }
checkoutUserErrors { field message }
}
}
checkoutCompleteFree — complete a zero-total checkout
mutation {
checkoutCompleteFree(checkoutId: "gid://indecommerce/Checkout/abc123") {
checkout { id completedAt order { id name } }
checkoutUserErrors { field message }
}
}
checkout.webUrl — the platform-hosted checkout
page handles payment collection via Stripe. checkoutCompleteFree is only valid when the
total is $0.00 (e.g. 100% discount codes or credit-covered orders).
Customer GraphQL API
Register buyers, authenticate them, and read their profile and order history using customer access tokens.
customerCreate — register a new buyer
mutation {
customerCreate(input: {
firstName: "Jane"
lastName: "Smith"
email: "[email protected]"
password: "s3cur3P@ss!"
phone: "+15550001234"
acceptsMarketing: true
}) {
customer { id email firstName lastName }
customerUserErrors { field message code }
}
}
customerAccessTokenCreate — log in
mutation {
customerAccessTokenCreate(input: {
email: "[email protected]"
password: "s3cur3P@ss!"
}) {
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors { field message code }
}
}
// Store accessToken in client; pass it as header for authenticated queries:
// X-Shopify-Storefront-Access-Token: <storefront_token>
// and include customerAccessToken in subsequent calls
customerUpdate — update profile
mutation {
customerUpdate(
customerAccessToken: "eyJ..."
customer: {
firstName: "Janet"
phone: "+15550009999"
acceptsMarketing: false
}
) {
customer { id firstName phone }
customerUserErrors { field message }
}
}
customerAccessTokenDelete — log out
mutation {
customerAccessTokenDelete(customerAccessToken: "eyJ...") {
deletedAccessToken
deletedCustomerAccessTokenId
userErrors { field message }
}
}
customer query — fetch authenticated customer
query {
customer(customerAccessToken: "eyJ...") {
id email firstName lastName
phone defaultAddress { address1 city country zip }
orders(first: 5, sortKey: PROCESSED_AT, reverse: true) {
nodes {
id name
processedAt
financialStatus fulfillmentStatus
currentTotalPrice { amount currencyCode }
lineItems(first: 10) { nodes { title quantity } }
}
}
addresses(first: 5) {
nodes { id address1 city country zip isDefault }
}
}
}
Complete GraphQL operation reference
| Operation | Kind | Description |
|---|---|---|
shop |
query | Store metadata, currency, domain |
products |
query | Paginated product catalog with variants |
product |
query | Single product by ID or handle |
collections / collection |
query | Collection list or single collection |
cart |
query | Fetch a cart by global ID |
customer |
query | Authenticated customer profile + orders |
node / nodes |
query | Fetch any resource(s) by global ID |
cartCreate |
mutation | Create a new cart with optional lines |
cartLinesAdd |
mutation | Append lines to an existing cart |
cartLinesUpdate |
mutation | Update line quantities/variants |
cartLinesRemove |
mutation | Remove lines from a cart |
cartBuyerIdentityUpdate |
mutation | Attach email/phone/customer to cart |
checkoutCreate |
mutation | Create a checkout with line items |
checkoutLineItemsAdd |
mutation | Append items to a checkout |
checkoutLineItemsUpdate |
mutation | Update checkout item quantities |
checkoutLineItemsRemove |
mutation | Remove items from a checkout |
checkoutShippingAddressUpdateV2 |
mutation | Set/update the shipping address |
checkoutEmailUpdateV2 |
mutation | Set/update the buyer email |
checkoutCompleteFree |
mutation | Complete a zero-total checkout |
customerCreate |
mutation | Register a new buyer account |
customerUpdate |
mutation | Update an authenticated buyer's profile |
customerAccessTokenCreate |
mutation | Authenticate buyer — returns token |
customerAccessTokenDelete |
mutation | Invalidate a buyer token (log out) |
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" }
App Settings API
The App Settings API lets merchants configure your app without touching code.
You declare a settings schema on your app listing — IndeCommerce automatically
renders a form on the app detail page. Saved values are validated, persisted per-install,
and injected into every Liquid theme extension render as the app_settings object.
How it works
- You declare a
settings_schemaJSON array on yourAppListing(via your install script or the Developer Portal). - IndeCommerce renders a schema-driven settings form on the merchant's app detail page.
- The merchant saves their values; they are stored per-install as a JSON object.
- At render time, defaults and merchant values are merged and injected as
app_settingsinto every theme extension Liquid template. - Your app can also read/write settings programmatically via the REST API.
Schema reference
settings_schema is a JSON array of field definition objects.
Set it on your listing in your install script:
listing.settings_schema = json.dumps([
{
"key": "primary_color", # used in Liquid: {{ app_settings.primary_color }}
"type": "color",
"label": "Accent Color",
"default": "#1a1a1a",
"description": "Checkout button and loading spinner color"
},
{
"key": "drawer_width",
"type": "number",
"label": "Drawer Width (px)",
"default": 420,
"min": 280,
"max": 600
},
{
"key": "font_family",
"type": "text",
"label": "Font Family",
"default": "",
"max_length": 200,
"description": "Leave blank to inherit the theme font"
}
])
Supported field types
| Type | Merchant input | Validation |
|---|---|---|
color | Color picker + hex text box | Must match #RGB or #RRGGBB |
text | Single-line text input | Truncated to max_length (default 500) |
textarea | Multi-line text area | Truncated to max_length |
number | Numeric input | Clamped to min/max if set |
boolean | Checkbox | Stored as true/false |
select | Drop-down list | Must be one of the declared options[].value strings |
Field object properties:
| Property | Required | Description |
|---|---|---|
key | Yes | Snake_case identifier. Used in values and in Liquid. |
type | Yes | One of the types listed above. |
label | Yes | Human-readable label shown in the form. |
default | No | Value returned when the merchant hasn't saved anything. |
description | No | Help text displayed below the field. |
min / max | No | Inclusive numeric bounds (number only). |
options | select only | Array of {"label": "...", "value": "..."} objects. |
max_length | No | Max character length for text/textarea (default 500). |
Liquid access
Inside any theme extension Liquid template, schema defaults merged with merchant values are available as
app_settings. Use Liquid's | default: filter for safety:
{% comment %} App Settings → CSS custom properties {% endcomment %}
<style>
:root {
--accent: {{ app_settings.primary_color | default: '#1a1a1a' }};
--width: {{ app_settings.drawer_width | default: 420 }}px;
--radius: {{ app_settings.border_radius | default: 8 }}px;
--font: {{ app_settings.font_family | default: "inherit" }};
}
</style>
<div style="max-width: var(--width);">
<button style="background: var(--accent);">Checkout</button>
</div>
REST API
OAuth apps can read and write settings programmatically. Both endpoints are scoped to the authenticated install.
GET /admin/api/2024-01/settings.json
Returns the schema and merged values. Requires the read_settings scope.
GET /admin/api/2024-01/settings.json
X-IndeCommerce-Access-Token: ica_<token>
HTTP/1.1 200 OK
{
"app_settings": {
"schema": [ { "key": "primary_color", "type": "color", ... } ],
"values": { "primary_color": "#4f46e5", "drawer_width": 380 }
}
}
PUT /admin/api/2024-01/settings.json
Merges the supplied key/value pairs. Unknown keys are ignored. Requires the write_settings scope.
PUT /admin/api/2024-01/settings.json
X-IndeCommerce-Access-Token: ica_<token>
Content-Type: application/json
{ "settings": { "primary_color": "#4f46e5", "drawer_width": 380 } }
HTTP/1.1 200 OK
{ "app_settings": { "values": { "primary_color": "#4f46e5", "drawer_width": 380 } } }
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)
- Storefront Cart REST API —
GET /api/cart,POST /api/cart/add,/api/cart/update,/api/cart/remove,GET /api/cart/count - Storefront GraphQL expanded — full cart mutations (
cartCreate,cartLinesAdd/Update/Remove,cartBuyerIdentityUpdate), checkout mutations, and customer mutations now documented with examples - App Settings API — declarative
settings_schemaon app listings, per-install merchant values, schema-driven settings form on app detail page,app_settingsLiquid context variable in theme extensions, andGET/PUT /admin/api/2024-01/settings.jsonREST endpoints (read_settings/write_settingsscopes)