Reference: API-01

Error codes

SaveLayer ingress APIs return a typed JSON envelope on failure. Use the machine-readable code for branching; treat default messages as human copy that may evolve.

01 / ENVELOPE

Error response shape

Failed requests use ok: false with an error object validated by ProxyErrorSchema in @savelayer/contracts. The HTTP status reflects the error category (401 auth, 404 missing resource, 409 conflict, etc.).

retryable is reserved for cases where a safe automatic retry may help. Today, unexpected server errors set retryable: true; most other codes default to false.

application/json 402
{
  "ok": false,
  "error": {
    "code": "PLAN_LIMIT_REACHED",
    "message": "The plan limit has been reached.",
    "retryable": false
  }
}
02 / PATTERNS

Client handling patterns

  • Branch on error.code, not on message. Messages are for display and may change.
  • Check HTTP status alongside the body for logging, CDN behavior, and HTTP client defaults—but still parse the JSON envelope when present.
  • Idempotent saves: treat DUPLICATE_SAVE as a successful “already saved” outcome in UI when appropriate.
  • Validation: when VALIDATION_ERROR is returned, the message may include a more specific hint (e.g. invalid payload shape).
  • Retries: only retry when retryable is true or for known transient HTTP statuses (429, 502), with backoff and jitter.

Possible validation detail strings when code is VALIDATION_ERROR:

  • Invalid save payload.
  • Invalid remove payload.
  • Invalid toggle payload.
  • Invalid list query.
  • Invalid batch payload.
  • Invalid is-saved query.
  • Invalid profile payload.
  • Invalid list management payload.
03 / CODES

Proxy error codes

The table lists each error code, typical HTTP status, and how to handle it in client code.

AUTH_REQUIRED

HTTP 401

Default message Sign in to save items.

When it occurs

The storefront customer is not signed in; app proxy requests lack a valid `logged_in_customer_id` on the signed URL; or the direct-API bearer token is missing, invalid, or expired.

Client handling

Show a sign-in prompt and retry after login. For app proxy, only call SaveLayer when Liquid/session indicates a logged-in customer. For headless/customer-account, re-exchange for a fresh bearer token.

CUSTOMER_REQUIRED

HTTP 401

Default message Customer must be logged in to access this resource.

When it occurs

Not emitted by SaveLayer app proxy or direct API ingress today. Reserved in the contract for a future distinct case (e.g. shop expects a different login mode). Proxy integrations: expect AUTH_REQUIRED (401) when the customer is not logged in.

Client handling

If you ever receive this code, handle like AUTH_REQUIRED. New integrations should key on AUTH_REQUIRED for missing customer context on the Online Store channel.

INVALID_PROXY_SIGNATURE

HTTP 401

Default message The app proxy signature is invalid.

When it occurs

Shopify’s app-proxy HMAC verification failed (query params or shared secret mismatch).

Client handling

Never construct proxy URLs manually without Shopify’s signed parameters. Use theme/app extension links or Liquid-generated app proxy URLs. Merchants: reinstall or confirm app proxy URL and secret in Partner settings.

INVALID_HEADLESS_SIGNATURE

HTTP 401

Default message The headless authorization signature is invalid.

When it occurs

The headless channel request failed cryptographic verification (wrong secret, stale timestamp, or malformed signature).

Client handling

Regenerate the request using the documented signing algorithm and current credentials. Rotate secrets if compromised. Do not expose signing keys in browser bundles.

INVALID_LEGACY_STOREFRONT_TOKEN

HTTP 401

Default message The legacy Storefront customer access token is invalid or could not resolve a customer.

When it occurs

The legacy Storefront customer access token or the shop's Storefront access token is invalid, expired, or could not resolve a customer via the Storefront API.

Client handling

Verify both the customer access token and the shop's public Storefront access token are current. Prefer migrating to the modern Customer Account API path when possible.

INVALID_EXTENSION_SESSION

HTTP 401

Default message The customer account session is invalid.

When it occurs

The customer account extension session token was missing, expired, or failed validation.

Client handling

Refresh the extension session or ask the shopper to re-open the account UI. Avoid caching tokens beyond their lifetime.

SHOP_NOT_INSTALLED

HTTP 403

Default message The shop is not fully installed.

When it occurs

The shop domain is known but SaveLayer is not installed or the install record is incomplete.

Client handling

Merchant must complete app installation from the Shopify admin. Storefronts should hide or disable save UI when the app is unavailable.

FORBIDDEN

HTTP 403

Default message You do not have access to this resource.

When it occurs

Authenticated context exists but the operation is not allowed for this customer or resource (authorization failure).

Client handling

Do not retry blindly. Confirm the customer owns the list/item or that the correct list scope is used. Show a generic “access denied” message.

LIST_NOT_FOUND

HTTP 404

Default message The requested list could not be found.

When it occurs

