Error codes
Registry of every error code the HOCO API returns, with its HTTP status, meaning, typical operations and the handling expected from each client.
Every error answers with an HTTP status and the envelope defined in the API conventions:
{
"error": {
"code": "STOCK_INSUFFICIENT",
"message": "Quantité non disponible pour HOCO N32. Réduisez la quantité ou retirez l'article.",
"details": { "variantId": "01a1a2b0-3c4d-7e5f-8a9b-0c1d2e3f4a5b", "requestedQty": 40, "availability": "IN_STOCK" },
"requestId": "req_01J9Z4K7M2"
}
}codeis the contract. Clients branch oncode(anddetails), never onmessage. Codes areUPPER_SNAKE_CASE, stable, and never reused with another meaning; a new code is an additive change announced in the API-contract changelog.messageis safe to show. Localised byAccept-Language(frdefault,ar,en), written for the end user, without internal detail. Clients may replace it with their own copy for the codes they handle explicitly.detailsis structured. Field errors, limits, conflicting state, ids — documented per code below and per operation on each page. Unknowndetailskeys must be ignored.requestIdis also returned in theX-Request-Idheader and written in the server logs and the audit log; support screens show it (« Référence : req_01J9Z4K7M2 »).- Unknown codes. A client that receives a code it does not know handles it by its HTTP status (table below) and shows
message.
The registry lists the codes of every API page. Codes marked † were introduced with the authentication, me & reference, files, notifications and invitations pages; the others come from the pages linked in Typical operations.
Handling by HTTP status
| Status | Meaning | Default client handling |
|---|---|---|
| 400 | Malformed or invalid input | Show field errors from details.fields next to the inputs; never retry unchanged. |
| 401 | No valid session | Clear the stored token and go to sign-in (the reseller app falls back to guest browsing). Exceptions: INVALID_CREDENTIALS on sign-in, AUTH_REQUIRED_FOR_PRICES (show the sign-up prompt). |
| 403 | Not allowed | Show the message; do not retry. Account codes (ACCOUNT_SUSPENDED, PASSWORD_CHANGE_REQUIRED…) route to their dedicated screens. |
| 404 | Not found or not visible to the caller | Show « Élément introuvable », remove it from local caches, go back. |
| 409 | State conflict | Refetch the resource, show its current state, let the user redo the action. IDEMPOTENCY_IN_PROGRESS alone is retried automatically after 1 s. |
| 410 | Gone (expired) | Show the message; offer to restart the flow. |
| 422 | Business rule refused valid input | Show the message and the guidance from details; the user changes something before retrying. |
| 426 | App too old | Forced-update screen with details.storeUrl (RA-36, DA-28). |
| 429 | Too many requests | Wait Retry-After seconds (countdown when user-visible); never hammer. |
| 500 | Server error | Generic error with requestId; mutations with an Idempotency-Key may be retried with the same key. |
| 501 | Contracted but not implemented yet (staging only) | Developer-facing banner; never in production. |
| 503 | Maintenance or a provider down | Maintenance screen when MAINTENANCE; otherwise retry with backoff. |
Offline replays from the delegate app follow the same codes inside each item of sync.batch: 4xx items are REJECTED (or CONFLICT for 409) and shown in the sync centre (DA-27), 5xx items are retried.
Registry
Platform
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
INTERNAL_ERROR | 500 | Unexpected server failure | any | Generic error with requestId; retry idempotent calls with the same key |
MAINTENANCE | 503 | The app is in maintenance (details.until, details.message) | any except appConfig.get | Maintenance screen; poll appConfig.get |
UPGRADE_REQUIRED | 426 | X-App-Version below minVersion, or missing on a mobile session (details.minVersion, details.storeUrl) | any except appConfig.get, auth.logout, sync.batch during its 72 h grace | Forced-update screen |
RATE_LIMITED | 429 | Request rate above the limit (Retry-After) | any; auth.otp.send hourly caps | Wait and retry later |
NOT_FOUND | 404 | Unknown resource, or one the caller may not see (existence is never revealed) | any read or action on an id | Remove from caches, go back |
NOT_IMPLEMENTED | 501 | Operation present in the contract, not yet implemented on staging | any (staging only) | Developer banner; the release checklist forbids it in production |
FEATURE_DISABLED | 403 | A switchable feature is off (details.feature, e.g. packs) | cart.packs.add, packs.* | Hide the feature |
PAYLOAD_TOO_LARGE † | 413 | JSON body above 1 MB (files go to storage, never in JSON) | any mutation | Developer error — fix the client |
Validation
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | Invalid input; details.fields[] = { path, code, message } with the field codes | any | Inline field errors |
REASON_REQUIRED | 422 | A reason (code from a reference list, or text) is mandatory for this action | cancellations, refusals, status changes, not-done visits, overrides | Open the reason picker |
FIELD_NOT_EDITABLE | 403 | A field the caller may never change (details.fields[] with { field, reason }, e.g. customerType) | profile.update, delegate.resellers.update | Show the field read-only with « Contactez HOCO » |
DUPLICATE_VALUE | 409 | A unique value is already used (name, slug, code, SKU) | back-office creations | Highlight the field |
RESOURCE_IN_USE | 409 | Archiving or deleting something still referenced | admin catalog, CRM, stock CRUD | Explain what still uses it |
BULK_TOO_LARGE | 422 | A bulk action matches too many records (details.matched, details.max) | admin.resellers.bulk | Ask to narrow the filter |
EXPORT_TOO_LARGE | 422 | Export above the row limit | admin exports | Ask to narrow the filter |
REPORT_RANGE_TOO_LARGE | 422 | Period longer than allowed (details.maxDays) | dashboards, reports | Shorten the period |
Auth & accounts
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
UNAUTHENTICATED | 401 | No session, or expired or revoked (details.reason: MISSING, EXPIRED, REVOKED); also a guest calling a signed-in operation | any session operation, cart.* | Clear the token; sign-in (reseller app: guest mode with the RA-41 prompt). REVOKED → « Vous avez été déconnecté de cet appareil » |
INVALID_CREDENTIALS | 401 | Unknown identifier or wrong password (same message for both) | auth.login | Inline error; keep the identifier |
WRONG_APP † | 403 | Correct credentials for an account of another type (details.userType) | auth.login, me.devices.register | « Ce compte est un compte délégué : utilisez l'application HOCO Délégué » |
ACCOUNT_SUSPENDED | 403 | Account SUSPENDED or DISABLED (details.status, details.supportPhone, details.supportWhatsapp) | auth.login and every session operation | Blocked screen with support contacts; sessions are gone |
ACCOUNT_PENDING_VALIDATION | 403 | Reseller account not validated yet | cart.checkout, orders.confirmAssisted, returns.create, payment declarations | Pending banner / RA-06 |
PASSWORD_CHANGE_REQUIRED | 403 | Temporary password not changed yet | everything except auth.password.change, auth.logout, auth.session, me.get | Forced change-password screen |
REAUTH_REQUIRED | 403 | Sensitive back-office action without a fresh sign-in (15 min) | role, setting, credit, user-status changes; personal or financial exports | Ask the password again, then retry |
AUTH_LOCKED | 429 | Sign-in locked 15 minutes after 5 consecutive failures (details.lockedUntil, Retry-After) | auth.login | Countdown and « Mot de passe oublié » |
TOO_MANY_ATTEMPTS | 429 | Attempts exhausted on a one-time code or on the current password | auth.otp.verify, auth.password.change | Request a new code / wait 15 minutes |
OTP_RESEND_TOO_SOON | 429 | Less than 60 s since the last code (details.retryAfterSeconds) | auth.otp.send | Resend countdown |
OTP_INVALID † | 422 | Wrong code (details.attemptsLeft) | auth.otp.verify | Clear the input, show attempts left |
OTP_EXPIRED † | 422 | No pending code: expired, replaced or used | auth.otp.verify | Offer « Renvoyer le code » |
OTP_DELIVERY_FAILED † | 503 | SMS gateway or mail server failure (nothing counted) | auth.otp.send | Retry after Retry-After |
VERIFICATION_TOKEN_INVALID † | 422 | Verification token unknown, expired, used, of another purpose or another phone (details.reason) | auth.signup.reseller, auth.password.reset, me.update | Restart the OTP step |
INVALID_CURRENT_PASSWORD † | 422 | Current password wrong; the session stays valid (details.attemptsLeft) | auth.password.change | Inline error |
TERMS_NOT_ACCEPTED † | 422 | Terms version accepted is not the current one | me.update (also a field code of auth.signup.reseller) | Show the current terms again |
PHONE_ALREADY_EXISTS | 409 | The phone belongs to another account or point of sale | auth.signup.reseller, me.update, auth.otp.send (PHONE_CHANGE), reseller creations | Sign-up: offer sign-in; CRM: open the existing record |
CUSTOMER_TYPE_REQUIRED | 422 | Approving an account without a customer type | admin.resellers.validate | Ask the validator to choose the type |
Access
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
PERMISSION_DENIED | 403 | The caller's roles lack the permission, or the user type cannot use the operation | any back-office operation; staff on reseller/delegate operations | « Accès refusé »; the UI should not have offered it |
OUT_OF_SCOPE | 403 | Record outside the delegate's zones or not the reseller's own (writes; reads answer 404) | delegate.*, reseller operations with ids | Refetch the list; the record left the scope |
NOT_ASSIGNED_DELEGATE | 403 | The tour belongs to another delegate now (details.assignedTo) | delegate.tours.* | Refresh the day; drop local actions on it |
PERMISSION_ESCALATION | 403 | Granting more than the actor holds | admin.roles.updatePermissions, admin.users.update | Explain; ask a super administrator |
PERMISSION_NOT_GRANTABLE | 422 | roles.administer / users.administer to a role other than SUPER_ADMIN | admin.roles.updatePermissions | Inline error |
SELF_MODIFICATION_FORBIDDEN | 403 | Changing one's own roles or status | admin.users.* | Explain |
LAST_SUPER_ADMIN | 409 | The change would leave no active super administrator | admin.users.setStatus, role changes | Explain |
ROLE_PROTECTED | 409 | Deleting a system role or editing a locked one | admin.roles.* | Explain |
ROLE_INCOMPATIBLE | 422 | A staff role on a delegate or reseller user, or the reverse | admin.users.* | Inline error |
CRM, zones & invitations
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
RESELLER_TRANSITION_NOT_ALLOWED | 409 | Status change not allowed (automatic funnel statuses, same status) | admin.resellers.setStatus, delegate.resellers.update | Show allowed statuses from details |
RESELLER_INCOMPLETE | 422 | Minimum data (cahier §15.5) missing (details.missing) | admin.resellers.validate | List missing fields |
RESELLER_HAS_ACCOUNT | 409 | The store already has an account — nothing to invite | delegate.invitations.create | Show the account state |
VALIDATION_NOT_PENDING | 409 | The record is not waiting for validation | admin.resellers.validate | Refresh the queue |
DUPLICATE_SUSPECTED | 409 | Near-identical record found (details.hints) | admin.resellers.create | Show the candidates; confirm with ignoreDuplicateHints |
LOCATION_REQUIRED | 422 | Approval or creation needs a GPS position | admin.resellers.validate, delegate.resellers.create | Ask for the position |
DELEGATE_INACTIVE | 422 | The delegate's account is not active | assignments | Pick another delegate |
DELEGATE_ZONE_MISMATCH | 422 | The delegate does not cover the commune | admin.resellers.update, assignments | Use the out-of-zone option or another delegate |
DELEGATE_HAS_OPEN_TOURS | 409 | Deactivating a delegate with open tours | admin.users.setStatus, admin.delegates.crud | Reassign or cancel the tours |
TASK_TRANSITION_NOT_ALLOWED · TASK_NOT_OPEN | 409 | Follow-up not in a state allowing the change | admin.tasks.update, delegate.tasks.* | Refresh the task |
ZONE_OVERLAP | 409 | A commune would belong to two active zones | admin.zones.crud | Highlight the communes |
ZONE_IN_USE | 409 | Deleting a zone that still has delegates or points of sale | admin.zones.crud | Reassign first |
INVITATION_NOT_FOUND † | 404 | No invitation with this code | invitations.resolve, invitations.attribute | « Code invalide » inline |
INVITATION_EXPIRED † | 410 | Invitation expired or replaced (details.reason: EXPIRED, REVOKED) | invitations.resolve, invitations.attribute | « Demandez un nouveau lien à votre délégué » |
INVITATION_ALREADY_USED † | 409 | The invited point of sale already has an account | invitations.resolve, invitations.attribute | Offer « Se connecter » |
INVITATION_INACTIVE | 409 | Sharing an expired or revoked invitation | delegate.invitations.markShared | Create a new invitation |
auth.signup.reseller never fails because of an invitation code: an invalid one is ignored and reported in invitationCodeIgnored.
Catalog, pricing & stock
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
AUTH_REQUIRED_FOR_PRICES | 401 | A guest asked for something that depends on prices (price range, price sort, price facet, price-only view) | catalog.products.list, catalog.facets | Show the RA-41 sign-up prompt — not a session expiry. The catalog page currently answers 400 VALIDATION_FAILED with field code SIGN_IN_REQUIRED; to reconcile on one form |
FAVORITES_LIMIT_REACHED | 422 | 500 favorites already | favorites.add | Explain |
ATTRIBUTE_VALUE_INVALID | 422 | A variant attribute value not allowed by its definition (type, unit, allowed values) | admin.variants.crud, admin.products.*, product import | Inline error on the attribute |
PRICE_ORDER_INVALID | 422 | Prices not ordered priceSuperGros ≤ priceDemiGros ≤ priceDetail | admin.variants.crud | Inline error |
PRODUCT_INCOMPLETE | 422 | An active product would lack mandatory data (image…) | admin.products.update, setStatus | List what is missing |
PRODUCT_TRANSITION_NOT_ALLOWED | 409 | Product status change not allowed | admin.products.setStatus | Show allowed statuses |
VARIANT_LAST_ACTIVE | 409 | Hiding the last active variant of an active product | admin.variants.crud | Hide the product instead |
CATEGORY_CYCLE · CATEGORY_DEPTH_EXCEEDED | 422 | Invalid category tree (cycle, deeper than category › sub-category) | admin.categories.* | Inline error |
IMPORT_FILE_INVALID | 422 | Unreadable import file, missing columns, too many rows | admin.products.import, admin.imports.create | Show details |
IMPORT_DRY_RUN_REQUIRED · IMPORT_DRY_RUN_EXPIRED · IMPORT_ALREADY_APPLIED | 422 · 409 · 409 | Import applied without a valid dry run, dry run too old, or applied twice | imports | Run the dry run again |
STOCK_BELOW_RESERVED | 422 | New physical stock below the reserved quantity | admin.stock.adjust | Release or change orders first |
INVENTORY_SESSION_CONFLICT · INVENTORY_TRANSITION_NOT_ALLOWED · INVENTORY_LINES_INCOMPLETE | 409 · 409 · 422 | Inventory session conflicts or incomplete counts | admin.inventory.* | Follow details |
VARIANT_NOT_IN_SCOPE | 422 | Variant not in the inventory session | admin.inventory.* | Inline error |
MULTI_WAREHOUSE_DISABLED · WAREHOUSE_INACTIVE | 422 | Warehouse rules | admin.warehouses.crud, stock and delivery operations | Explain |
Cart & orders
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
CART_EMPTY | 422 | Checkout of an empty cart | cart.checkout | Back to the catalog |
CART_CHANGED | 409 | The re-validated total differs from expectedTotalTtc | cart.checkout | Show the new total and ask to confirm again |
CART_LINE_LIMIT_REACHED | 422 | 100 lines already (details.limit) | cart.items.add, orders.reorder | Explain |
PRODUCT_UNAVAILABLE | 422 | A variant is not orderable for this reseller (details.lines) | cart.*, cart.checkout, assisted orders, admin.orders.create | Remove the line |
QUANTITY_NOT_ALLOWED | 422 | Minimum, step or maximum quantity broken | cart.*, cart.checkout, assisted orders | Adjust the stepper to details |
STOCK_INSUFFICIENT | 422 | Not enough available stock — never a quantity in details | cart.*, cart.checkout, admin.orders.validate | Lower the quantity or remove the line |
ORDER_MIN_AMOUNT_NOT_MET | 422 | Total below orders.minAmount (details.minAmount, currentAmount) | cart.checkout, assisted orders | Progress bar « Plus que … DA » |
ADDRESS_NOT_DELIVERABLE · ADDRESS_MISSING | 422 | Address not served or incomplete | cart.checkout, admin.orders.* | Choose or complete an address |
PACK_UNAVAILABLE | 422 | Pack outside its dates or an item not orderable (V2) | cart.packs.add | Explain |
NOTHING_TO_REORDER | 422 | No line of the order can be added again | orders.reorder | Show details.unavailableLines |
RESELLER_SUSPENDED | 403 | Commercial suspension: ordering blocked, account still usable | cart.checkout, assisted orders, admin.orders.create | Suspended banner with support contacts |
RESELLER_NOT_VALIDATED | 422 | Back-office action on an order of a non-validated reseller | admin.orders.validate | Validate the account first |
ORDER_TRANSITION_NOT_ALLOWED | 409 | Status change not allowed from the current status | orders.cancel, admin.orders.transition, admin.orders.cancel | Refetch the order |
ORDER_NOT_MODIFIABLE · ORDER_INVOICED | 409 | Order lines can no longer change / an invoice exists | admin.orders.modify, admin.orders.cancel | Explain |
ASSISTED_ORDER_ALREADY_PENDING | 409 | An assisted order already awaits this reseller's confirmation | delegate.assisted.submit | Open the pending one |
ASSISTED_ORDER_EXPIRED | 409 | The confirmation delay has passed | orders.confirmAssisted, orders.rejectAssisted, admin.orders.confirmAssisted | Refetch; the order is cancelled |
ASSISTED_CONFIRMATION_NOT_PENDING | 409 | Not an assisted order awaiting the reseller | admin.orders.confirmAssisted | Refetch |
DOCUMENT_NOT_AVAILABLE | 409 | Invoice or delivery note not produced yet (details.availableFrom) | orders.document, admin.orders.documents | Hide the download until then |
DELIVERY_INFO_REQUIRED · DELIVERY_PROOF_REQUIRED | 422 | Shipping data or proof of delivery missing | admin.orders.transition, admin.deliveries.* | Ask for the data |
DELIVERY_NOT_EDITABLE · CARRIER_INACTIVE | 409 · 422 | Delivery in a final state / carrier deactivated | admin.deliveries.update | Explain |
Returns, after-sales & support
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
ORDER_NOT_DELIVERED | 409 | Return requested on an order that is not DELIVERED (details.status) | returns.create | Hide « Demander un retour » |
RETURN_WINDOW_EXPIRED | 422 | More than returns.windowDays after delivery (details.returnableUntil) | returns.create | Dialog with the window and the WhatsApp button; offer an after-sales claim |
RETURN_QTY_EXCEEDS_DELIVERED | 422 | Quantity above delivered minus already returned (details.lines) | returns.create | Cap the quantity |
RETURN_TRANSITION_NOT_ALLOWED | 409 | Return status change not allowed | admin.returns.decide, admin.returns.receive | Refetch |
AFTER_SALES_WINDOW_EXPIRED | 422 | After-sales claim outside its window (afterSales.windowDays or the product warranty) | tickets.create (type AFTER_SALES) | Dialog with the window and the WhatsApp button |
TICKET_TRANSITION_NOT_ALLOWED | 409 | Ticket status change not allowed | tickets.close, admin.tickets.* | Refetch |
NOTIFICATION_PREFERENCE_LOCKED † | 422 | Changing a locked channel (transactional, security, in-app) | notifications.preferences.update | Keep the switch disabled |
Finance
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
CREDIT_LIMIT_EXCEEDED | 422 | CREDIT order above the available credit with policy BLOCK | cart.checkout, admin.orders.validate | Suggest another payment method |
CREDIT_ACCOUNT_BLOCKED | 422 | Credit account blocked | admin.orders.validate | Explain; override with permission |
PAYMENT_METHOD_NOT_ALLOWED | 422 | Method disabled, or CREDIT without a credit line (details.reason) | cart.checkout, payments.declare | Refresh the method list |
PAYMENT_METHOD_DISABLED | 422 | Method not in payments.enabledMethods | admin.payments.record | Explain |
PAYMENT_ALREADY_DECLARED | 409 | Same method and reference already declared (details.paymentNumber) | payments.declare | Open the existing declaration |
PAYMENT_NOT_PENDING · PAYMENT_NOT_CONFIRMED | 409 | Payment not in the required state | admin.payments.verify, admin.payments.allocate | Refetch |
ALLOCATION_EXCEEDS_BALANCE · ALLOCATION_EXCEEDS_PAYMENT · ALLOCATION_RESELLER_MISMATCH | 422 | Invalid allocation of a payment to invoices | admin.payments.allocate | Inline error per invoice |
INVOICE_ALREADY_CANCELLED · INVOICE_HAS_PAYMENTS | 409 | Invoice cannot be cancelled | admin.invoices.cancel, admin.orders.cancel | Deallocate first |
INVOICE_CANCELLED | 422 | Allocation to a cancelled invoice | admin.payments.allocate | Remove the invoice |
Field
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
GPS_OUT_OF_TOLERANCE | 422 | Check-in farther than field.gpsToleranceMeters (details.distanceMeters) | delegate.visits.arrive | DA-28 « Vous êtes à 320 m du point de vente »; justified check-in when allowed |
GPS_ACCURACY_TOO_LOW | 422 | Position less precise than field.gpsMaxAccuracyMeters | delegate.visits.arrive, delegate.resellers.create | Wait for a better fix |
GPS_MOCK_LOCATION | 422 | The device reports a mocked position | delegate.visits.arrive, delegate.tours.start | Ask to disable fake GPS apps |
TOUR_TRANSITION_NOT_ALLOWED · TOUR_NOT_STARTABLE | 409 | Tour not in a state allowing the action | delegate.tours.start, suspend, resume | Refresh the day |
TOUR_ALREADY_IN_PROGRESS · TOUR_NOT_IN_PROGRESS · TOUR_NOT_TODAY · TOUR_DATE_TAKEN | 409 | Tour state or date conflict | delegate.tours.*, admin.tours.* | Follow details |
TOUR_ORDER_IMPOSED | 409 | Reordering stops of an imposed tour | delegate.tours.reorder | Disable drag and drop |
TOUR_CLOSING_BLOCKED | 409 | Closing blocked (details.blockingItems[].code: UNSYNCED_DATA, VISIT_NOT_ENDED, UNVISITED_STOPS_WITHOUT_REASON) | delegate.tours.close | Open DA-20 with the blockers |
VISIT_TRANSITION_NOT_ALLOWED · VISIT_NOT_CHECKED_IN · VISIT_IN_PROGRESS · VISIT_ALREADY_PLANNED | 409 | Visit state conflict | delegate.visits.*, delegate.tours.addVisit | Refresh the visit |
VISIT_LOCKED | 409 | Finished visit locked (unlock needs field.validate) | delegate.visits.* | Read-only visit |
VISIT_REQUIREMENTS_NOT_MET | 422 | Ending a visit without what its type requires (details.missing[].code: REPORT_REQUIRED, PHOTO_REQUIRED, CHECKLIST_INCOMPLETE) | delegate.visits.end | Go to the missing step |
PHOTO_LIMIT_REACHED | 422 | Maximum photos reached (details.max) | delegate.visits.addPhoto, delegate.resellers.addPhoto | Disable the camera button |
Sync & idempotency
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
IDEMPOTENCY_KEY_REQUIRED | 400 | Idempotency-Key missing where it is mandatory | cart.checkout, sync.batch, delegate.* mutations, every operation flagged idem | Developer error — always send it |
IDEMPOTENCY_KEY_REUSED | 409 | Same key with a different body (also a reused client file id) | any mutation | Developer error — new action needs a new key |
IDEMPOTENCY_IN_PROGRESS | 409 | The first request with this key is still running | any mutation | Retry automatically after 1 s |
OCCURRED_AT_OUT_OF_RANGE | 422 | occurredAt more than 5 min in the future or older than 72 h | offline-capable delegate.*, sync.batch items | Sync centre: « Action enregistrée il y a plus de 72 h » |
CLIENT_ID_CONFLICT | 409 | A client-generated id already used by another record | delegate.resellers.create, offline creations | Regenerate the id; keep the data |
VERSION_CONFLICT | 409 | Optimistic concurrency: the record changed since it was read | PATCH operations with a version, sync.batch items | Show the server version; let the user merge |
Files
| Code | HTTP | Meaning | Typical operations | Client handling |
|---|---|---|---|---|
FILE_PURPOSE_NOT_ALLOWED † | 403 | The caller may not upload this purpose | files.createUpload | Developer error |
FILE_TYPE_NOT_ALLOWED † | 422 | Declared or detected type not accepted for the purpose (details.allowed) | files.createUpload, files.completeUpload | « Format non accepté » |
FILE_TOO_LARGE † | 422 | Above the purpose's limit (details.maxSize) | files.createUpload, files.completeUpload | Compress (images) or pick a smaller file |
FILE_NOT_UPLOADED † | 422 | Completion before the object exists in storage | files.completeUpload, operations attaching a file | Retry the PUT, then complete |
FILE_SIZE_MISMATCH † | 422 | Stored size differs from the declared size | files.completeUpload | Start a new upload |
FILE_UNREADABLE † | 422 | Corrupt image or PDF, or an image above 40 megapixels | files.completeUpload | Pick another file |
FILE_NOT_READY | 422 | Attaching or reading a file whose upload was not completed | every operation taking a fileId, files.downloadUrl | Complete the upload first |
FILE_PURPOSE_MISMATCH | 422 | The attached file has another purpose | every operation taking a fileId | Developer error |
Field codes
Codes inside details.fields[].code of 400 VALIDATION_FAILED (and of the per-item results of sync.batch). Generic ones come from the Zod schemas of the contract; specific ones from the pages shown.
| Field code | Meaning | Examples |
|---|---|---|
REQUIRED · UNKNOWN_FIELD | Missing mandatory field · field not in the schema | any body |
INVALID_FORMAT · INVALID_ENUM · TOO_SHORT · TOO_LONG · OUT_OF_RANGE · NOT_POSITIVE | Generic type, enum, length and range checks | any body or query |
IN_FUTURE · TOO_OLD | Date outside the accepted window | payments.declare (paidAt) |
INVALID_PHONE · INVALID_EMAIL · INVALID_IDENTIFIER | Phone not an Algerian mobile in any accepted format · email · neither phone nor email | auth.*, sign-up, profiles |
PASSWORD_TOO_SHORT · PASSWORD_TOO_WEAK · PASSWORD_TOO_COMMON · PASSWORD_REUSED | Password policy (AUTH-07) | auth.signup.reseller, auth.password.* |
TERMS_NOT_ACCEPTED | Terms version not the current one | auth.signup.reseller |
COMMUNE_NOT_IN_WILAYA | Commune of another wilaya | sign-up, addresses, reseller forms |
UNKNOWN_REFERENCE_CODE · UNKNOWN_REFERENCE_ITEM | Code not in the reference list or inactive | sign-up (businessTypeCode), visit reports |
DEVICE_ID_MISMATCH | Body deviceId differs from X-Device-Id | me.devices.register, invitations.attribute |
INVALID_INVITATION_CODE | Not 8 characters of the code alphabet | invitations.resolve |
SIGN_IN_REQUIRED | Guest using a price filter or sort | catalog.products.list, catalog.facets |
UNKNOWN_ATTRIBUTE · INVALID_RANGE · CURSOR_MISMATCH · INVALID_BARCODE | Catalog query checks | catalog.* |
MISSING_VISIT · UNKNOWN_VISIT · VISIT_NOT_PLANNED | Tour stop list checks | delegate.tours.reorder |
Cart and order issue codes
Not errors: values of Issue.code returned inside carts and assisted orders (see cart issues). A BLOCKING issue makes cart.checkout answer with the error of the same code.
| Issue code | Severity |
|---|---|
PRICE_CHANGED · PROMOTION_ENDED | WARNING |
PRODUCT_UNAVAILABLE · STOCK_INSUFFICIENT · QUANTITY_NOT_ALLOWED · ORDER_MIN_AMOUNT_NOT_MET · ACCOUNT_PENDING_VALIDATION · RESELLER_SUSPENDED | BLOCKING |
Variants to retire
Spellings found in other pages or in the plan for the same situation as a registry code. The registry spelling is the one to implement; the pages listed are to be aligned.
| Found | Use | Where |
|---|---|---|
CUSTOMER_TYPE_LOCKED (422) | FIELD_NOT_EDITABLE (403) | requirements › authentication, delegate-app screens index, mobile API integration and architecture |
FIELD_NOT_EDITABLE with status 422 | status 403, as in the permissions matrix | API › delegate resellers |
RETURN_WINDOW_CLOSED, AFTER_SALES_WINDOW_CLOSED | RETURN_WINDOW_EXPIRED, AFTER_SALES_WINDOW_EXPIRED | API › orders, lifecycles and statuses |
RETURN_QTY_EXCEEDED | RETURN_QTY_EXCEEDS_DELIVERED | plan task SRV-20 |
MOCK_LOCATION_DETECTED | GPS_MOCK_LOCATION | requirements › geography |
OTP_RATE_LIMITED | RATE_LIMITED (hourly caps) · OTP_RESEND_TOO_SOON (60 s cooldown) | plan task SRV-05 |
TOO_MANY_ATTEMPTS for the sign-in lock | AUTH_LOCKED (sign-in lock only; TOO_MANY_ATTEMPTS stays for codes and current passwords) | requirements › authentication (AUTH-07) |
FILE_NOT_READY with status 409 | status 422 | API › finance |
SIGN_IN_REQUIRED field code vs AUTH_REQUIRED_FOR_PRICES | one form to choose | API › catalog |
API reference
Conventions, shared schemas and the full operation catalogue of the HOCO API — the contract every app is built against.
Authentication & sessions
Sign-in for the four clients, OTP codes, reseller sign-up with customer type and account claim, password reset and change, guest access and session management.