Errors and rate limits
This page explains how the Fulify API reports problems, the limits it enforces, and how to retry.
Error format
Every error response has the same shape:
Branch on code, which is stable. message is written for people and may change. Some errors carry extra fields inside error:
| Field | When |
|---|---|
| errors | Selection-guide validation (a list of messages) and variant-group validation (a list of line and message pairs). |
| totalErrors | Variant-group validation, when there are more problems than listed. |
| retryAfter | Rate limiting. Seconds to wait before retrying. |
| upgradeUrl | A plan doesn't include the feature. |
| productCount | Deleting a size chart that's still attached to products. |
Status and error codes
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body isn't valid JSON, a parameter or field is invalid, or a required value is missing. |
| 400 | invalid_request | The variant-group request is structurally wrong: rows is missing, empty or has more than 5,000 entries. |
| 401 | unauthorized | The key is missing, invalid, revoked or expired. |
| 403 | forbidden_scope | The key lacks the scope named in the message. |
| 403 | feature_not_available | Your plan doesn't include this feature. The error carries upgradeUrl. |
| 404 | not_found | No such record in your organization. A record that belongs to another organization returns the same. |
| 409 | conflict | The change would break a rule, such as deleting a size chart that's still in use. |
| 413 | payload_too_large | The body is over 5 MB, or the request has more than 500 products or more than 1,000 SKUs in one assignment. |
| 422 | validation_failed | Variant-group rows failed validation and nothing was written. The error lists each bad line. |
| 429 | rate_limited | Too many requests. Wait retryAfter seconds and retry. |
| 500 | server_error | Something went wrong on Fulify's side. Safe to retry. |
Per-product errors on POST /products
Problems with individual products aren't HTTP errors. When the request itself is valid, POST /products returns 200, and each product that failed has "action": "error" and an errors list in results. The other products are still written.
Always check summary.failed and the entries in results, not just the status code.
Common per-product errors:
| Error | What to do |
|---|---|
| sku is required | Every product needs a sku. |
| name is required to create a product | Send name when the SKU is new. |
| basePrice is required to create a product | Send basePrice when the SKU is new. |
| duplicate sku in this request | Send each SKU once per request. |
| brand not found | Create the brand in Fulify, or fix the name. |
| one or more categories not found | Create the categories in Fulify, or fix the names. |
| taxCategoryId not found in this organization | Use an id from your own organization. |
| must be a number or string | Send prices, weights and dimensions as a number or a string, or null to clear the field. |
| type must be one of | Use physical, digital or service. |
| Unsafe or invalid image URL | Use a public https image URL. |
| unknown option | Create the option type in Settings → Variant Options first. |
| write failed | The product passed validation but couldn't be saved, so nothing was changed. Check field lengths and that prices are plain decimals, then retry. If it keeps failing, contact support with the SKU. |
Rate limits
Limits are per API key, over a rolling 60-second window. Reads and writes have separate budgets.
| Budget | Limit | Applies to |
|---|---|---|
| Read | 600 per minute | Every GET request. |
| Write | 300 per minute | POST /products, selection-guide POST, PATCH and DELETE, and size-chart assignment. |
| Variant groups | 60 per minute | POST /variant-groups/bulk. |
POST /variant-groups/bulk counts against the same write budget, with a lower ceiling. It's refused once the key has made 60 writes of any kind in the window, even if those were product writes.
Over the limit, you get 429:
retryAfter is in the error body, in seconds.
Staying under the limits
- Batch writes. One
POST /productswith 500 products counts as one write. - Use
updatedSinceto read only what changed, andpageSize=200to read fewer, larger pages. - Use webhooks instead of tight polling loops.
- Give each integration its own key. Limits are per key.
Retrying safely
- 429: wait
retryAfterseconds, then retry the same request. - 500 and network errors: retry with exponential backoff, for example after 1, 2, 4 and 8 seconds.
- 400, 401, 403, 404, 409, 413 and 422: don't retry unchanged. Fix the request or the key first.
POST /products is safe to retry. It upserts by SKU, so sending the same products again updates them to the same values rather than creating duplicates. Products that already succeeded are simply updated again, which also fires another product.updated webhook.