Skip to main content
The Store API uses a consistent, Stripe-style error format across all endpoints. Every error response includes a machine-readable code and a human-readable message.

Error Response Format

All errors return a JSON object with a single error key:
{
  "error": {
    "code": "record_not_found",
    "message": "Product not found"
  }
}
Validation errors include an additional details field with per-field error messages:
{
  "error": {
    "code": "validation_error",
    "message": "Name can't be blank and Email is invalid",
    "details": {
      "name": ["can't be blank"],
      "email": ["is invalid"]
    }
  }
}

Schema

FieldTypeDescription
error.codestringMachine-readable error code (see table below)
error.messagestringHuman-readable description of the error
error.detailsobjectField-specific validation errors. Each key is a field name, each value is an array of error strings. Only present for validation errors.

HTTP Status Codes

StatusMeaningWhen
400Bad RequestMissing required parameters, malformed JSON, invalid arguments
401UnauthorizedMissing or invalid API key, expired JWT token
403ForbiddenAuthenticated but not authorized for this resource
404Not FoundResource doesn’t exist or isn’t accessible
422Unprocessable ContentValidation failed, invalid state transition, payment error
429Too Many RequestsRate limit exceeded (see Rate Limiting)

Error Codes

Authentication & Authorization

CodeStatusDescription
authentication_required401Request requires a valid JWT token
authentication_failed401Email or password is incorrect
invalid_token401API key or JWT token is invalid or expired
invalid_provider400OAuth provider not recognized
access_denied403User doesn’t have permission for this action

Resource Errors

CodeStatusDescription
record_not_found404Resource doesn’t exist or isn’t accessible in the current store
resource_invalid422Resource couldn’t be saved

Validation Errors

CodeStatusDescription
validation_error422Model validation failed. Check details for field-specific messages.
parameter_missing400A required parameter is missing
parameter_invalid400A parameter has an invalid value

Order Errors

CodeStatusDescription
order_not_found404Order doesn’t exist or doesn’t belong to the current user/guest
order_already_completed422Cannot modify an order that has already been completed
order_cannot_transition422Order can’t move to the requested state (e.g., advancing without an address)
order_empty422Order has no line items
order_invalid_state422Order is in an invalid state for this operation

Line Item & Stock Errors

CodeStatusDescription
line_item_not_found404Line item doesn’t exist in this order
variant_not_found404Variant doesn’t exist or isn’t available
insufficient_stock422Not enough stock to fulfill the requested quantity
invalid_quantity422Quantity must be a positive integer

Payment Errors

CodeStatusDescription
payment_failed422Payment was declined or couldn’t be processed
payment_processing_error422Error communicating with the payment gateway
gateway_error422Payment gateway returned an error

Digital Download Errors

CodeStatusDescription
attachment_missing403File attachment not found for this digital product
download_unauthorized403User is not authorized to download this file
digital_link_expired403Download link has expired
download_limit_exceeded403Maximum number of downloads reached

Other Errors

CodeStatusDescription
rate_limit_exceeded429Too many requests. Retry after the Retry-After header value.
request_too_large413Request body exceeds the size limit
processing_error422Generic server-side processing error
invalid_request400Request is malformed (e.g., invalid JSON body)

Examples

Not Found

curl https://api.mystore.com/api/v3/store/products/prod_nonexistent \
  -H "Authorization: Bearer spree_pk_xxx"
404
{
  "error": {
    "code": "record_not_found",
    "message": "Product not found"
  }
}

Validation Error

curl -X POST https://api.mystore.com/api/v3/store/account/addresses \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{}'
422
{
  "error": {
    "code": "validation_error",
    "message": "First name can't be blank, Address can't be blank, City can't be blank, and Country can't be blank",
    "details": {
      "firstname": ["can't be blank"],
      "address1": ["can't be blank"],
      "city": ["can't be blank"],
      "country": ["can't be blank"]
    }
  }
}

Insufficient Stock

curl -X POST https://api.mystore.com/api/v3/store/cart/line_items \
  -H "Authorization: Bearer spree_pk_xxx" \
  -H "X-Spree-Order-Token: abc123" \
  -H "Content-Type: application/json" \
  -d '{"variant_id": "var_xxx", "quantity": 999}'
422
{
  "error": {
    "code": "insufficient_stock",
    "message": "Quantity selected exceeds available stock"
  }
}

Invalid State Transition

curl -X PATCH https://api.mystore.com/api/v3/store/orders/or_xxx/advance \
  -H "Authorization: Bearer spree_pk_xxx" \
  -H "X-Spree-Order-Token: abc123"
422
{
  "error": {
    "code": "order_cannot_transition",
    "message": "Cannot transition state via :next from :cart"
  }
}

Handling Errors in the SDK

The @spree/sdk package throws a SpreeError for all non-2xx responses:
import { SpreeError } from '@spree/sdk';

try {
  const product = await client.store.products.get('nonexistent');
} catch (error) {
  if (error instanceof SpreeError) {
    console.log(error.code);    // 'record_not_found'
    console.log(error.message); // 'Product not found'
    console.log(error.status);  // 404
    console.log(error.details); // undefined (or field errors for validation)
  }
}

Common Patterns

Handle specific error codes:
try {
  await client.store.orders.lineItems.create(orderId, {
    variant_id: variantId,
    quantity: 1,
  });
} catch (error) {
  if (error instanceof SpreeError) {
    switch (error.code) {
      case 'insufficient_stock':
        showNotification('This item is out of stock');
        break;
      case 'variant_not_found':
        showNotification('This product is no longer available');
        break;
      case 'order_already_completed':
        // Cart was already checked out — create a new one
        await createNewCart();
        break;
      default:
        showNotification(error.message);
    }
  }
}
Display validation errors per field:
try {
  await client.store.customer.addresses.create(addressData);
} catch (error) {
  if (error instanceof SpreeError && error.details) {
    // error.details = { city: ["can't be blank"], zipcode: ["is invalid"] }
    for (const [field, messages] of Object.entries(error.details)) {
      setFieldError(field, messages.join(', '));
    }
  }
}