Errors
The error envelope, every error code, and what to do about each.
Every error response has the same shape:
{
"error": {
"type": "invalid_request_error",
"code": "photo_required_for_channel",
"message": "Product 66a1… has no images: channel \"etsy\" requires at least one image. Add an image, then publish again.",
"param": null,
"doc_url": "https://developers.nembol.com/docs/getting-started/errors#photo_required_for_channel"
},
"request_id": "req_9f83…"
}type: one of four families (below). Branch on this for retry logic.code: stable machine-readable string. Branch on this for specific handling.message: human-readable, English, tells you how to fix it. Never parse it.param: the offending field for validation errors (e.g.variants[2].price).request_id: include it when contacting support.
Error types
Common codes
| Code | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | Key missing, malformed, revoked or rolled |
insufficient_scope | 403 | Key lacks the scope for this endpoint |
plan_not_eligible | 403 | Store's plan does not include API access (during a trial: only test keys work) |
product_not_found / order_not_found / variant_not_found / listing_not_found / action_not_found / operation_not_found / event_not_found / import_not_found / channel_account_not_found / channel_connection_not_found / ignored_listing_not_found / webhook_endpoint_not_found / redirect_url_not_found / promotion_not_found / discount_not_found / bundle_not_found / notification_not_found | 404 | No such resource in this store. listing_not_found also appears per item in batch listing results |
validation_failed | 422 | See param and message |
invalid_json | 400 | The body isn't valid JSON (a missing quote, comma or bracket). Nothing was read or changed |
payload_too_large | 413 | The body is over 25 MB. Send images as URLs or through POST /files, or split the batch |
unsupported_encoding | 415 | The body's charset or encoding isn't supported. Send UTF-8 JSON (gzip is fine) |
subscription_inactive | 402 | The store's Nembol subscription isn't active: channels can't be connected or disconnected (POST /channel_connections, POST /channel_accounts/{id}/disconnect). The same code can arrive on return_url when the link is opened |
photo_required_for_channel | 422 | Publishing an imageless product to a channel that requires images |
billing_managed_by_nembol | 409 | A billing link on a store billed directly by Nembol (billing_source: "manual"): changes go through billing@nembol.com |
no_items_applied | 422 | POST /inventory/batch and POST /products/batch: no item could be applied (every SKU unknown, ambiguous or on a product that requires attention, or invalid). Reasons in error.details.rejected_items; nothing changed |
image_unreachable | 422 | An image url couldn't be downloaded (the host refused or the URL is wrong; param says which image). Use a public URL or send the file as base64 |
channel_not_connected | 422 | Publish/delist, or a promotion, on a channel the store hasn't connected |
not_supported_for_channel | 422 | This endpoint only works for some channels. The message names them |
setting_not_supported | 422 | That setting doesn't exist on this channel account. Its writable array lists what can be changed |
trial_publish_limit | 403 | The trial's publish limit on this channel is reached. Also per item in batch listing results. Subscribe to publish more |
sku_not_found / sku_ambiguous | 422 | Batch item rejected (/inventory/batch, /products/batch): unknown SKU, or a SKU on more than one variant (error.details.product_ids lists them; nothing written). Fix the duplicate or update by id |
images_unsupported_on_batch_update | per-item | Batch item matched an existing product but sent images. Update its images with PATCH /products/{id} |
file_too_large / unsupported_file_type | 422 | An image (POST /files, or images[] as base64 or url) is over 10 MB, or isn't a JPEG, PNG, WebP or GIF, e.g. a URL that answers an HTML page. param says which image |
idempotency_key_reused | 409 | Same Idempotency-Key with a different payload |
idempotency_key_in_flight | 409 | The first request with this Idempotency-Key is still running. Retry shortly |
precondition_failed | 412 | expected_updated_at didn't match. Re-read and retry |
product_still_listed | 422 | Delete refused: the product still has live listings. Delist first |
order_not_fulfillable | 422 | The order is canceled/refunded and can't be marked shipped |
channel_notification_unsupported | 422 | This channel can't receive shipment notifications. Retry with notify_channel: false |
overrides_not_supported | 422 | Only overrides.title / overrides.description can be set on a listing for now. An empty body re-syncs |
listing_not_delistable | 422 | Imported/linked listings are owned by the channel |
listing_operation_in_progress | 409 | Wait for the running operation, then retry |
not_started | per-item | Operation result: the channel job couldn't start because another operation took the listing. Retry |
no_price / price_below_minimum | per-item | Price adjustment result: the product has no priced variants, or the new price would fall below the minimum. Nothing changed for that product |
channel_error | n/a | (in a listing's issues[]) the channel rejected the last write. The message is the channel's error. Filter with error_contains |
import_not_supported | 422 | Products can't be imported from this channel |
import_already_running | 409 | An import from this channel account, or a CSV import, is already queued or running. Wait for it to finish |
import_still_running / import_already_undone | 409 | Undo import: the import hasn't finished yet, or was already undone |
promotion_reaches_nothing | 422 | Promotions: none of the products is listed on the chosen channels, so it would discount nothing. Publish them first, or save it as a draft |
promotion_conflict | 409 | Some products are already in another live promotion. See error.details.product_ids and promotion_ids |
promotion_limit_reached | 403 | Basic (and a free trial) runs one promotion at a time (active or scheduled; drafts don't count); Pro and above have no limit. See limits.running_promotions in GET /billing. Stop the other one or upgrade |
unknown_channel | 422 | Promotions: a channel in channels that promotions don't run on. The message names it and lists the valid ones |
discount_exceeds_price | 422 | Promotions: a fixed discount isn't lower than a covered price. error.details names the product_id, variant_id, channel and price |
promotion_running / promotion_not_running / promotion_reverting | 409 | Promotions: edit or delete while it runs (stop it first); stop on one that isn't running; delete while channel prices are still being restored (retry shortly) |
plan_upgrade_required | 403 | Storefront: the store's plan doesn't include this feature |
code_exists | 409 | Storefront: a discount with this code already exists (param: code) |
transition_not_allowed | 409 | Storefront order: the order can't move from its current status to the one requested |
already_refunded / order_not_paid | 409 | Storefront order refund: nothing is left to refund, or the order was never paid. Answered before any confirmation |
stripe_refund_failed / paypal_refund_failed | 502 | Storefront order refund: the payment provider refused it; the message gives its reason |
sku_required / title_required | per-item | Batch item missing the SKU to match on / a title to create with |
route_not_found | 404 | No such endpoint. Check the reference |
rate_limit_exceeded | 429 | Bucket empty. Honor Retry-After |
write_quota_exceeded | 429 | Daily channel-write quota reached |
internal_error | 500 | Something went wrong on our side. Retry with backoff; quote request_id if it persists |
channel_unavailable | 502 | The channel didn't answer or failed. Retry later |
confirmation_required | 409 | Irreversible action: show confirmation.summary, then repeat the same request with Nembol-Confirmation. See Undo and confirmations |
confirmation_invalid | 409 | The confirmation token is used, expired or for a different request. A fresh one is in the answer |
undo_conflict | 409 | Nothing undone: every value was changed again since. See error.details |
action_already_undone | 409 | The action was already undone |
not_reversible | 422 | The action can't be undone; the message says what to do instead |
undo_window_expired | 422 | Actions can be undone for 30 days |
product_not_deleted | 409 | Restore called on a product that isn't deleted |
product_requires_attention | 409 | The product is to_verify: resolve it (POST /products/{id}/resolve or a merge) before writing its listings |
product_not_requiring_attention | 409 | Resolve called on a product that isn't to_verify |
resolution_not_applicable | 409 | That resolution doesn't fit the product's requires_attention.reason |
still_requires_attention | 409 | recheck: the channel setting isn't fixed yet. See error.details.reason |
confirm_different | 409 | Merge: the products don't look alike. Check error.details.product_ids, then send confirm_different: true |
same_channel_account | 422 | Merge: two products are live on the same channel account. See error.details.channels |
variants_differ | 422 | Merge: variants don't correspond. Match them with variant_maps using error.details.matching |
invalid_choices / too_few / too_many | 422 | Merge: a choices entry doesn't fit, or not 2–10 products |
product_merged | 409 | Restore refused: the product was merged. Undo the merge instead |
not_a_partner | 403 | Nembol-Account or partner endpoints used by a store that isn't a partner |
not_on_a_sub_account | 400 | You sent Nembol-Account to a partner endpoint (/accounts, /partner/*). Those work on your own partner account: call them without the header |
sandbox_limit | 409 | A sandbox partner reached its maximum of open companies. Close one first |
plan_too_small | 409 | The company has more products or channel accounts than the new plan allows |
account_not_found | 404 | No company with that id for your partner key |
billed_by_partner | 409 | /billing called inside a company: companies are billed to the partner |
billing_managed_elsewhere | 422 | The store is billed through Shopify, Wix or WooCommerce.com. Change, cancel or update payment there |
return_url_not_allowed | 422 | return_url must start with a URL registered for this store (POST /redirect_urls) |
plan_limits_exceeded | 422 | Plan change: the store uses more than the target plan allows. Reduce usage or pick a larger plan |
no_payment_method_yet / no_subscription_yet | 422 | The store has no Stripe billing yet. Subscribe first with POST /billing/plan_change_link |
already_canceled / already_canceling / nothing_to_cancel | 422 | Cancel: the subscription is already canceled, already ends at period end, or there is nothing to cancel (trial, no Stripe subscription) |
trial_not_available | 422 | The store can't restart a trial |
billing_provider_error | 502 | Stripe or PayPal didn't return a page. 409 when PayPal refuses the change. Retry later |
billing_links_unavailable | 503 | Billing links aren't available yet. Manage the subscription from the Nembol dashboard |
account_suspended / account_closed | 409 | Writes on a suspended (partner invoice overdue) or closed company |
product_limit_reached | 409 | The company's plan product limit is reached. Change its plan or delete products |
channel_slots_full | 422 | No free slot left for this channel. Disconnect an account first |
plan_channel_not_included | 403 | The store's plan doesn't include this channel. Upgrade to connect it |
channel_not_disconnectable | 422 | The store pays its Nembol subscription through this account, so it can't be disconnected |
connection_not_awaiting_choice | 409 | choose on a connection that isn't needs_choice |
choice_expired | 410 | More than an hour since the seller logged in. Create a new link |
link_expired / link_already_used | n/a | (on return_url) the connect link expired or was already used. Create a new one |
different_channel_account | n/a | (in connection.error and on return_url) the seller logged into a different shop than the one being reconnected (channel_account_id, or a disconnected account brought back: reconnects_shop). Nothing changed. Log into that shop, or connect the new one with new_account: true (without channel_account_id) |
channel_login_failed | n/a | (on return_url and in connection.error) the channel login didn't start or didn't complete. Create a new link |
channel_credentials_rejected | n/a | (in connection.error) the channel rejected the credentials sent |
plan_not_eligible / subscription_inactive / slot_no_longer_available | n/a | (on return_url, connection error) the store can no longer connect that channel |
no_account_to_connect | n/a | (connection error) the seller's channel account has nothing Nembol can connect |
Codes are added over time; handle unknown codes by falling back on type.
Last updated on October 21, 2026
Need help?
Email us and a human replies, usually within one business day.