The referenced save list does not exist or is not visible to this customer.

Client handling

Refresh list state from the server. If the user bookmarked an old list ID, redirect to the default list or an empty state.

ITEM_NOT_FOUND

HTTP 404

Default message The requested item could not be found.

When it occurs

Remove, toggle, or update targeted a save item that does not exist (or was already removed).

Client handling

Treat as idempotent success for “remove” UX when appropriate, or refresh the list UI. Avoid assuming the item still exists after errors.

VALIDATION_ERROR

HTTP 400

Default message The request payload is invalid.

When it occurs

JSON body failed schema validation, required fields are missing, or values are out of allowed shape (including invalid JSON).

Client handling

Log the response body in development only. Fix payloads to match the published request schemas (entity type, GIDs, limits). Surface a generic “something went wrong” for shoppers.

UNSUPPORTED_ENTITY_TYPE

HTTP 400

Default message The requested entity type is not supported.

When it occurs

The requested entity type is not in SaveLayer’s supported set for this API version.

Client handling

Use only supported entity types from the integration docs. Gate UI so unsupported product shapes never call save APIs.

INDEX_UPDATE_DEFERRED

HTTP 400

Default message The index update was deferred.

When it occurs

An index or denormalized customer metafield update was intentionally deferred (operational soft-failure).

Client handling

Optional refresh after a short delay. Core save/list data in metaobjects remains authoritative; index lag should not block primary UX.

DUPLICATE_SAVE

HTTP 409

Default message The item is already saved.

When it occurs

The customer attempted to save an entity that is already present in the target list.

Client handling

Update UI to “saved” state without alarming the user. Safe to treat as success for idempotent “save” buttons.

BATCH_CONFLICT

HTTP 409

Default message Batch contains conflicting operations on the same item.

When it occurs

A batch request contains multiple operations targeting the same item (same context, entity type, and entity GID).

Client handling

Deduplicate operations client-side before sending. Each item should appear at most once per batch request.

BATCH_NOT_ALLOWED

HTTP 403

Default message Batch operations are not available on your current plan.

When it occurs

The merchant's plan does not include batch operations (e.g. Free plan).

Client handling

Fall back to individual save/remove calls, or prompt the merchant to upgrade to a plan that supports batch operations.

BATCH_TOO_LARGE

HTTP 422

Default message Batch size exceeds your plan's limit.

When it occurs

The number of operations in the batch exceeds the plan's maxBatchSize limit.

Client handling

Split the batch into smaller chunks within the plan's batch size limit. Check the plan's maxBatchSize via the usage endpoint.

RATE_LIMITED

HTTP 429

Default message Too many requests. Please try again shortly.

When it occurs

Request volume exceeded configured rate limits for the shop or route.

Client handling

Honor Retry-After if present; otherwise exponential backoff. Debounce rapid clicks on save buttons and batch operations where possible.

PLAN_LIMIT_REACHED

HTTP 402

Default message The plan limit has been reached.

When it occurs

The merchant’s plan or usage caps block further writes (billing / entitlement).

Client handling

Show merchant-facing messaging in admin; storefront can show a soft capacity message. Merchants must upgrade or reduce usage.

CHANNEL_NOT_ALLOWED

HTTP 403

Default message This channel is not available on your current plan.

When it occurs

The request arrived on a channel (headless, customer-account) that is not included in the merchant’s current plan.

Client handling

Show a contextual upgrade prompt explaining which plan unlocks the requested channel. Do not retry on the same channel without a plan change.

MAX_LISTS_REACHED

HTTP 402

Default message You've reached the maximum number of save lists on your plan.

When it occurs

The customer or merchant has reached the maximum number of save lists allowed by the current plan.

Client handling

Prompt the merchant to upgrade for more lists, or guide the customer to use an existing list instead of creating a new one.

CANNOT_DELETE_DEFAULT_LIST

HTTP 409

Default message The default list cannot be deleted. Set another list as the default first.

When it occurs

A delete-list request targeted the customer’s default save list while it was still marked as default.

Client handling

Call set-default with another list ID first, then retry delete. Refresh list UI so the user picks a new default before removing the old default list.

SHOPIFY_UPSTREAM_ERROR

HTTP 502

Default message Shopify returned an upstream error.

When it occurs

Shopify Admin or Storefront API returned an error, timeout, or unexpected response while SaveLayer processed the request.

Client handling

Retry with backoff for transient cases. If persistent, merchant should check Shopify status and app scopes. Do not assume data was written—re-fetch state.

INTERNAL_ERROR

HTTP 500

Default message An internal error occurred.

When it occurs

An unexpected exception occurred server-side after validation (bug, infrastructure, or unhandled edge case).

Client handling

When `retryable` is true, retry with backoff a few times. Otherwise show a generic error and support contact. Never rely on error message text for branching logic—use `code`.