BigCommerce REST API Adapter
v2/v3 Compatible
Last updated: May 15, 2026
A drop-in compatible REST API for STOAR that mirrors BigCommerce v2 (/api/v2/orders) and v3 (/api/v3/catalog/products) — same URL paths, same RFC 2822 dates, same status_id numeric vocabulary, same { data, meta.pagination } envelope on v3, same X-Pagination-* headers on v2, same X-Auth-Token header. Existing BigCommerce client libraries (bigcommerce/api Node SDK, bigcommerce-api-php, BigCommerce Python) talk to STOAR with no modification.
This is the fourth implementation of STOAR's extensible API adapter framework (after Magento, WooCommerce, and Shopify). It demonstrates that the architecture handles two version-flavours under one adapter (v2 + v3) without duplicating any business logic.
Table of contents #
- Quick start
- Authentication
- Endpoints
- Query parameters
- Field mapping (BigCommerce ↔ STOAR)
- Status translation
- Response shape
- Real-world example
- Error shape
- Rate limiting
Quick start #
TOKEN="5|abcdef…" # Sanctum personal-access token with bigcommerce:admin ability
# v2 — orders
curl -H "X-Auth-Token: $TOKEN" \
"https://app.stoar.ai/api/v2/orders?limit=5" | jq '.[] | {id, status_id, status, total_inc_tax}'
# v2 — line items for a specific order (separate sub-endpoint)
curl -H "X-Auth-Token: $TOKEN" \
"https://app.stoar.ai/api/v2/orders/10126/products" | jq '.[].name'
# v3 — catalog products with pagination meta envelope
curl -H "X-Auth-Token: $TOKEN" \
"https://app.stoar.ai/api/v3/catalog/products?limit=2&page=1" | jq '.meta.pagination'
# v3 — single product
curl -H "X-Auth-Token: $TOKEN" \
"https://app.stoar.ai/api/v3/catalog/products/789"
Bearer auth (-H "Authorization: Bearer $TOKEN") is also accepted as a fallback.
Authentication #
BigCommerce's canonical auth header is X-Auth-Token: <token>. STOAR's adapter accepts it and a Bearer fallback:
| Method | Format | Use case |
|---|---|---|
| X-Auth-Token | X-Auth-Token: <sanctum-token> |
Default for all BigCommerce client libraries |
| Bearer (STOAR extension) | Authorization: Bearer <sanctum-token> |
Native Sanctum — interchangeable with the other adapters |
Tokens are issued from STOAR's /manager/api-tokens page or programmatically via AdminUser::createToken('label', ['bigcommerce:admin']). A single token can carry every adapter's ability simultaneously (bigcommerce:admin + magento:admin + shopify:admin + woocommerce:admin).
The AuthTokenMiddleware runs before auth:sanctum and promotes X-Auth-Token into a Authorization: Bearer … header. From STOAR's perspective every BC request is a normal Sanctum-authenticated request.
OAuth client-credential flow (real BC apps go through /auth/load and /oauth2/token) is not supported.
Endpoints #
All paths are relative to /api. v2 endpoints live under /api/v2; v3 endpoints under /api/v3.
GET /api/v2/orders
List orders — flat array body + pagination headers.
Authorization: bigcommerce:admin.
Query: see Query parameters.
Response:
HTTP/1.1 200 OK
X-Pagination-Total-Count: 150
X-Pagination-Page-Total: 3
Content-Type: application/json
[
{ /* order object — see Response shape */ }
]
Why headers, not envelope? This is BC v2's actual convention. v3 endpoints use the
{ data, meta }envelope (see catalog products below). Real BC client libraries handle both.
GET /api/v2/orders/{id}
Single order — bare object (NO envelope on v2).
Errors:
{ "status": 404, "title": "The order requested could not be found.", "type": "..." }
GET /api/v2/orders/{id}/products
Note — line items live at a SEPARATE endpoint, not embedded on the order resource. This is one of BC's bigger differences from Magento, WooCommerce, and Shopify (all of which inline line items).
Response: flat array of BC v2 line-item objects.
[
{
"id": 30219,
"order_id": 10126,
"product_id": 112238,
"variant_id": 95589,
"name": "Widget",
"sku": "WID-1-A",
"type": "physical",
"base_price": 299,
"price_ex_tax": 299,
"price_inc_tax": 299,
"base_total": 897,
"total_ex_tax": 897,
"total_inc_tax": 897,
"quantity": 3,
"is_refunded": false,
"product_options": [],
"..."
}
]
GET /api/v3/catalog/products
List catalog products — v3 envelope with data + meta.pagination.
Query: see Query parameters.
Response (200):
{
"data": [
{ /* product — see Response shape */ }
],
"meta": {
"pagination": {
"total": 150,
"count": 50,
"per_page": 50,
"current_page": 1,
"total_pages": 3,
"links": {
"current": "https://.../api/v3/catalog/products?page=1&limit=50",
"next": "https://.../api/v3/catalog/products?page=2&limit=50"
},
"too_many": false
}
}
}
links.previous is included on pages > 1; links.next on all but the last page.
GET /api/v3/catalog/products/{id}
Single product — v3 envelope with data (single object) + empty meta.
Response:
{
"data": { /* product — see Response shape */ },
"meta": {}
}
Query parameters #
Common (orders + products)
| Param | Default | Purpose |
|---|---|---|
limit |
50 |
items per page (max 250) |
page |
1 |
page number, 1-indexed |
v2 orders only
| Param | Example | Effect |
|---|---|---|
status_id |
11 |
Numeric BC status code → translated to Stoar status |
customer_id |
42 |
Filter by customer |
min_id / max_id |
100 |
id range |
min_date_created / max_date_created |
2025-01-01T00:00:00 |
created_at range |
sort |
id:desc |
<field>:<asc\|desc>. Fields: id, date_created, date_modified, total_inc_tax |
v3 catalog products only
V3 supports BC's richer operator suffix syntax: id:in, price:min, etc.
| Param | Example | Effect |
|---|---|---|
id |
42 |
exact id |
id:in |
1,2,3 |
id IN list |
id:not_in |
4,5 |
id NOT IN list |
sku |
WID-1 |
exact sku |
name |
Widget |
exact name |
keyword |
widget |
LIKE substring on name |
is_visible |
true / false |
translates to active / inactive |
is_featured |
true / false |
filter on is_featured |
categories |
5 |
exact category id |
price:min |
10 |
price >= |
price:max |
100 |
price <= |
sort |
name, -price, -date_created, id |
leading - flips direction |
Unknown operators or fields are silently ignored — same as BC behaviour.
Field mapping (BigCommerce ↔ STOAR) #
Order (v2)
| BC v2 field | STOAR source | Notes |
|---|---|---|
id |
id |
numeric |
status_id |
status (translated) |
numeric BC code (1, 11, 7, 3, 10, 5, 4) — see Status translation |
status |
status (translated) |
string label ("Pending", "Awaiting Fulfillment", "Shipped", …) |
customer_id |
customer_id ?? 0 |
guest orders get 0 |
date_created, date_modified |
created_at, updated_at |
RFC 2822 format (NOT ISO 8601) |
date_shipped |
updated_at if status='shipped'/'delivered' |
empty string otherwise |
currency_code, default_currency_code |
currency (uppercased) |
e.g. EUR |
currency_exchange_rate |
always '1.0000000000' |
Stoar is single-currency |
total_inc_tax |
total_amount |
NUMERIC float (NOT a string — that's V3 / WC behaviour) |
total_ex_tax |
total_amount - tax_amount |
|
total_tax |
tax_amount |
|
subtotal_ex_tax |
subtotal |
|
subtotal_inc_tax |
subtotal + tax_amount |
|
subtotal_tax |
tax_amount |
|
shipping_cost_ex_tax, shipping_cost_inc_tax, base_shipping_cost |
shipping_amount |
|
discount_amount, coupon_discount |
discount_amount |
|
payment_method |
first non-archived OrderPayment.gateway |
|
payment_status |
'captured' if payment_status='succeeded' |
empty otherwise |
refunded_amount |
refunded_amount ?? 0 |
|
staff_notes |
admin_notes |
|
customer_message |
customer_notes |
|
billing_address |
billing_info JSON |
nested object — flat shape with street_1/street_2 keys |
products |
sub-resource | {url, resource} pointer to /orders/{id}/products |
shipping_addresses |
sub-resource | {url, resource} pointer (not implemented) |
coupons |
sub-resource | {url, resource} pointer (not implemented) |
items_total |
sum(items.quantity) |
|
items_shipped |
sum(items.quantity) if status='shipped'/'delivered', else 0 |
|
customer_locale |
always 'en' |
not stored |
channel_id |
always 1 |
not modelled |
Order line_item (v2 /products)
| BC v2 field | Stoar source |
|---|---|
id |
OrderItem.id |
order_id |
OrderItem.order_id |
product_id |
OrderItem.product_id |
variant_id |
OrderItem.variant_id |
name, name_customer, name_merchant |
OrderItem.name |
sku |
variant SKU first, falls back to product |
type |
always 'physical' |
base_price, price_ex_tax |
OrderItem.price |
price_inc_tax |
price + (tax_amount / quantity) |
base_total, total_ex_tax |
price * quantity |
total_inc_tax |
(price * quantity) + tax_amount |
total_tax |
OrderItem.tax_amount |
quantity |
OrderItem.quantity |
product_options[] |
one element with variant name (when not Default) |
weight, width, height, depth |
always 0 |
is_refunded, quantity_refunded, refund_amount |
always false/0 (Stoar tracks refund at order-level only) |
Catalog product (v3)
| BC v3 field | STOAR source | Notes |
|---|---|---|
id |
id |
|
name, description, sku |
passthrough | |
type |
always 'physical' |
BC also has 'digital'; Stoar is physical-only |
is_visible |
status === 'active' |
bool |
availability |
'available' if active else 'disabled' |
enum |
is_featured |
is_featured |
bool |
inventory_tracking |
'product' for simple, 'variant' for configurable |
|
inventory_level |
stock (sum of variants for configurable) |
|
inventory_warning_level |
low_stock_threshold ?? 0 |
|
price |
price (raw) |
NUMERIC float |
sale_price |
special_price ?? 0 |
float |
calculated_price |
min(price, special_price) | float |
weight |
weight ?? 0 |
float |
tax_class_id |
tax_class_id ?? 0 |
|
categories |
[category_id] |
array of single int (NOT nested objects) |
base_variant_id |
first variant's id | |
images[] |
image_path + gallery_paths |
inline array |
variants[] |
inline | full variant objects |
custom_url |
{url: "/{slug}", is_customized: false, create_redirect: false} |
|
page_title, meta_description, meta_keywords |
meta_title, meta_description, [] |
|
date_created, date_modified |
created_at, updated_at |
ISO 8601 (v3 uses ISO; v2 uses RFC 2822) |
condition |
always 'New' |
not modelled |
total_sold, view_count, reviews_rating_sum, reviews_count |
always 0 |
not aggregated |
related_products |
always [-1] |
BC convention for "auto-related" |
brand_id |
always null |
Stoar has no brand model |
Catalog product variant (v3)
| BC v3 field | Stoar source |
|---|---|
id |
Variant.id |
product_id |
Variant.product_id |
sku |
Variant.sku |
price, calculated_price |
Variant.price |
inventory_level |
Variant.stock |
weight, calculated_weight |
Variant.weight |
purchasing_disabled |
!Variant.is_active |
image_url |
Variant.image_path ?? "" |
option_values[] |
always [] (Stoar variant attributes use a different model) |
Status translation #
BigCommerce uses NUMERIC status_id codes plus a parallel string status field. The translator decomposes Stoar's enum into both.
Stoar → BigCommerce (rendering)
Stoar status |
status_id |
status (string) |
|---|---|---|
pending |
1 | Pending |
paid |
11 | Awaiting Fulfillment |
processing |
7 | Awaiting Pickup |
shipped |
3 | Shipped |
delivered |
10 | Completed |
cancelled |
5 | Cancelled |
refunded |
4 | Refunded |
BigCommerce filter → Stoar (parsing)
?status_id=N is translated using the same lookup table. Codes that don't match anything in the map (e.g. BC's status 2 = "Manually Verified" or 6 = "Declined") fall through silently — matching BC's "no exact equivalent" behaviour. Real BC's full code map has ~14 entries; STOAR exposes the seven that map cleanly.
Product status
BigCommerce has no string status field — it uses is_visible (bool) + availability enum. Filter via ?is_visible=true for active products, ?is_visible=false for inactive.
Response shape #
v2 order — annotated
{
"id": 456,
"customer_id": 45,
"date_created": "Tue, 03 Jun 2025 04:56:43 +0000",
"date_modified": "Tue, 03 Jun 2025 04:56:43 +0000",
"date_shipped": "",
"status_id": 11,
"status": "Awaiting Fulfillment",
"subtotal_ex_tax": 100,
"subtotal_inc_tax": 110,
"subtotal_tax": 10,
"total_ex_tax": 105,
"total_inc_tax": 115,
"total_tax": 10,
"items_total": 2,
"items_shipped": 0,
"payment_method": "stripe",
"payment_status": "captured",
"refunded_amount": 0,
"currency_code": "EUR",
"currency_exchange_rate": "1.0000000000",
"default_currency_code": "EUR",
"billing_address": {
"first_name": "Jane",
"last_name": "Doe",
"company": "",
"street_1": "1 Test St",
"street_2": "",
"city": "Berlin",
"state": "",
"zip": "10115",
"country": "Germany",
"country_iso2": "DE",
"phone": "+49…",
"email": "[email protected]",
"form_fields": []
},
"products": {
"url": "https://.../api/v2/orders/456/products",
"resource": "/orders/456/products"
},
"shipping_addresses": {
"url": "https://.../api/v2/orders/456/shippingaddresses",
"resource": "/orders/456/shippingaddresses"
},
"coupons": {
"url": "https://.../api/v2/orders/456/coupons",
"resource": "/orders/456/coupons"
},
"store_default_currency_code": "EUR",
"channel_id": 1,
"..."
}
v3 catalog product — abbreviated
{
"data": {
"id": 789,
"name": "Widget",
"type": "physical",
"sku": "WID-1",
"description": "<p>A great widget</p>",
"weight": 0.5,
"price": 99.99,
"sale_price": 0,
"retail_price": 0,
"calculated_price": 99.99,
"categories": [5],
"is_visible": true,
"is_featured": true,
"availability": "available",
"inventory_tracking": "product",
"inventory_level": 42,
"page_title": "Widget – buy now",
"custom_url": {
"url": "/widget",
"is_customized": false,
"create_redirect": false
},
"images": [
{ "id": 0, "image_file": "products/widget.webp", "is_thumbnail": true, "sort_order": 0, "url_thumbnail": "products/widget.webp", "url_zoom": "products/widget.webp", "..." }
],
"variants": [
{ "id": 12, "product_id": 789, "sku": "WID-RED", "price": 99.99, "inventory_level": 22, "purchasing_disabled": false, "..." }
]
},
"meta": {}
}
Real-world example #
GET /api/v2/orders/10126 against production. Same Stoar order featured in Magento, WooCommerce, and Shopify — rendered in BC v2 shape. PII anonymised.
{
"id": 10126,
"customer_id": 5794,
"date_created": "Tue, 03 Jun 2025 04:56:43 +0000",
"date_modified": "Tue, 03 Jun 2025 04:56:43 +0000",
"date_shipped": "",
"status_id": 11,
"status": "Awaiting Fulfillment",
"subtotal_ex_tax": 936.98,
"subtotal_inc_tax": 936.98,
"subtotal_tax": 0,
"shipping_cost_ex_tax": 0,
"shipping_cost_inc_tax": 0,
"shipping_cost_tax": 0,
"total_ex_tax": 936.98,
"total_inc_tax": 936.98,
"total_tax": 0,
"items_total": 5,
"items_shipped": 0,
"payment_method": "payid",
"payment_provider_id":null,
"payment_status": "captured",
"refunded_amount": 0,
"order_is_digital": false,
"currency_id": 1,
"currency_code": "USD",
"currency_exchange_rate": "1.0000000000",
"default_currency_id": 1,
"default_currency_code": "USD",
"staff_notes": "",
"customer_message": "",
"discount_amount": 0,
"coupon_discount": 0,
"shipping_address_count": 1,
"is_deleted": false,
"ebay_order_id": "0",
"cart_id": null,
"billing_address": {
"first_name": "Jane",
"last_name": "Doe",
"company": "",
"street_1": "1 Example Street",
"street_2": "",
"city": "Phoenix",
"state": "AZ",
"zip": "85001",
"country": "United States",
"country_iso2": "US",
"phone": "+1-555-0100",
"email": "",
"form_fields": []
},
"products": {
"url": "https://app.stoar.ai/api/v2/orders/10126/products",
"resource": "/orders/10126/products"
},
"shipping_addresses": {
"url": "https://app.stoar.ai/api/v2/orders/10126/shippingaddresses",
"resource": "/orders/10126/shippingaddresses"
},
"coupons": {
"url": "https://app.stoar.ai/api/v2/orders/10126/coupons",
"resource": "/orders/10126/coupons"
},
"store_default_currency_code": "USD",
"store_default_to_transactional_exchange_rate": "1.0000000000",
"custom_status": "Awaiting Fulfillment",
"channel_id": 1,
"ip_address": "",
"ip_address_v6": "",
"geoip_country": "",
"geoip_country_iso2": "",
"is_email_opt_in": false,
"credit_card_type": null,
"order_source": "www",
"external_source": null,
"external_id": null,
"external_merchant_id":null,
"tax_provider_id": "",
"customer_locale": "en",
"external_order_id": ""
}
Caveats integrators should know
| Field | Observed | Why |
|---|---|---|
date_created, date_modified |
RFC 2822 string | v2 specifically uses RFC 2822 (Tue, 03 Jun 2025 04:56:43 +0000). v3 endpoints use ISO 8601 — the inconsistency is BC's, we mirror it. |
payment_method |
"payid" |
Stoar's gateway-key — not a BC-canonical method name. |
payment_status |
"captured" for paid orders |
BC has its own status vocabulary; we map succeeded → captured. |
total_inc_tax, total_ex_tax, etc. |
floats (e.g. 936.98) |
BC v2 renders money as floats — distinct from WooCommerce / Shopify which use 2-dp strings. |
country |
"United States" |
Long name, derived from ISO-2 via a small lookup table (DE/US/GB/AU/AT/CH). Other countries return "". |
products, shipping_addresses, coupons |
{url, resource} sub-resource pointers |
BC convention for "follow this URL to fetch related data". /orders/{id}/products is implemented; the others are not yet available. |
customer_locale |
always "en" |
not stored on Stoar Customer. |
channel_id |
always 1 |
Stoar is single-store; BC supports multi-channel. |
currency_exchange_rate |
always "1.0000000000" |
Stoar single-currency. |
staff_notes, customer_message |
admin_notes, customer_notes |
BC's terminology. |
external_*, ebay_order_id, cart_id |
empty / null / "0" | Stoar has no marketplace integration. |
Error shape #
BigCommerce uses an RFC 7807-style envelope:
{
"status": 404,
"title": "The order requested could not be found.",
"type": "https://developer.bigcommerce.com/api-docs/getting-started/api-status-codes",
"errors": { "<field>": "..." }
}
type always points to BC's status-codes documentation page. errors is only present on validation failures (400).
| HTTP | Trigger | title |
|---|---|---|
| 400 | bad query / validation | Invalid query parameters. (with errors map) |
| 401 | missing or invalid token | Not authenticated. |
| 403 | wrong-ability token | Insufficient OAuth scope. |
| 404 | unknown resource | The {resource} requested could not be found. |
| 422 | precondition failed | dynamic message |
| 429 | rate-limit exceeded | Too many requests. (with X-Rate-Limit-Time-Reset-Ms and X-Rate-Limit-Requests-Left headers) |
| 500 | unexpected server error | Internal Server Error |
Rate limiting #
The same api-rest Sanctum-token-keyed throttle that protects all other adapters protects BC routes. Configure both the threshold and the master switch at /manager/api-settings. When triggered, the response uses BC's RFC 7807 envelope:
HTTP/1.1 429 Too Many Requests
X-Rate-Limit-Time-Reset-Ms: 60000
X-Rate-Limit-Requests-Left: 0
Content-Type: application/json
{
"status": 429,
"title": "Too many requests.",
"type": "https://developer.bigcommerce.com/api-docs/getting-started/api-status-codes"
}
The X-Rate-Limit-* headers match BC's documented behaviour. Real BC returns more headers and uses second-resolution windows; STOAR uses Laravel's minute window — close enough for client-library compatibility.
A single Sanctum token used across all four adapters shares one bucket — see Magento's Rate limiting section for the configuration UI walkthrough.
See also #
app/Api/Adapters/BigCommerce/Support/StatusTranslator.php— bidirectional vocabulary mapping- BigCommerce REST API reference — the upstream spec this adapter mirrors