Catalog & favorites
Home aggregate, brands, categories, product search with advanced filters and facets, product detail, barcode lookup, similar products and favorites — for guests, resellers and delegates.
The catalog group is what the reseller app (Imad, Flutter), the reseller web (Anis, Next.js) and the delegate's first-order assistance read to show products. It is built in SRV-11 and consumed by MRA-01, MRA-02, MRA-03, WEB-02 and MDA-09 (through the shared hoco_catalog package).
- Guests welcome, without prices. Every catalog read except favorites is
optional: without a session it answers withprice: nullandpriceLocked: true(« Créez un compte pour voir les prix », RA-41, AUTH-10). Favorites and the cart need a session (401 UNAUTHENTICATED). - One price per caller. A signed-in reseller sees, for every variant, the price of their
customerType(SUPER_GROS,DEMI_GROSorDETAIL) with promotions applied on top — never the other two prices, never the purchase price (PRC-02, PRC-05). - Availability, never quantities. Products and variants say
IN_STOCKorOUT_OF_STOCK; no stock number leaves the back-office (STK-07). - Only sellable products.
ACTIVEandOUT_OF_STOCKproducts that the visibility rules allow for the caller;DRAFT,HIDDENandARCHIVEDnever appear (CAT-04, CAT-05). - Fast. Search, filters and facets answer in under 3 seconds end to end (cahier §5.1, CAT-07); the server target is p95 under 800 ms for a page of 20.
| Method | Path | Operation | Access | Apps | Release |
|---|---|---|---|---|---|
| GET | /catalog/home | catalog.homeReseller home aggregate: banners, daily offer, quick tiles, popular categories, recommended products, account tiles — guests allowed, without prices | guest or session | RA · RW | V1 |
| GET | /catalog/brands | catalog.brandsActive brands — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/categories | catalog.categoriesCategory tree with product counts visible to the caller — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/products | catalog.products.listSearch, filter and sort products with the caller's resolved price and availability (cursor pagination) — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/products/suggest | catalog.products.suggestType-ahead suggestions (products, brands, categories) — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/facets | catalog.facetsFilter facets with counts for the current query: categories and sub-categories, brands, price range, attributes, availability, promotion — guests allowed (no price facet) | guest or session | RA · RW · DA | V1 |
| GET | /catalog/products/by-barcode/{barcode} | catalog.products.byBarcodeFind a product variant by barcode — guests allowed, without prices | guest or session | RA · DA | V1 |
| GET | /catalog/products/{productId} | catalog.products.getProduct detail: variants, gallery, specs, prices, availability, quantity limits, active promotion — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/products/{productId}/similar | catalog.products.similarSimilar and complementary products — guests allowed, without prices | guest or session | RA · RW | V1 |
| GET | /favorites | favorites.listMy favorite products | self | RA · RW | V1 |
| PUT | /favorites/{productId} | favorites.addAdd a product to favorites (idempotent) | self | RA · RW | V1 |
| DELETE | /favorites/{productId} | favorites.removeRemove a product from favorites | self | RA · RW | V1 |
Besides the errors listed per operation, every operation can answer 426 UPGRADE_REQUIRED, 429 RATE_LIMITED, 500 INTERNAL_ERROR and 503 MAINTENANCE; with a session also 401 UNAUTHENTICATED (expired token — the app drops it and continues as a guest) and 403 ACCOUNT_SUSPENDED. See the error registry.
Caller context
The same operations serve four kinds of callers. The server decides from the session, never from a client flag.
| Caller | Prices | Visibility rules | Favorites, isFavorite | Invitation step | Extra parameter |
|---|---|---|---|---|---|
| Guest (no session) | price: null, priceLocked: true; promotion prices null | Only products visible to all resellers | 401 on favorites; isFavorite: false | — | Price filters and price sorts refused (401 AUTH_REQUIRED_FOR_PRICES) |
Reseller PENDING_VALIDATION | Prices of the declared customerType when catalog.pricesVisibleBeforeValidation = true (default); otherwise as a guest | Applied for the reseller | ✓ | CATALOG_VIEWED on first read | — |
Reseller ACTIVE | Prices of Reseller.customerType | Applied for the reseller | ✓ | CATALOG_VIEWED on first read | — |
| Delegate | Prices of the reseller given in resellerId (required on priced reads) | Applied for that reseller | isFavorite: false; favorites 403 PERMISSION_DENIED | Never set by a delegate | resellerId — must be in the delegate's scope (403 OUT_OF_SCOPE) |
Assumption — staff users calling these operations are treated like a delegate without scope restriction; the back-office uses the admin catalog instead.
Prices
ProductVariantstores three selling prices HT in centimes —priceSuperGros,priceDemiGros,priceDetail— plusvatRate(basis points, 1900 by default) andrecommendedPrice(prix conseillé, TTC). The caller'scustomerTypepicks one (PRC-01). There are no quantity tiers in V1.- Every unit price is returned as a
Price:ht,ttc = round(ht × (1 + vatRate / 10 000))to the centime, andvatRate. The apps display TTC (« 2 490 DA »). - An active promotion targeted at the caller adds
promotion.price(the unit price after the promotion). When several promotions could apply, the catalog shows the one the cart would apply under the stacking rules (PRM-03). - Prices are computed at read time and never cached across callers (
Cache-Control: private, no-storeon priced responses). A change of customer type by the back-office takes effect on the next read. - The cart re-prices everything again; the catalog price is for display only (cahier §5.2 « Le produit ajouté au panier conserve le bon prix » is guaranteed by
cart.checkout, not by the client).
Visibility and availability
Status. Only products in ACTIVE or OUT_OF_STOCK are returned. OUT_OF_STOCK products stay visible with « En rupture » and cannot be added to the cart. A variant switched off in the back-office is never returned.
Visibility rules (CAT-05). ProductVisibilityRule rows, set in the product editor, allow or deny a product for segments or individual resellers. A product without rules is visible to every reseller. A product hidden for the caller behaves as if it did not exist: absent from lists, counts, facets, suggestions, home, favorites and promotion pages, and 404 NOT_FOUND on its detail.
Availability (STK-07, BR-16). Per variant: IN_STOCK when the available stock (physical − reserved, over the sellable warehouses) is above 0, otherwise OUT_OF_STOCK. A product card shows the availability of the variant it displays; the product is OUT_OF_STOCK only when every variant is. No response of this group carries a quantity, a threshold or a « Plus que N pièces » hint.
Search and filters
catalog.products.list and catalog.facets accept the same filter parameters, so the facet counts always describe the list the user is looking at (CAT-12).
| Parameter | Type | Meaning |
|---|---|---|
q | string, 2–80 chars | Free text on product name, model reference, variant SKU, barcode (exact), brand and category names; case- and accent-insensitive (unaccent + trigram) |
categoryId | Uuid | A category or sub-category; a category matches its sub-categories too |
brandId | Uuid, repeatable | brandId=…&brandId=… — any of the brands |
priceMin, priceMax | Money (centimes, TTC) | On the unit price the caller would pay (promotion included). Signed-in only — a guest gets 401 AUTH_REQUIRED_FOR_PRICES |
attr.<key> | string, repeatable | Attribute filter, <key> from the category's attribute definitions (CAT-13). Values: attr.connector=USB_C&attr.connector=LIGHTNING (any of), or a numeric range attr.power=20..65, open ends attr.capacity=10000.. or attr.length=..1 |
availability | IN_STOCK | OUT_OF_STOCK | Availability of at least one matching variant |
promotion | true | Only products with an active promotion for the caller |
new | true | Only new arrivals: publishedAt within catalog.newProductDays (default 30) |
Combination rules: values of one parameter are OR-ed, different parameters are AND-ed. Attribute filters apply to variants: a product matches when at least one of its visible variants matches every attribute filter, and its card then shows that variant (defaultVariantId is the first matching variant). Unknown attribute keys for the selected category return 400 VALIDATION_FAILED (UNKNOWN_ATTRIBUTE); an attribute filter without categoryId is allowed only for attributes defined on every category that has products in the result.
Sort (sort): relevance (default when q is set: exact SKU, barcode or reference first, then name prefix, then similarity, then popularity), popular (default otherwise: units ordered over the last 30 days, all resellers, refreshed daily), newest (publishedAt descending), price_asc, price_desc (signed-in only, on the caller's effective TTC unit price). The catalog tabs of reference screen 3 map to new=true&sort=newest (Nouveautés), sort=popular (Populaires) and promotion=true (Promotions).

Schemas
ProductCard is the shared list item (with price: Price | null, priceLocked, availability and category.parentId). The schemas below are specific to this group.
type StockAvailability = "IN_STOCK" | "OUT_OF_STOCK";
interface BrandView {
id: Uuid;
name: string; // "HOCO"
slug: string; // "hoco"
logoUrl: string; // public CDN (SVG or WebP)
productCount: number; // products visible to the caller
}
interface CategoryNode {
id: Uuid;
name: string; // "Chargeurs"
slug: string;
parentId: Uuid | null; // null = top-level category
imageUrl: string | null; // icon of the category chip
sortOrder: number;
productCount: number; // visible products, sub-categories included
children: CategoryNode[]; // sub-categories (one level in V1)
}
interface ProductDetail {
id: Uuid;
name: string;
reference: string; // model reference, "N32"
brand: { id: Uuid; name: string; logoUrl: string };
category: { id: Uuid; name: string; parentId: Uuid | null; path: { id: Uuid; name: string }[] };
status: "ACTIVE" | "OUT_OF_STOCK";
availability: StockAvailability; // OUT_OF_STOCK only when every variant is
description: string; // plain text, paragraphs separated by \n
gallery: FileRef[]; // public PRODUCT_IMAGE files, in display order
specs: Spec[];
warrantyMonths: number | null;
isNew: boolean;
isFavorite: boolean; // false for guests and delegates
priceLocked: boolean; // true for guests
defaultVariantId: Uuid;
variants: VariantView[];
share: { url: string; text: string };
updatedAt: Iso;
}
interface Spec {
group: string; // "Alimentation", "Connectique", "Général"
label: string; // "Puissance maximale"
value: string; // "30W"
highlighted: boolean; // shown on the product sheet (max 6); all specs on RA-15
}
interface VariantView {
id: Uuid;
sku: string; // "N32_BLACK_EU"
barcode: string | null; // EAN-13
label: string; // "Noir"
attributes: VariantAttribute[]; // filterable attributes (CAT-13)
imageIds: Uuid[]; // gallery images of this variant, shown first when selected
price: Price | null; // caller's price; null for guests
recommendedPrice: Money | null; // prix conseillé, TTC — shown to guests too
promotion: VariantPromotion | null;
availability: StockAvailability;
limits: { minQty: number; qtyStep: number; maxQtyPerOrder: number | null };
inCartQty: number; // quantity of this variant in my cart; 0 for guests and delegates
}
interface VariantAttribute {
key: string; // "power"
label: string; // "Puissance"
type: "ENUM" | "NUMBER" | "BOOLEAN";
value: string | string[] | number | boolean; // ENUM codes, number in `unit`, boolean
unit: string | null; // "W", "mAh", "m"
display: string; // "30 W" — ready to show
onProductSheet: boolean; // false: kept for the variant choice and filters, left out of the specifications (contract 1.1.0)
}
interface VariantPromotion {
id: Uuid;
label: string; // "Offre flash -30 %"
type: "PERCENT_OFF" | "AMOUNT_OFF" | "FIXED_PRICE" | "FREE_DELIVERY";
price: Price | null; // unit price after promotion; null for guests and FREE_DELIVERY
endsAt: Iso;
}Operations
GET /catalog/home
catalog.home · Access optional — guest or reseller (self); delegates 403 PERMISSION_DENIED · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements CAT-11, PRM-05, FIN-10
Purpose. One call that renders the home screen of reference screen 1 (cahier §5): greeting, notification and cart badges, banner carousel with the daily offer, quick tiles, popular categories, recommended products and the three account tiles (balance, available credit, loyalty points).

Request — no parameters. Accept-Language selects banner and label texts.
Response — 200 OK, Cache-Control: private, no-store.
interface HomeView {
serverTime: Iso; // for countdowns that do not trust the device clock
store: { // null for guests
resellerId: Uuid;
number: string; // REV-000123
storeName: string; // « Bonjour, Magasin El Wafa »
customerType: CustomerType;
accountStatus: "PENDING_VALIDATION" | "ACTIVE";
canOrder: boolean; // false while pending validation or commercially suspended
} | null;
badges: { unreadNotifications: number; cartItems: number } | null; // null for guests
banners: Banner[]; // placement HOME_CAROUSEL — see Promotions
dailyOffer: PromotionSummary | null; // see Promotions; endsAt drives the countdown
quickTiles: QuickTile[];
popularCategories: { id: Uuid; name: string; parentId: Uuid | null; imageUrl: string | null; productCount: number }[];
recommended: ProductCard[]; // 3 to 10 cards
account: { // null for guests
balanceDue: Money; // « Solde » — same value as finance.summary
creditAvailable: Money | null; // « Crédit disponible »; null without a credit line
loyaltyPoints: number | null; // « Points de fidélité »; null until V2
currency: "DZD";
} | null;
}
interface QuickTile {
key: "CATALOG" | "ORDERS" | "PROMOTIONS" | "LOYALTY" | "SUPPORT";
enabled: boolean; // LOYALTY false until V2; ORDERS false for guests
badge: number | null; // ORDERS: assisted orders to confirm · PROMOTIONS: active offers · SUPPORT: tickets waiting for my reply
}{
"serverTime": "2026-11-03T09:30:00Z",
"store": {
"resellerId": "01a17f02-93c4-7e8a-b1d2-4f5a6b7c8d90",
"number": "REV-000123",
"storeName": "Magasin El Wafa",
"customerType": "DEMI_GROS",
"accountStatus": "ACTIVE",
"canOrder": true
},
"badges": { "unreadNotifications": 3, "cartItems": 4 },
"banners": [
{
"id": "01a19d01-bfe0-70c2-bdd8-9395044ddb46",
"placement": "HOME_CAROUSEL",
"eyebrow": "OFFRE DU JOUR",
"title": "Accessoires mobiles premium",
"subtitle": "HOCO • ACEFAST • BOROFONE — Qualité, performance et fiabilité réunies",
"imageUrl": "https://cdn.hoco.dz/banners/2026/11/offre-du-jour-premium.webp",
"imageAlt": "Chargeur, câbles, power bank et écouteurs HOCO",
"theme": "DARK",
"cta": { "label": "Voir l'offre", "target": { "type": "PROMOTION", "promotionId": "01a19cfd-2c00-78b5-80c8-b4b9fb167f17" } },
"countdownEndsAt": "2026-11-03T22:59:59Z",
"startsAt": "2026-11-03T07:00:00Z",
"endsAt": "2026-11-03T22:59:59Z",
"sortOrder": 1
},
{
"id": "01a1724e-5a60-72f0-8be7-c953ed45608f",
"placement": "HOME_CAROUSEL",
"eyebrow": "ACEFAST",
"title": "-10 % sur les câbles ACEFAST",
"subtitle": "Jusqu'au 15 novembre",
"imageUrl": "https://cdn.hoco.dz/banners/2026/10/acefast-cables.webp",
"imageAlt": "Câbles tressés ACEFAST",
"theme": "LIGHT",
"cta": { "label": "J'en profite", "target": { "type": "PROMOTION", "promotionId": "01a17249-c680-746a-84fd-f70a4efea668" } },
"countdownEndsAt": null,
"startsAt": "2026-10-25T23:00:00Z",
"endsAt": "2026-11-15T22:59:59Z",
"sortOrder": 2
},
{
"id": "01a13ece-c260-7e9c-91c0-2d084c776990",
"placement": "HOME_CAROUSEL",
"eyebrow": "NOUVEAUTÉ",
"title": "Power bank HOCO J86A 10000mAh",
"subtitle": "Charge rapide 22,5 W, écran LED",
"imageUrl": "https://cdn.hoco.dz/banners/2026/10/j86a-launch.webp",
"imageAlt": "Power bank HOCO J86A noir",
"theme": "DARK",
"cta": { "label": "Découvrir", "target": { "type": "PRODUCT", "productId": "01a15889-fa80-7ff3-b294-f863a2ee34a6" } },
"countdownEndsAt": null,
"startsAt": "2026-10-20T08:00:00Z",
"endsAt": null,
"sortOrder": 3
},
{
"id": "01a181c1-6e60-75cc-90c0-c06383fd05e5",
"placement": "HOME_CAROUSEL",
"eyebrow": "BOROFONE",
"title": "Écouteurs TWS dès 2 980 DA",
"subtitle": null,
"imageUrl": "https://cdn.hoco.dz/banners/2026/10/borofone-tws.webp",
"imageAlt": "Écouteurs BOROFONE BW06 blancs",
"theme": "LIGHT",
"cta": { "label": "Voir la gamme", "target": { "type": "BRAND", "brandId": "01a05bfb-77d0-7e16-9999-f9ce5c47cd69" } },
"countdownEndsAt": null,
"startsAt": "2026-10-28T08:00:00Z",
"endsAt": "2026-11-30T22:59:59Z",
"sortOrder": 4
}
],
"dailyOffer": {
"id": "01a19cfd-2c00-78b5-80c8-b4b9fb167f17",
"label": "Offre flash -30 %",
"description": "Jusqu'à -30 % sur une sélection de produits HOCO, aujourd'hui seulement.",
"type": "PERCENT_OFF",
"value": 3000,
"badge": "-30 %",
"isDailyOffer": true,
"startsAt": "2026-11-03T07:00:00Z",
"endsAt": "2026-11-03T22:59:59Z",
"imageUrl": "https://cdn.hoco.dz/promotions/2026/11/flash-hoco-30.webp",
"scope": { "kind": "PRODUCTS", "summary": "6 produits HOCO", "productCount": 6 },
"conditions": { "minQtyPerLine": null, "minOrderAmountTtc": null },
"capStatus": "AVAILABLE",
"stackable": false
},
"quickTiles": [
{ "key": "CATALOG", "enabled": true, "badge": null },
{ "key": "ORDERS", "enabled": true, "badge": 1 },
{ "key": "PROMOTIONS", "enabled": true, "badge": 3 },
{ "key": "LOYALTY", "enabled": false, "badge": null },
{ "key": "SUPPORT", "enabled": true, "badge": 1 }
],
"popularCategories": [
{ "id": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "name": "Chargeurs", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/chargeurs.webp", "productCount": 58 },
{ "id": "01a05c04-a378-7a15-a596-11824871a56b", "name": "Câbles", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/cables.webp", "productCount": 74 },
{ "id": "01a05c04-a760-7afa-a91b-ad281947800e", "name": "Écouteurs", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/ecouteurs.webp", "productCount": 61 },
{ "id": "01a05c04-ab48-7b23-9612-4a7450cd39ef", "name": "Power banks", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/power-banks.webp", "productCount": 33 },
{ "id": "01a05c04-af30-7fac-8820-db93215bfc7e", "name": "Supports voiture", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/supports.webp", "productCount": 27 }
],
"recommended": [
{
"id": "01a08ac7-1ce0-7b52-8b6a-3ec2aaccb8fb",
"name": "HOCO X89 Câble USB-C",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-a378-7a15-a596-11824871a56b", "name": "Câbles", "parentId": null },
"imageUrl": "https://cdn.hoco.dz/products/x89/x89-black-1-480.webp",
"defaultVariantId": "01a08ac7-20c8-70bf-b753-d181dfe71ff6",
"price": { "ht": 105042, "ttc": 125000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 160000,
"availability": "IN_STOCK",
"isNew": false,
"isFavorite": true
},
{
"id": "01a08fe8-e500-7017-b062-03c53547a2be",
"name": "ACEFAST A45 Chargeur rapide",
"brand": { "id": "01a05bfb-73e8-7a2c-8ba9-f89b715db9c7", "name": "ACEFAST" },
"category": { "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0" },
"imageUrl": "https://cdn.hoco.dz/products/a45/a45-black-1-480.webp",
"defaultVariantId": "01a08fe8-e8e8-762d-81d1-ab0daa011e53",
"price": { "ht": 331933, "ttc": 395000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 490000,
"availability": "IN_STOCK",
"isNew": true,
"isFavorite": false
},
{
"id": "01a0901f-d380-7a88-9746-4ec8936c3d68",
"name": "BOROFONE BW06 Écouteurs TWS",
"brand": { "id": "01a05bfb-77d0-7e16-9999-f9ce5c47cd69", "name": "BOROFONE" },
"category": { "id": "01a05c04-a760-7afa-a91b-ad281947800e", "name": "Écouteurs", "parentId": null },
"imageUrl": "https://cdn.hoco.dz/products/bw06/bw06-white-1-480.webp",
"defaultVariantId": "01a0901f-d768-77a1-94f2-1bca2251bdcd",
"price": { "ht": 250420, "ttc": 298000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 390000,
"availability": "IN_STOCK",
"isNew": false,
"isFavorite": false
}
],
"account": {
"balanceDue": 12540000,
"creditAvailable": 25000000,
"loyaltyPoints": null,
"currency": "DZD"
}
}For a guest the same call returns store, badges and account as null, ORDERS and SUPPORT tiles with enabled: false, and every card with "price": null, "priceLocked": true, "isFavorite": false.
| Field | Notes |
|---|---|
badges.cartItems | Number of cart lines — the bottom-bar badge and « 4 articles » of the cart (reference screens 1 and 5). Same value as cart.get → itemsCount. |
banners | Same items and rules as banners.list with placement=HOME_CAROUSEL, at most 6; guests only get banners whose audience is every reseller. |
dailyOffer | The active promotion flagged isDailyOffer for the caller, null when none. The client renders the countdown from endsAt − serverTime, not from the device clock. |
popularCategories | Up to 8 top-level categories ranked by units ordered over the last 30 days by resellers of the caller's wilaya (all wilayas for guests, or when the wilaya has fewer than 20 orders), ties by sortOrder; categories without a visible product are skipped. |
recommended | Rules-based (CAT-11), at most 10, IN_STOCK only, de-duplicated: (1) up to 3 products of active promotions targeted at the caller, (2) best sellers of the caller's wilaya over 30 days that the reseller has not ordered in the last 60 days, (3) new arrivals. Guests get (1) for all-reseller promotions, then national best sellers. |
account.balanceDue, account.creditAvailable | Copied from finance.summary; « Voir le détail » opens RA-29, « Utiliser mon crédit » opens the catalog. null blocks are hidden by the client. |
account.loyaltyPoints | Always null in V1; the « Points de fidélité » tile and the LOYALTY quick tile stay hidden or disabled until the loyalty programme ships (V2). |
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller is a delegate or a staff user. |
Rules & side effects
- Read-only except the invitation funnel: the first successful
catalog.home,catalog.products.listorcatalog.products.getby a reseller account records theCATALOG_VIEWEDstep of the reseller's invitation, once (InvitationEvent, outboxinvitation.step_completed→ push to the inviting delegate) — see the invitation funnel. Guests and delegates never trigger it. - Each block is computed independently with a 300 ms budget; a block that fails or times out is returned empty (
[]ornull) and logged, so the home always renders (cahier §5 acceptance criterion « l'accueil charge les informations autorisées »). - Content depends on the reseller's wilaya, segment and customer type through promotion targeting and visibility rules (cahier §5 « Le contenu affiché dépend du profil, de la zone et des promotions actives »).
Used by — RA-09 (« Accueil »), RW-02, guest home of RA-41.
GET /catalog/brands
catalog.brands · Access optional — guest, reseller or delegate · Apps RA, RW, DA · Idempotency-Key — · Release V1 · Requirements CAT-01
Purpose. Lists the active brands (HOCO, ACEFAST, BOROFONE — managed as data) for the brand filter and logos.
Request
| Query | Type | Required | Notes |
|---|---|---|---|
resellerId | Uuid | delegates, optional | Counts products visible to that reseller; without it a delegate gets counts of all sellable products |
Response — 200 OK, ETag, Cache-Control: private, max-age=300; 304 Not Modified on a matching If-None-Match. Not paginated, ordered by the brand's sort order.
{
"items": [
{ "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO", "slug": "hoco", "logoUrl": "https://cdn.hoco.dz/brands/hoco.svg", "productCount": 184 },
{ "id": "01a05bfb-73e8-7a2c-8ba9-f89b715db9c7", "name": "ACEFAST", "slug": "acefast", "logoUrl": "https://cdn.hoco.dz/brands/acefast.svg", "productCount": 46 },
{ "id": "01a05bfb-77d0-7e16-9999-f9ce5c47cd69", "name": "BOROFONE", "slug": "borofone", "logoUrl": "https://cdn.hoco.dz/brands/borofone.svg", "productCount": 63 }
]
}Errors
| Status | Code | When |
|---|---|---|
| 403 | OUT_OF_SCOPE | Delegate passes a resellerId outside their scope. |
Rules & side effects — a brand with no product visible to the caller is omitted; archived brands never appear. Logos are public CDN files the apps cache for 7 days.
Used by — brand filter of RA-13 and RW-03; brand logos on product cards of RA-09, RA-12 and DA-17.
GET /catalog/categories
catalog.categories · Access optional — guest, reseller or delegate · Apps RA, RW, DA · Idempotency-Key — · Release V1 · Requirements CAT-02
Purpose. Returns the category tree — categories and their sub-categories — with the number of products visible to the caller, for the category chips, the category screen and the filter sheet.
Request
| Query | Type | Required | Notes |
|---|---|---|---|
resellerId | Uuid | delegates, optional | As for brands |
Response — 200 OK, ETag, Cache-Control: private, max-age=300; 304 on a matching If-None-Match. Not paginated; top-level categories and their children ordered by sortOrder.
{
"items": [
{
"id": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "name": "Chargeurs", "slug": "chargeurs", "parentId": null,
"imageUrl": "https://cdn.hoco.dz/categories/chargeurs.webp", "sortOrder": 1, "productCount": 58,
"children": [
{ "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "slug": "chargeurs-muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "imageUrl": null, "sortOrder": 1, "productCount": 38, "children": [] },
{ "id": "01a05c04-9f90-7b8b-ae4e-13b022cb0e3e", "name": "Chargeurs voiture", "slug": "chargeurs-voiture", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "imageUrl": null, "sortOrder": 2, "productCount": 20, "children": [] }
]
},
{ "id": "01a05c04-a378-7a15-a596-11824871a56b", "name": "Câbles", "slug": "cables", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/cables.webp", "sortOrder": 2, "productCount": 74, "children": [] },
{ "id": "01a05c04-a760-7afa-a91b-ad281947800e", "name": "Écouteurs", "slug": "ecouteurs", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/ecouteurs.webp", "sortOrder": 3, "productCount": 61, "children": [] },
{ "id": "01a05c04-ab48-7b23-9612-4a7450cd39ef", "name": "Power banks", "slug": "power-banks", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/power-banks.webp", "sortOrder": 4, "productCount": 33, "children": [] },
{ "id": "01a05c04-af30-7fac-8820-db93215bfc7e", "name": "Supports voiture", "slug": "supports-voiture", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/supports.webp", "sortOrder": 5, "productCount": 27, "children": [] },
{ "id": "01a05c04-b318-7218-abfe-013bfc53a1d3", "name": "Accessoires audio", "slug": "accessoires-audio", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/audio.webp", "sortOrder": 6, "productCount": 18, "children": [] },
{ "id": "01a05c04-b700-7dd1-ae30-e84913d90799", "name": "Montres", "slug": "montres", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/montres.webp", "sortOrder": 7, "productCount": 9, "children": [] },
{ "id": "01a05c04-bae8-721a-ba40-f91d0c95b468", "name": "Accessoires", "slug": "accessoires", "parentId": null, "imageUrl": "https://cdn.hoco.dz/categories/accessoires.webp", "sortOrder": 8, "productCount": 13, "children": [] }
]
}Errors
| Status | Code | When |
|---|---|---|
| 403 | OUT_OF_SCOPE | Delegate passes a resellerId outside their scope. |
Rules & side effects
productCountcounts visible products (ACTIVEandOUT_OF_STOCK) of the category and its sub-categories; categories with 0 are omitted. The chip « Tous » is client-side.- The tree has two levels in V1 (category → sub-category), matching the
parentIdcarried by everyProductCard.category. A product belongs to exactly one leaf.
Used by — category chips of RA-12, filter sheet RA-13, sidebar of RW-03, product picker of DA-17.
GET /catalog/products
catalog.products.list · Access optional — guest, reseller, or delegate with resellerId · Apps RA, RW, DA · Idempotency-Key — · Release V1 · Requirements CAT-07, CAT-12, CAT-05, PRC-02, STK-07
Purpose. Searches, filters and sorts the catalog and returns product cards with the caller's price and availability, page by page (cahier §5.1).
Request
| Query | Type | Required | Default | Notes |
|---|---|---|---|---|
q, categoryId, brandId, priceMin, priceMax, attr.<key>, availability, promotion, new | — | no | — | See Search and filters |
sort | relevance | popular | newest | price_asc | price_desc | no | relevance with q, else popular | Price sorts signed-in only |
resellerId | Uuid | delegates: yes | — | Prices and visibility of that reseller (scope-checked); refused for resellers other than their own id |
cursor | string | no | — | Opaque, from nextCursor |
limit | integer 1–100 | no | 20 |
Example — a reseller opens Chargeurs sorted by price:
GET /v1/catalog/products?categoryId=01a05c04-97c0-76c5-9c89-9cf30f53d5b0&sort=price_asc&limit=3The delegate's product picker (DA-17) sends the reseller: GET /v1/catalog/products?resellerId=01a17f02-93c4-7e8a-b1d2-4f5a6b7c8d90&q=x89.
Response — 200 OK, Cache-Control: private, no-store: { items: ProductCard[], nextCursor: string | null }.
{
"items": [
{
"id": "01a094f3-c9c0-7e9c-84b6-23b8c48bdffd",
"name": "HOCO Z36 Chargeur voiture 38W",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-9f90-7b8b-ae4e-13b022cb0e3e", "name": "Chargeurs voiture", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0" },
"imageUrl": "https://cdn.hoco.dz/products/z36/z36-black-1-480.webp",
"defaultVariantId": "01a094f3-cda8-7e0c-a573-11857a12f2bd",
"price": { "ht": 176471, "ttc": 210000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 280000,
"availability": "IN_STOCK",
"promotion": { "id": "01a181bc-da80-7da6-9f6e-161582e00776", "label": "Z36 à 1 880 DA", "price": { "ht": 157983, "ttc": 188000, "vatRate": 1900 } },
"isNew": false,
"isFavorite": false
},
{
"id": "01a08ac2-8900-7f6d-937c-c81b37e89a5b",
"name": "HOCO N32 Chargeur rapide 30W (USB-C + USB-A)",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0" },
"imageUrl": "https://cdn.hoco.dz/products/n32/n32-black-1-480.webp",
"defaultVariantId": "01a08ac2-8ce8-7069-8328-28fd7158a572",
"price": { "ht": 209244, "ttc": 249000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 320000,
"availability": "IN_STOCK",
"isNew": false,
"isFavorite": true
},
{
"id": "01a08fe8-e500-7017-b062-03c53547a2be",
"name": "ACEFAST A45 Chargeur rapide",
"brand": { "id": "01a05bfb-73e8-7a2c-8ba9-f89b715db9c7", "name": "ACEFAST" },
"category": { "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0" },
"imageUrl": "https://cdn.hoco.dz/products/a45/a45-black-1-480.webp",
"defaultVariantId": "01a08fe8-e8e8-762d-81d1-ab0daa011e53",
"price": { "ht": 331933, "ttc": 395000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 490000,
"availability": "IN_STOCK",
"isNew": true,
"isFavorite": false
}
],
"nextCursor": "eyJzIjoicHJpY2VfYXNjIiwicCI6Mzk1MDAwLCJpIjoiMDFhMDhmZTgifQ"
}The same request from a guest returns identical cards with "price": null, "priceLocked": true, "promotion": { "id": "…", "label": "Z36 à 1 880 DA", "price": null } and "isFavorite": false — but a guest cannot send sort=price_asc.
| Field | Notes |
|---|---|
items[].price | Unit price of defaultVariantId for the caller's customer type, before promotion; with attribute filters, defaultVariantId is the first matching variant. |
items[].promotion | Present only when a promotion targeted at the caller applies to that variant; price is the price after promotion (what priceMin, priceMax and price sorts use). |
items[].availability | Of the displayed variant. Out-of-stock products are listed (after in-stock ones under relevance and popular) unless availability=IN_STOCK is sent. |
nextCursor | Keyset cursor bound to the sort and filters; reusing it with other filters returns 400 VALIDATION_FAILED (CURSOR_MISMATCH). |
Errors
| Status | Code | When |
|---|---|---|
| 401 | AUTH_REQUIRED_FOR_PRICES | A guest sends priceMin, priceMax or a price sort — the app shows the sign-up prompt of RA-41 (not a session expiry). |
| 400 | VALIDATION_FAILED | Field codes: TOO_SHORT (q under 2 characters), UNKNOWN_ATTRIBUTE, INVALID_RANGE (attr.power=65..20), REQUIRED (resellerId missing for a delegate), CURSOR_MISMATCH, limit above 100. |
| 403 | OUT_OF_SCOPE | Delegate's resellerId is outside their zones and assignments, or a reseller sends another reseller's id. |
| 403 | PERMISSION_DENIED | Staff without a delegate profile sends resellerId (see the assumption above). |
Rules & side effects
- Visibility, prices and availability as described at the top of this page; nothing here reveals a quantity, another customer type's price or the purchase price.
- The first successful call by a reseller account records
CATALOG_VIEWED(seecatalog.homerule 1). - Search terms (without user id) feed the « recherches sans résultat » report for the catalog team; a search with no result returns
items: []and the client proposes the WhatsApp button (support.whatsapp). - Performance: served from indexed columns and a denormalised
product_searchview refreshed on product, price, stock-availability and visibility changes; promotions are applied per page in memory. Target p95 under 800 ms server-side.
Used by — RA-12 (« Catalogue », infinite scroll), search results of RA-10, RA-13, RW-03, category and brand pages, product picker of DA-17.
GET /catalog/products/suggest
catalog.products.suggest · Access optional — guest, reseller or delegate · Apps RA, RW, DA · Idempotency-Key — · Release V1 · Requirements CAT-07
Purpose. Type-ahead under the search field: a few products, brands and categories matching what is typed, without prices, light enough to call on every keystroke (debounced 250 ms by the clients).
Request
| Query | Type | Required | Default | Notes |
|---|---|---|---|---|
q | string 2–80 | yes | — | |
limit | integer 1–10 | no | 5 | Per section |
resellerId | Uuid | no | — | Delegates: suggestions limited to that reseller's visible products |
GET /v1/catalog/products/suggest?q=charg&limit=3Response — 200 OK.
{
"query": "charg",
"products": [
{ "id": "01a08ac2-8900-7f6d-937c-c81b37e89a5b", "name": "HOCO N32 Chargeur rapide 30W (USB-C + USB-A)", "reference": "N32", "brandName": "HOCO", "imageUrl": "https://cdn.hoco.dz/products/n32/n32-black-1-120.webp", "matchedOn": "NAME" },
{ "id": "01a08fe8-e500-7017-b062-03c53547a2be", "name": "ACEFAST A45 Chargeur rapide", "reference": "A45", "brandName": "ACEFAST", "imageUrl": "https://cdn.hoco.dz/products/a45/a45-black-1-120.webp", "matchedOn": "NAME" },
{ "id": "01a094f3-c9c0-7e9c-84b6-23b8c48bdffd", "name": "HOCO Z36 Chargeur voiture 38W", "reference": "Z36", "brandName": "HOCO", "imageUrl": "https://cdn.hoco.dz/products/z36/z36-black-1-120.webp", "matchedOn": "NAME" }
],
"brands": [],
"categories": [
{ "id": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "name": "Chargeurs", "parentId": null, "productCount": 58 },
{ "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "productCount": 38 },
{ "id": "01a05c04-9f90-7b8b-ae4e-13b022cb0e3e", "name": "Chargeurs voiture", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "productCount": 20 }
]
}| Field | Notes |
|---|---|
products[].matchedOn | NAME, REFERENCE, SKU, BARCODE, BRAND or CATEGORY — lets the client highlight why it matched. |
brands[] | { id, name, logoUrl } — tapping opens the catalog with brandId. |
categories[] | Tapping opens the catalog with categoryId (sub-categories included). |
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | q missing or shorter than 2 characters. |
| 403 | OUT_OF_SCOPE | Delegate's resellerId outside scope. |
Rules & side effects — no prices, no availability, never records CATALOG_VIEWED; recent searches are kept on the device only. Rate limit 120 calls per minute per session or IP.
Used by — search field of RA-09, RA-10, RA-12, RW-03, DA-17.
GET /catalog/facets
catalog.facets · Access optional — guest, reseller, or delegate with resellerId · Apps RA, RW, DA · Idempotency-Key — · Release V1 · Requirements CAT-12, CAT-13
Purpose. Gives the filter sheet (mobile) and the sidebar (web) everything they need — categories and sub-categories, brands, price range, the attributes of the selected category, availability, promotion and new arrivals — each with the number of results it would give, for the current query.
Request — the same filter parameters as catalog.products.list (q, categoryId, brandId, priceMin, priceMax, attr.<key>, availability, promotion, new, and resellerId for delegates); no sort, cursor or limit.
GET /v1/catalog/facets?categoryId=01a05c04-97c0-76c5-9c89-9cf30f53d5b0&brandId=01a05bfb-7000-73a7-ac3c-c71515d5e3b3Response — 200 OK, Cache-Control: private, max-age=60.
interface Facets {
total: number; // results of the current query, all filters applied
categories: FacetCategory[]; // tree: top-level categories with their sub-categories
brands: { id: Uuid; name: string; count: number; selected: boolean }[];
priceRange: { min: Money; max: Money } | null; // TTC unit prices; null for guests
attributes: FacetAttribute[]; // only when categoryId is set
availability: { value: "IN_STOCK" | "OUT_OF_STOCK"; count: number }[];
promotion: { count: number };
new: { count: number };
}
interface FacetCategory {
id: Uuid;
name: string;
parentId: Uuid | null;
count: number;
selected: boolean;
children: FacetCategory[];
}
interface FacetAttribute {
key: string; // used as attr.<key>
label: string; // "Puissance"
type: "ENUM" | "NUMBER" | "BOOLEAN";
unit: string | null; // "W"
widget: "CHECKBOXES" | "CHIPS" | "RANGE" | "TOGGLE";
values?: { value: string; label: string; count: number; selected: boolean }[]; // ENUM and BOOLEAN
range?: { min: number; max: number; selectedMin: number | null; selectedMax: number | null;
buckets: { min: number; max: number; count: number }[] }; // NUMBER
}{
"total": 36,
"categories": [
{
"id": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "name": "Chargeurs", "parentId": null, "count": 36, "selected": true,
"children": [
{ "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "count": 24, "selected": false, "children": [] },
{ "id": "01a05c04-9f90-7b8b-ae4e-13b022cb0e3e", "name": "Chargeurs voiture", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "count": 12, "selected": false, "children": [] }
]
},
{ "id": "01a05c04-a378-7a15-a596-11824871a56b", "name": "Câbles", "parentId": null, "count": 52, "selected": false, "children": [] },
{ "id": "01a05c04-a760-7afa-a91b-ad281947800e", "name": "Écouteurs", "parentId": null, "count": 38, "selected": false, "children": [] },
{ "id": "01a05c04-ab48-7b23-9612-4a7450cd39ef", "name": "Power banks", "parentId": null, "count": 21, "selected": false, "children": [] },
{ "id": "01a05c04-af30-7fac-8820-db93215bfc7e", "name": "Supports voiture", "parentId": null, "count": 19, "selected": false, "children": [] }
],
"brands": [
{ "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO", "count": 36, "selected": true },
{ "id": "01a05bfb-73e8-7a2c-8ba9-f89b715db9c7", "name": "ACEFAST", "count": 12, "selected": false },
{ "id": "01a05bfb-77d0-7e16-9999-f9ce5c47cd69", "name": "BOROFONE", "count": 10, "selected": false }
],
"priceRange": { "min": 188000, "max": 1290000 },
"attributes": [
{
"key": "power", "label": "Puissance", "type": "NUMBER", "unit": "W", "widget": "RANGE",
"range": {
"min": 10, "max": 65, "selectedMin": null, "selectedMax": null,
"buckets": [ { "min": 10, "max": 19, "count": 9 }, { "min": 20, "max": 30, "count": 17 }, { "min": 31, "max": 65, "count": 10 } ]
}
},
{
"key": "connector", "label": "Ports", "type": "ENUM", "unit": null, "widget": "CHIPS",
"values": [
{ "value": "USB_C", "label": "USB-C", "count": 29, "selected": false },
{ "value": "USB_A", "label": "USB-A", "count": 22, "selected": false },
{ "value": "LIGHTNING", "label": "Lightning", "count": 4, "selected": false }
]
},
{
"key": "fast_charge", "label": "Charge rapide", "type": "BOOLEAN", "unit": null, "widget": "TOGGLE",
"values": [ { "value": "true", "label": "Oui", "count": 27, "selected": false } ]
},
{
"key": "color", "label": "Couleur", "type": "ENUM", "unit": null, "widget": "CHECKBOXES",
"values": [
{ "value": "BLACK", "label": "Noir", "count": 33, "selected": false },
{ "value": "WHITE", "label": "Blanc", "count": 21, "selected": false }
]
}
],
"availability": [ { "value": "IN_STOCK", "count": 33 }, { "value": "OUT_OF_STOCK", "count": 3 } ],
"promotion": { "count": 5 },
"new": { "count": 4 }
}| Field | Notes |
|---|---|
| counts | Disjunctive: each facet group is counted with every filter applied except its own, so the user sees what choosing another value would give (HOCO is selected, yet ACEFAST shows 12). total applies all filters. |
categories | Full two-level tree of categories that have results; the selected node carries selected: true. |
priceRange | Lowest and highest effective TTC unit price among the results — bounds of the price slider. null for guests (prices are hidden). |
attributes | Definitions of the selected category and of its parent (AttributeDefinition, BO-71), at most 12, in the order set by the catalog team; a value whose count would be 0 is omitted. Empty array without categoryId. |
range.buckets | Three to five buckets precomputed for the slider histogram. |
Errors
| Status | Code | When |
|---|---|---|
| 401 | AUTH_REQUIRED_FOR_PRICES | A guest sends a price filter. |
| 400 | VALIDATION_FAILED | Same field codes as catalog.products.list (UNKNOWN_ATTRIBUTE, INVALID_RANGE, REQUIRED). |
| 403 | OUT_OF_SCOPE | Delegate's resellerId outside scope. |
Rules & side effects
- Computed on the same visibility, price and availability rules as the list, so
totalequals the number of items the list would return with the same filters. - Clients call it when the filter sheet opens and after each change (debounced 300 ms); the list is reloaded when the user applies the filters (« Afficher 36 produits »).
- Guests get every facet except
priceRange(and cannot send price filters).
Used by — filter and sort sheets RA-13, sidebar filters of RW-03, product picker filters of DA-17.
GET /catalog/products/by-barcode/{barcode}
catalog.products.byBarcode · Access optional — guest, reseller, or delegate with resellerId · Apps RA, DA · Idempotency-Key — · Release V1 · Requirements CAT-10
Purpose. Finds the variant behind a scanned barcode (reference screen 1 shows the scanner in the search field) so the app can open the product with that variant selected, or the delegate can add it to an assisted order.
Request
| Path / query | Type | Required | Notes |
|---|---|---|---|
barcode | string, 8–14 digits | yes | EAN-8, UPC-A, EAN-13 or GTIN-14 as read by mobile_scanner |
resellerId | Uuid | delegates: yes | Prices and visibility of that reseller |
GET /v1/catalog/products/by-barcode/6931474783950Response — 200 OK.
{
"productId": "01a08ac2-8900-7f6d-937c-c81b37e89a5b",
"variantId": "01a08ac2-8ce8-7069-8328-28fd7158a572",
"sku": "N32_BLACK_EU",
"barcode": "6931474783950",
"name": "HOCO N32 Chargeur rapide 30W (USB-C + USB-A)",
"variantLabel": "Noir",
"brandName": "HOCO",
"imageUrl": "https://cdn.hoco.dz/products/n32/n32-black-1-480.webp",
"price": { "ht": 209244, "ttc": 249000, "vatRate": 1900 },
"priceLocked": false,
"promotion": null,
"availability": "IN_STOCK",
"limits": { "minQty": 1, "qtyStep": 1, "maxQtyPerOrder": 20 }
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Not 8–14 digits (INVALID_BARCODE), or resellerId missing for a delegate. |
| 403 | OUT_OF_SCOPE | Delegate's resellerId outside scope. |
| 404 | NOT_FOUND | No variant has this barcode, or its product is not visible to the caller — the same answer in both cases. The app shows « Produit introuvable » and offers the text search. |
Rules & side effects — barcodes are unique across variants (enforced by the back-office); a leading-zero UPC-A and its EAN-13 form match the same variant. Does not record CATALOG_VIEWED.
Used by — RA-11 (« Scanner »), scanner of DA-17.
GET /catalog/products/{productId}
catalog.products.get · Access optional — guest, reseller, or delegate with resellerId · Apps RA, RW, DA · Idempotency-Key — · Release V1 · Requirements CAT-08, PRC-02, STK-07
Contract 1.1.0 (SRV-41): the detail carries complements — the « Souvent achetés avec » products set on BO-28 (SN-14, at most 5), as ProductCards in their order, the ones visible to the caller only. Every promotion of a variant and of a ProductCard carries isFlashSale (and a card's endsAt), so the apps show the flash-sale badge and countdown without matching ids against promotions.list?tab=flash.
Purpose. The full product sheet of reference screen 4 (cahier §5.2): gallery, name and reference, price and recommended price, availability, highlighted specs and the full list, quantity limits, description, warranty, variants and the active promotion.

Request
| Path / query | Type | Required | Notes |
|---|---|---|---|
productId | Uuid | yes | |
variantId | Uuid | no | Variant to pre-select (from a scan or a deep link); only changes defaultVariantId in the response |
resellerId | Uuid | delegates: yes | |
If-None-Match | header | no | 304 when unchanged for this caller |
Response — 200 OK, ETag (per caller), Cache-Control: private, no-store: a ProductDetail.
{
"id": "01a08ac2-8900-7f6d-937c-c81b37e89a5b",
"name": "HOCO N32 Chargeur rapide 30W (USB-C + USB-A)",
"reference": "N32",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO", "logoUrl": "https://cdn.hoco.dz/brands/hoco.svg" },
"category": {
"id": "01a05c04-9ba8-779f-8030-3262210b2afb",
"name": "Chargeurs muraux",
"parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0",
"path": [
{ "id": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0", "name": "Chargeurs" },
{ "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux" }
]
},
"status": "ACTIVE",
"availability": "IN_STOCK",
"description": "Le chargeur HOCO N32 offre une charge rapide et sécurisée avec une puissance totale de 30W.\nÉquipé d'un port USB-C PD et d'un port USB-A QC3.0, il est compatible avec la plupart des smartphones, tablettes et accessoires.",
"gallery": [
{ "id": "01a08ac3-7360-7090-8e9c-cf36fff86a03", "url": "https://cdn.hoco.dz/products/n32/n32-black-1.webp", "thumbnailUrl": "https://cdn.hoco.dz/products/n32/n32-black-1-240.webp", "mimeType": "image/webp", "size": 184320, "purpose": "PRODUCT_IMAGE" },
{ "id": "01a08ac3-7748-7823-9c91-f62f45f326f6", "url": "https://cdn.hoco.dz/products/n32/n32-black-2.webp", "thumbnailUrl": "https://cdn.hoco.dz/products/n32/n32-black-2-240.webp", "mimeType": "image/webp", "size": 162816, "purpose": "PRODUCT_IMAGE" },
{ "id": "01a08ac3-7b30-75bc-a0d0-d5b0d312ca82", "url": "https://cdn.hoco.dz/products/n32/n32-black-3.webp", "thumbnailUrl": "https://cdn.hoco.dz/products/n32/n32-black-3-240.webp", "mimeType": "image/webp", "size": 149504, "purpose": "PRODUCT_IMAGE" },
{ "id": "01a08ac3-7f18-7e9c-b667-5d8ef9eeaa26", "url": "https://cdn.hoco.dz/products/n32/n32-black-4.webp", "thumbnailUrl": "https://cdn.hoco.dz/products/n32/n32-black-4-240.webp", "mimeType": "image/webp", "size": 171008, "purpose": "PRODUCT_IMAGE" },
{ "id": "01a08ac3-8300-7b9f-96f7-090a696f8174", "url": "https://cdn.hoco.dz/products/n32/n32-box.webp", "thumbnailUrl": "https://cdn.hoco.dz/products/n32/n32-box-240.webp", "mimeType": "image/webp", "size": 201728, "purpose": "PRODUCT_IMAGE" },
{ "id": "01a08ac3-86e8-7fda-b23c-2ce13bd908ec", "url": "https://cdn.hoco.dz/products/n32/n32-white-1.webp", "thumbnailUrl": "https://cdn.hoco.dz/products/n32/n32-white-1-240.webp", "mimeType": "image/webp", "size": 158720, "purpose": "PRODUCT_IMAGE" }
],
"specs": [
{ "group": "Alimentation", "label": "Puissance maximale", "value": "30W", "highlighted": true },
{ "group": "Connectique", "label": "Ports", "value": "USB-C (PD) + USB-A (QC3.0)", "highlighted": true },
{ "group": "Charge rapide", "label": "Technologie", "value": "PD 3.0, QC 3.0, SCP, FCP, AFC", "highlighted": true },
{ "group": "Alimentation", "label": "Entrée", "value": "100-240V ~ 50/60Hz 0.8A", "highlighted": true },
{ "group": "Alimentation", "label": "Sortie USB-C", "value": "5V/3A, 9V/3A, 12V/2.5A, 15V/2A, 20V/1.5A (30W Max)", "highlighted": true },
{ "group": "Alimentation", "label": "Sortie USB-A", "value": "5V/3A, 9V/2A, 12V/1.5A (18W Max)", "highlighted": true },
{ "group": "Général", "label": "Prise", "value": "EU (type C)", "highlighted": false },
{ "group": "Général", "label": "Matériau", "value": "PC ignifugé", "highlighted": false },
{ "group": "Général", "label": "Dimensions", "value": "84 × 41 × 29 mm", "highlighted": false },
{ "group": "Général", "label": "Poids", "value": "68 g", "highlighted": false }
],
"warrantyMonths": 6,
"isNew": false,
"isFavorite": true,
"priceLocked": false,
"defaultVariantId": "01a08ac2-8ce8-7069-8328-28fd7158a572",
"variants": [
{
"id": "01a08ac2-8ce8-7069-8328-28fd7158a572",
"sku": "N32_BLACK_EU",
"barcode": "6931474783950",
"label": "Noir",
"attributes": [
{ "key": "color", "label": "Couleur", "type": "ENUM", "value": "BLACK", "unit": null, "display": "Noir", "onProductSheet": true },
{ "key": "power", "label": "Puissance", "type": "NUMBER", "value": 30, "unit": "W", "display": "30 W", "onProductSheet": true },
{ "key": "connector", "label": "Ports", "type": "ENUM", "value": ["USB_C", "USB_A"], "unit": null, "display": "USB-C, USB-A", "onProductSheet": true },
{ "key": "fast_charge", "label": "Charge rapide", "type": "BOOLEAN", "value": true, "unit": null, "display": "Oui", "onProductSheet": true }
],
"imageIds": [
"01a08ac3-7360-7090-8e9c-cf36fff86a03",
"01a08ac3-7748-7823-9c91-f62f45f326f6",
"01a08ac3-7b30-75bc-a0d0-d5b0d312ca82",
"01a08ac3-7f18-7e9c-b667-5d8ef9eeaa26",
"01a08ac3-8300-7b9f-96f7-090a696f8174"
],
"price": { "ht": 209244, "ttc": 249000, "vatRate": 1900 },
"recommendedPrice": 320000,
"promotion": null,
"availability": "IN_STOCK",
"limits": { "minQty": 1, "qtyStep": 1, "maxQtyPerOrder": 20 },
"inCartQty": 10
},
{
"id": "01a08ac2-90d0-7efe-a2ae-802c0242b473",
"sku": "N32_WHITE_EU",
"barcode": "6931474783967",
"label": "Blanc",
"attributes": [
{ "key": "color", "label": "Couleur", "type": "ENUM", "value": "WHITE", "unit": null, "display": "Blanc" },
{ "key": "power", "label": "Puissance", "type": "NUMBER", "value": 30, "unit": "W", "display": "30 W" },
{ "key": "connector", "label": "Ports", "type": "ENUM", "value": ["USB_C", "USB_A"], "unit": null, "display": "USB-C, USB-A" },
{ "key": "fast_charge", "label": "Charge rapide", "type": "BOOLEAN", "value": true, "unit": null, "display": "Oui" }
],
"imageIds": ["01a08ac3-86e8-7fda-b23c-2ce13bd908ec"],
"price": { "ht": 209244, "ttc": 249000, "vatRate": 1900 },
"recommendedPrice": 320000,
"promotion": null,
"availability": "OUT_OF_STOCK",
"limits": { "minQty": 1, "qtyStep": 1, "maxQtyPerOrder": 20 },
"inCartQty": 0
}
],
"share": {
"url": "https://app.hoco.dz/p/01a08ac2-8900-7f6d-937c-c81b37e89a5b",
"text": "HOCO N32 Chargeur rapide 30W (USB-C + USB-A) — réf. N32_BLACK_EU"
},
"updatedAt": "2026-10-28T14:20:00Z"
}| Field | Notes |
|---|---|
variants[].price | The caller's price (« 2 490 DA »); null for guests, who see the price lock and the sign-up prompt instead. The block also shows recommendedPrice (« Prix conseillé 3 200 DA ») to everyone. |
variants[].limits | Drive the quantity stepper: start at minQty, step qtyStep, stop at maxQtyPerOrder (« Max : 20 pièces par commande »). Stock is not a limit the client knows: if the quantity is not available, cart.items.add answers 422 STOCK_INSUFFICIENT. |
variants[].inCartQty | Lets « Ajouter au panier » show what is already in the cart; maxQtyPerOrder applies to the cart total of the variant. |
variants[].attributes | Typed values used by the filters; specs are the descriptive sheet (RA-15). Both are maintained by the catalog team (BO-28, BO-71). |
availability (variant) | « En stock » or « En rupture »; « Ajouter au panier » and « Commander maintenant » are disabled on OUT_OF_STOCK. |
share | Link served by the reseller web (/p/{productId}, app link on mobile) and a text without price, safe to forward to end customers. |
warrantyMonths | From the product data of cahier §15.4 (Garantie); also used for after-sales claims (tickets.create). |
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | resellerId missing for a delegate; variantId not a variant of this product. |
| 403 | OUT_OF_SCOPE | Delegate's resellerId outside scope. |
| 404 | NOT_FOUND | Unknown product, or not visible to the caller (DRAFT, HIDDEN, ARCHIVED, or denied by a visibility rule). |
Rules & side effects
- The first successful call by a reseller account records
CATALOG_VIEWED(seecatalog.home). - « Commander maintenant » is client-side: add to the cart, then open the cart (there is no single-product checkout).
- Product images are public CDN files; the API never streams them.
Used by — RA-14 (« Détail produit »), RA-15 (« Caractéristiques »), RW-04, product sheet in DA-17, guest product page of RA-41.
GET /catalog/products/{productId}/similar
catalog.products.similar · Access optional — guest or reseller · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements CAT-08
Contract 1.1.0 (SRV-41), fix: complementary lists the pairs set on BO-28 first (ProductComplement, by position, visible ones), then the category fallback of catalog.complementaryCategories, without duplicates.
Purpose. Feeds « Produits similaires » under the product sheet: similar products (alternatives in the same sub-category) and complementary products (what sells with it), computed from the category as cahier §5.2 asks.
Request
| Path / query | Type | Required | Default | Notes |
|---|---|---|---|---|
productId | Uuid | yes | — | |
limit | integer 1–20 | no | 10 | Per list |
Response — 200 OK: { similar: ProductCard[], complementary: ProductCard[] }.
{
"similar": [
{
"id": "01a08fe8-e500-7017-b062-03c53547a2be",
"name": "ACEFAST A45 Chargeur rapide",
"brand": { "id": "01a05bfb-73e8-7a2c-8ba9-f89b715db9c7", "name": "ACEFAST" },
"category": { "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0" },
"imageUrl": "https://cdn.hoco.dz/products/a45/a45-black-1-480.webp",
"defaultVariantId": "01a08fe8-e8e8-762d-81d1-ab0daa011e53",
"price": { "ht": 331933, "ttc": 395000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 490000,
"availability": "IN_STOCK",
"isNew": true,
"isFavorite": false
}
],
"complementary": [
{
"id": "01a08ac7-1ce0-7b52-8b6a-3ec2aaccb8fb",
"name": "HOCO X89 Câble USB-C",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-a378-7a15-a596-11824871a56b", "name": "Câbles", "parentId": null },
"imageUrl": "https://cdn.hoco.dz/products/x89/x89-black-1-480.webp",
"defaultVariantId": "01a08ac7-20c8-70bf-b753-d181dfe71ff6",
"price": { "ht": 105042, "ttc": 125000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 160000,
"availability": "IN_STOCK",
"isNew": false,
"isFavorite": true
},
{
"id": "01a15889-fa80-7ff3-b294-f863a2ee34a6",
"name": "HOCO J86A Power Bank 10000mAh",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-ab48-7b23-9612-4a7450cd39ef", "name": "Power banks", "parentId": null },
"imageUrl": "https://cdn.hoco.dz/products/j86a/j86a-black-1-480.webp",
"defaultVariantId": "01a15889-fe68-733d-8056-75eec8b226f5",
"price": { "ht": 302521, "ttc": 360000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 450000,
"availability": "IN_STOCK",
"promotion": { "id": "01a19cfd-2c00-78b5-80c8-b4b9fb167f17", "label": "Offre flash -30 %", "price": { "ht": 211765, "ttc": 252000, "vatRate": 1900 } },
"isNew": true,
"isFavorite": false
},
{
"id": "01a094d8-5280-7779-b2fc-bf0e216609a3",
"name": "HOCO EW41 Écouteurs TWS",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-a760-7afa-a91b-ad281947800e", "name": "Écouteurs", "parentId": null },
"imageUrl": "https://cdn.hoco.dz/products/ew41/ew41-white-1-480.webp",
"defaultVariantId": "01a094d8-5668-7b74-a958-fc54830e96a2",
"price": { "ht": 579832, "ttc": 690000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 850000,
"availability": "IN_STOCK",
"promotion": { "id": "01a19cfd-2c00-78b5-80c8-b4b9fb167f17", "label": "Offre flash -30 %", "price": { "ht": 405882, "ttc": 483000, "vatRate": 1900 } },
"isNew": false,
"isFavorite": false
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown or invisible product. |
Rules & side effects
- Similar: same sub-category, effective price within ±40 % of the product's (by
recommendedPricefor guests), excluding the product itself, ranked by popularity; filled up from the parent category when fewer than 4. - Complementary: products of the categories linked to the product's category in the catalog settings (default pairs: chargers → cables and power banks; earphones → audio accessories; car holders → car chargers), best sellers first.
IN_STOCKproducts only; visibility and prices as for the list. The app shows both lists in one carousel (« Produits similaires », reference screen 4) and « Voir tout » opens the catalog on the product's sub-category.
GET /favorites
favorites.list · Access session — reseller (self) · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements CAT-09
Purpose. My favorite products as cards, most recently added first — shared between the mobile app and the web.
Request
| Query | Type | Required | Default |
|---|---|---|---|
cursor | string | no | — |
limit | integer 1–100 | no | 20 |
Response — 200 OK: { items: ProductCard[], nextCursor }, every card with isFavorite: true.
{
"items": [
{
"id": "01a08ac2-8900-7f6d-937c-c81b37e89a5b",
"name": "HOCO N32 Chargeur rapide 30W (USB-C + USB-A)",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-9ba8-779f-8030-3262210b2afb", "name": "Chargeurs muraux", "parentId": "01a05c04-97c0-76c5-9c89-9cf30f53d5b0" },
"imageUrl": "https://cdn.hoco.dz/products/n32/n32-black-1-480.webp",
"defaultVariantId": "01a08ac2-8ce8-7069-8328-28fd7158a572",
"price": { "ht": 209244, "ttc": 249000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 320000,
"availability": "IN_STOCK",
"isNew": false,
"isFavorite": true
},
{
"id": "01a08ac7-1ce0-7b52-8b6a-3ec2aaccb8fb",
"name": "HOCO X89 Câble USB-C",
"brand": { "id": "01a05bfb-7000-73a7-ac3c-c71515d5e3b3", "name": "HOCO" },
"category": { "id": "01a05c04-a378-7a15-a596-11824871a56b", "name": "Câbles", "parentId": null },
"imageUrl": "https://cdn.hoco.dz/products/x89/x89-black-1-480.webp",
"defaultVariantId": "01a08ac7-20c8-70bf-b753-d181dfe71ff6",
"price": { "ht": 105042, "ttc": 125000, "vatRate": 1900 },
"priceLocked": false,
"recommendedPrice": 160000,
"availability": "IN_STOCK",
"isNew": false,
"isFavorite": true
}
],
"nextCursor": null
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Guest — the app shows the sign-up prompt of RA-41. |
| 403 | PERMISSION_DENIED | Caller is not a reseller. |
Rules & side effects — a favorite whose product is no longer visible (hidden, archived, denied) is kept in Favorite but not listed; it reappears if the product becomes visible again. Out-of-stock favorites are listed with « En rupture ».
Used by — RA-16 (« Favoris »), RW-05.
PUT /favorites/{productId}
favorites.add · Access session — reseller (self) · Apps RA, RW · Idempotency-Key — (idempotent by nature) · Release V1 · Requirements CAT-09
Purpose. Marks a product as favorite (the heart on cards and on the product sheet).
Request — path productId; no body.
PUT /v1/favorites/01a08fe8-e500-7017-b062-03c53547a2beResponse — 204 No Content, also when the product was already a favorite.
Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Guest — sign-up prompt (RA-41). |
| 403 | PERMISSION_DENIED | Caller is not a reseller. |
| 404 | NOT_FOUND | Unknown or invisible product. |
| 422 | FAVORITES_LIMIT_REACHED | The reseller already has 500 favorites (details.limit). |
Rules & side effects — favorites belong to the reseller (shared by all their devices and the web), not to the device; no event, no notification. The client updates the heart optimistically and rolls back on error.
Used by — hearts on RA-12, RA-14, RW-03, RW-04.
DELETE /favorites/{productId}
favorites.remove · Access session — reseller (self) · Apps RA, RW · Idempotency-Key — (idempotent by nature) · Release V1 · Requirements CAT-09
Purpose. Removes a product from my favorites.
Request — path productId; no body.
Response — 204 No Content, also when the product was not a favorite (or is no longer visible).
Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Guest. |
| 403 | PERMISSION_DENIED | Caller is not a reseller. |
Rules & side effects — hard delete of the Favorite row; no event.
Used by — RA-16 (swipe or heart), hearts on RA-12, RA-14, RW-05.
Notifications & announcements
The in-app inbox, push notifications and their deep links, the live event stream of the reseller and delegate apps, channel preferences and back-office announcements.
Cart & checkout
The reseller's server-side cart shared by the app and the web — lines, re-validated prices and availability, promotions, totals, blocking issues — and the idempotent checkout that submits an order for back-office confirmation.