Errors
Every error response uses the same shape. The HTTP status code tells you the broad category; the error.code tells you the precise reason.
Error format
json
{
"error": {
"code": "validation_failed",
"message": "The request body did not match the expected schema.",
"request_id": "req_8f3a1c2d4e5b6a7f",
"details": {
"field": "variants[0].price",
"reason": "must be a number"
}
}
}| Field | Type | Always present | Purpose |
|---|---|---|---|
code | string | Yes | Machine-readable error identifier. Use this for branching in your client. The full list lives in Humind error codes. |
message | string | Yes | Human-readable summary. Short, English-only. Don't display raw to end users, it's written for developers. |
request_id | string | Yes | Unique identifier for this request. Attach it to support tickets so we can find the matching server logs. |
details | object | No | Per-error context: which field failed validation, which scopes were missing, which field caused an idempotency conflict, etc. The shape depends on code. |
HTTP status codes
| Code | Meaning | When it's returned |
|---|---|---|
200 OK | Success | GET, PUT, PATCH, POST upserts that updated an existing resource. |
201 Created | Resource created | POST that created a new resource. |
204 No Content | Success, no body | DELETE. |
207 Multi-Status | Per-item statuses in batch | POST /products/batch. Top-level is 207; each item in the response carries its own status code. See Batch upsert. |
400 Bad Request | Malformed or invalid request | Body isn't valid JSON (invalid_json), schema validation fails (validation_failed), or a required header is missing/too long (idempotency_key_required, idempotency_key_too_long). |
401 Unauthorized | Auth failure | Missing, malformed, invalid, revoked, or expired API key. |
403 Forbidden | Auth succeeded but disallowed | Key is valid but lacks the required scope for this endpoint. |
404 Not Found | Resource doesn't exist or belongs to another tenant | Includes "exists but belongs to another company", we never leak cross-tenant existence. |
409 Conflict | Idempotency reuse with different body | The Idempotency-Key was used before with a different request body. |
413 Payload Too Large | Body over 5 MB | Trim the payload or split into batches. Returns payload_too_large. |
415 Unsupported Media Type | Wrong/missing Content-Type | A write request (POST, PUT, PATCH) didn't send Content-Type: application/json. Returns unsupported_media_type. |
422 Unprocessable Entity | Resource-level invariant violated | Currently used for cannot_delete_last_variant, wrong_type, file_not_uploaded, invalid_lang_format, tag_resolution_failed, and the import / webhook state-machine errors. Plain "field X is wrong" validation issues return 400 validation_failed. |
429 Too Many Requests | Throttled | Per-API-key rate limit (rate_limited) or auth-layer IP throttle (too_many_failures). |
500 Internal Server Error | Unexpected error on our side | Includes the request_id. Safe to retry with the same Idempotency-Key. |
502 / 503 / 504 | Upstream / capacity issue | Transient. Retry with backoff using the same Idempotency-Key. |
Humind error codes
| Code | HTTP | Meaning | How to fix |
|---|---|---|---|
missing_credentials | 401 | No Authorization header, or it doesn't start with Bearer . | Add Authorization: Bearer hmd_live_... to the request. |
invalid_key_format | 401 | The token doesn't match hmd_(live|test)_<22>_<8>. | Check for trailing whitespace, missing characters, or pasted ellipsis. See Key format. |
invalid_checksum | 401 | The 8-char checksum at the end of the token doesn't match the rest. Almost always a copy-paste typo. | Re-copy the key from the dashboard, or regenerate it if you can no longer access the original. |
invalid_key | 401 | Token has the right shape but doesn't match any active key (unknown, revoked, or expired). | Verify the key in your dashboard. If it was revoked, create a new one. |
revoked | 401 | The key was revoked manually or via "regenerate". | Create a new key and update your secret store. |
expired | 401 | The key has an expires_at in the past. | Create a new key; consider not setting an expiry or set it further out. |
insufficient_scope | 403 | The key is valid but doesn't have a scope this endpoint requires. details.required lists the required scopes; details.missing lists the ones missing. | Create a new key with the missing scopes. Scopes are immutable, you can't add scopes to an existing key. See Scopes. |
validation_failed | 400 | The body parsed but the body didn't match the resource schema; details.issues lists each offending field with its path, message, and code. | Fix the field(s) and resend. The same Idempotency-Key is safe to reuse on the corrected request only if the body change is the validation fix; in practice, generate a new UUID. |
invalid_json | 400 | The request body isn't valid JSON. | Verify your serialization. The Content-Type: application/json header is required and the body must parse with JSON.parse. |
payload_too_large | 413 | Request body exceeds the 5 MB limit. | Split into smaller payloads. For catalog backfills use POST /products/batch (up to 500 items per call) or the Imports async pipeline. |
unsupported_media_type | 415 | The request omitted Content-Type: application/json on a POST, PUT, or PATCH (or sent a different content-type). | Set Content-Type: application/json on every write request. |
not_found | 404 | The resource doesn't exist, or belongs to a different company. | Check the id you passed. If you're using api:<external_id>, confirm the external_id exists for this company. |
idempotency_conflict | 409 | An Idempotency-Key was reused with a different request body. | Generate a fresh UUID for this request. Reuse the key only when retrying the exact same request. |
idempotency_key_required | 400 | A state-changing call (POST, PUT, PATCH, DELETE) was made without an Idempotency-Key header on an endpoint that now requires it. | Add an Idempotency-Key: <uuid> header. See Idempotency. |
idempotency_key_too_long | 400 | The Idempotency-Key header exceeds 255 characters. | Use a UUID v4 (36 chars) or any short unique value. |
idempotency_in_progress | 409 | The same Idempotency-Key is currently being processed by another concurrent request. | Wait for the original request to finish, then retry with the same key, the cached response will be replayed. |
duplicate_external_id_in_batch | - | The same external_id appeared more than once in a batch payload (POST /products/batch, POST /collections/batch, POST /knowledge/batch). The first occurrence is processed normally; later duplicates are returned with status: "failed" in the per-item result. Surfaced inside the 207 body, not as a top-level HTTP error. | Deduplicate external_ids in your batch payload before sending. |
handle_already_used | 409 | A POST / PUT / PATCH on /collections set a handle that already belongs to a different collection in this company (matched on a different external_id). details.handle carries the offending value. | Pick a different handle, or PATCH the existing collection (matched by external_id) instead of creating a new one. |
tag_resolution_failed | 422 | A tag name in tags couldn't be resolved or created (rare). details.tag carries the offending name. | Retry with the same body. If it persists, share the request_id with support. |
translation_not_found | 404 | A translation sub-resource GET targets a {lang} that has no translation stored on the resource. DELETE is idempotent and does not raise this code. | Confirm the locale code, or use the parent resource's translations field to see which locales exist. |
variant_not_found | 404 | The {variant_external_id} on a variant sub-resource call doesn't match any variant on the product. DELETE is idempotent and does not raise this code. | Confirm the variant's external_id against the parent product's variants array. |
invalid_lang_format | 422 | The {lang} segment of a translation sub-resource URL doesn't match ^[a-z]{2}(-[A-Z]{2})?$. | Use a BCP 47 short tag like en, fr, de, pt-BR. |
webpage_fetch_failed | - | A webpage knowledge entry's URL fetch failed. Surfaced via the entry's status='failed', not a top-level HTTP error. | Verify the URL is publicly reachable and HTML, then PATCH the entry to re-queue. See The webpage type. |
wrong_type | 400 | A type-specific knowledge endpoint (e.g. /file-upload-url, /file-upload-completed) was called on an entry whose discriminator doesn't match. The error message echoes the actual type. | Either retarget the call to a matching entry, or use the right endpoint for the entry's type. See The file type. |
file_not_uploaded | 422 | POST /knowledge/{id}/file-upload-completed was called before the merchant PUT the file payload to the SAS URL minted by /file-upload-url. | Complete the upload PUT first, then retry. See The file type. |
too_many_failures | 429 | An IP has accumulated too many failed auth attempts in a short window. | Stop probing. Verify your key, then retry after one minute. |
import_not_pending | 422 | start or cancel was called on an import that's not in the right state for that transition (e.g. start on an already processing / done / failed / cancelled import). | Check status first via GET /imports/{sync_id}. Each lifecycle transition is one-way. |
import_blob_missing | 422 | start was called on an import for which no NDJSON file has been uploaded yet (the blob behind upload_url returns 404). | PUT the file to upload_url first, then call start. See Imports. |
webhook_endpoint_not_found | 404 | The webhook endpoint :id doesn't match an endpoint owned by your company. | Confirm the id. Cross-tenant lookups also return 404. See Webhooks. |
invalid_event_type | 422 | An entry in events on POST / PATCH /webhooks/endpoints isn't a known event type. | Use one of the values listed in Available events. |
invalid_url | 422 | The url on a webhook endpoint isn't a valid HTTPS URL, or points to a private host (localhost, 127.0.0.1, RFC1918) in live mode. | Use a publicly reachable https:// URL. For local dev, use a hmd_test_* key with a tunnel. |
webhook_disabled | 422 | An attempt to interact with a disabled webhook endpoint in a way that requires it to be active. | Re-activate via PATCH /webhooks/endpoints/:id with { "status": "active" }. |
internal_error | 500 | Unexpected server-side failure. | Retry with backoff. If it persists, contact support with the request_id. |
Debugging tips
- Always log the
request_idalongside the status code anderror.codein your client. Including it in a support ticket cuts our triage time from "find a needle in 500k log lines" to "look up one record". - Don't loop on 401s. A 401 means human-in-the-loop intervention is needed (verify the key, rotate it, fix the header). Re-retrying a bad key just trips the too_many_failures cooldown.
- Treat 5xx as transient. Retry with exponential backoff and the same
Idempotency-Key. We log every 5xx with therequest_id; if you see a pattern, share the IDs with support. - 400 vs 422. A 400 covers anything wrong with the request envelope or the schema: invalid JSON, missing/oversized headers, a field with the wrong type or out of range. A 422 is reserved for resource-level invariants the schema alone can't express — e.g. "you can't remove the last variant of a product", or "this knowledge entry's
typedoesn't match the endpoint you called". Most validation errors are 400; if you see 422, look at the specific code in the table above.
Next
- Authentication: full list of auth-related codes (
missing_credentials,invalid_checksum,revoked, …) in context. - Conventions: request format and idempotency, the source of most validation errors.
- Products: per-endpoint error tables.