HOCO B2B
SRSAPI reference

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 with price: null and priceLocked: 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_GROS or DETAIL) 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_STOCK or OUT_OF_STOCK; no stock number leaves the back-office (STK-07).
  • Only sellable products. ACTIVE and OUT_OF_STOCK products that the visibility rules allow for the caller; DRAFT, HIDDEN and ARCHIVED never 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.
MethodPathOperationAccessAppsRelease
GET/catalog/homecatalog.home
Reseller home aggregate: banners, daily offer, quick tiles, popular categories, recommended products, account tiles — guests allowed, without prices
guest or sessionRA · RWV1
GET/catalog/brandscatalog.brands
Active brands — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/categoriescatalog.categories
Category tree with product counts visible to the caller — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/productscatalog.products.list
Search, filter and sort products with the caller's resolved price and availability (cursor pagination) — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/products/suggestcatalog.products.suggest
Type-ahead suggestions (products, brands, categories) — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/facetscatalog.facets
Filter facets with counts for the current query: categories and sub-categories, brands, price range, attributes, availability, promotion — guests allowed (no price facet)
guest or sessionRA · RW · DAV1
GET/catalog/products/by-barcode/{barcode}catalog.products.byBarcode
Find a product variant by barcode — guests allowed, without prices
guest or sessionRA · DAV1
GET/catalog/products/{productId}catalog.products.get
Product detail: variants, gallery, specs, prices, availability, quantity limits, active promotion — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/products/{productId}/similarcatalog.products.similar
Similar and complementary products — guests allowed, without prices
guest or sessionRA · RWV1
GET/favoritesfavorites.list
My favorite products
selfRA · RWV1
PUT/favorites/{productId}favorites.add
Add a product to favorites (idempotent)
selfRA · RWV1
DELETE/favorites/{productId}favorites.remove
Remove a product from favorites
selfRA · RWV1

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.

CallerPricesVisibility rulesFavorites, isFavoriteInvitation stepExtra parameter
Guest (no session)price: null, priceLocked: true; promotion prices nullOnly products visible to all resellers401 on favorites; isFavorite: false—Price filters and price sorts refused (401 AUTH_REQUIRED_FOR_PRICES)
Reseller PENDING_VALIDATIONPrices of the declared customerType when catalog.pricesVisibleBeforeValidation = true (default); otherwise as a guestApplied for the reseller✓CATALOG_VIEWED on first read—
Reseller ACTIVEPrices of Reseller.customerTypeApplied for the reseller✓CATALOG_VIEWED on first read—
DelegatePrices of the reseller given in resellerId (required on priced reads)Applied for that resellerisFavorite: false; favorites 403 PERMISSION_DENIEDNever set by a delegateresellerId — 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

  1. ProductVariant stores three selling prices HT in centimes — priceSuperGros, priceDemiGros, priceDetail — plus vatRate (basis points, 1900 by default) and recommendedPrice (prix conseillé, TTC). The caller's customerType picks one (PRC-01). There are no quantity tiers in V1.
  2. Every unit price is returned as a Price: ht, ttc = round(ht × (1 + vatRate / 10 000)) to the centime, and vatRate. The apps display TTC (« 2 490 DA »).
  3. 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).
  4. Prices are computed at read time and never cached across callers (Cache-Control: private, no-store on priced responses). A change of customer type by the back-office takes effect on the next read.
  5. 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).

ParameterTypeMeaning
qstring, 2–80 charsFree text on product name, model reference, variant SKU, barcode (exact), brand and category names; case- and accent-insensitive (unaccent + trigram)
categoryIdUuidA category or sub-category; a category matches its sub-categories too
brandIdUuid, repeatablebrandId=…&brandId=… — any of the brands
priceMin, priceMaxMoney (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, repeatableAttribute 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
availabilityIN_STOCK | OUT_OF_STOCKAvailability of at least one matching variant
promotiontrueOnly products with an active promotion for the caller
newtrueOnly 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).

Reference screen 3 — search, category chips, the three tabs and availability pills (prices hidden for guests, availability never a quantity).
Reference screen 3 — search, category chips, the three tabs and availability pills (prices hidden for guests, availability never a quantity). — client reference (cahier des charges)

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).

Reference screen 1 — every block maps to one field of HomeView.
Reference screen 1 — every block maps to one field of HomeView. — client reference (cahier des charges)

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.

FieldNotes
badges.cartItemsNumber of cart lines — the bottom-bar badge and « 4 articles » of the cart (reference screens 1 and 5). Same value as cart.get → itemsCount.
bannersSame items and rules as banners.list with placement=HOME_CAROUSEL, at most 6; guests only get banners whose audience is every reseller.
dailyOfferThe active promotion flagged isDailyOffer for the caller, null when none. The client renders the countdown from endsAt − serverTime, not from the device clock.
popularCategoriesUp 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.
recommendedRules-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.creditAvailableCopied 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.loyaltyPointsAlways 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

StatusCodeWhen
403PERMISSION_DENIEDCaller is a delegate or a staff user.

Rules & side effects

  1. Read-only except the invitation funnel: the first successful catalog.home, catalog.products.list or catalog.products.get by a reseller account records the CATALOG_VIEWED step of the reseller's invitation, once (InvitationEvent, outbox invitation.step_completed → push to the inviting delegate) — see the invitation funnel. Guests and delegates never trigger it.
  2. Each block is computed independently with a 300 ms budget; a block that fails or times out is returned empty ([] or null) and logged, so the home always renders (cahier §5 acceptance criterion « l'accueil charge les informations autorisées »).
  3. 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

QueryTypeRequiredNotes
resellerIdUuiddelegates, optionalCounts 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

StatusCodeWhen
403OUT_OF_SCOPEDelegate 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

QueryTypeRequiredNotes
resellerIdUuiddelegates, optionalAs 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

StatusCodeWhen
403OUT_OF_SCOPEDelegate passes a resellerId outside their scope.

Rules & side effects

  1. productCount counts visible products (ACTIVE and OUT_OF_STOCK) of the category and its sub-categories; categories with 0 are omitted. The chip « Tous » is client-side.
  2. The tree has two levels in V1 (category → sub-category), matching the parentId carried by every ProductCard.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

QueryTypeRequiredDefaultNotes
q, categoryId, brandId, priceMin, priceMax, attr.<key>, availability, promotion, new—no—See Search and filters
sortrelevance | popular | newest | price_asc | price_descnorelevance with q, else popularPrice sorts signed-in only
resellerIdUuiddelegates: yes—Prices and visibility of that reseller (scope-checked); refused for resellers other than their own id
cursorstringno—Opaque, from nextCursor
limitinteger 1–100no20

Example — a reseller opens Chargeurs sorted by price:

GET /v1/catalog/products?categoryId=01a05c04-97c0-76c5-9c89-9cf30f53d5b0&sort=price_asc&limit=3

The 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.

FieldNotes
items[].priceUnit price of defaultVariantId for the caller's customer type, before promotion; with attribute filters, defaultVariantId is the first matching variant.
items[].promotionPresent 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[].availabilityOf the displayed variant. Out-of-stock products are listed (after in-stock ones under relevance and popular) unless availability=IN_STOCK is sent.
nextCursorKeyset cursor bound to the sort and filters; reusing it with other filters returns 400 VALIDATION_FAILED (CURSOR_MISMATCH).

Errors

StatusCodeWhen
401AUTH_REQUIRED_FOR_PRICESA guest sends priceMin, priceMax or a price sort — the app shows the sign-up prompt of RA-41 (not a session expiry).
400VALIDATION_FAILEDField 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.
403OUT_OF_SCOPEDelegate's resellerId is outside their zones and assignments, or a reseller sends another reseller's id.
403PERMISSION_DENIEDStaff without a delegate profile sends resellerId (see the assumption above).

Rules & side effects

  1. 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.
  2. The first successful call by a reseller account records CATALOG_VIEWED (see catalog.home rule 1).
  3. 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).
  4. Performance: served from indexed columns and a denormalised product_search view 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

QueryTypeRequiredDefaultNotes
qstring 2–80yes—
limitinteger 1–10no5Per section
resellerIdUuidno—Delegates: suggestions limited to that reseller's visible products
GET /v1/catalog/products/suggest?q=charg&limit=3

Response — 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 }
  ]
}
FieldNotes
products[].matchedOnNAME, 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

StatusCodeWhen
400VALIDATION_FAILEDq missing or shorter than 2 characters.
403OUT_OF_SCOPEDelegate'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-c71515d5e3b3

Response — 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 }
}
FieldNotes
countsDisjunctive: 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.
categoriesFull two-level tree of categories that have results; the selected node carries selected: true.
priceRangeLowest and highest effective TTC unit price among the results — bounds of the price slider. null for guests (prices are hidden).
attributesDefinitions 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.bucketsThree to five buckets precomputed for the slider histogram.

Errors

StatusCodeWhen
401AUTH_REQUIRED_FOR_PRICESA guest sends a price filter.
400VALIDATION_FAILEDSame field codes as catalog.products.list (UNKNOWN_ATTRIBUTE, INVALID_RANGE, REQUIRED).
403OUT_OF_SCOPEDelegate's resellerId outside scope.

Rules & side effects

  1. Computed on the same visibility, price and availability rules as the list, so total equals the number of items the list would return with the same filters.
  2. 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 »).
  3. 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 / queryTypeRequiredNotes
barcodestring, 8–14 digitsyesEAN-8, UPC-A, EAN-13 or GTIN-14 as read by mobile_scanner
resellerIdUuiddelegates: yesPrices and visibility of that reseller
GET /v1/catalog/products/by-barcode/6931474783950

Response — 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

StatusCodeWhen
400VALIDATION_FAILEDNot 8–14 digits (INVALID_BARCODE), or resellerId missing for a delegate.
403OUT_OF_SCOPEDelegate's resellerId outside scope.
404NOT_FOUNDNo 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.

Reference screen 4 — price, availability and the quantity limit come from the selected VariantView; the stock hint of the mock-up (« Plus que 20 pièces ») is not implemented (§1b).
Reference screen 4 — price, availability and the quantity limit come from the selected VariantView; the stock hint of the mock-up (« Plus que 20 pièces ») is not implemented (§1b). — client reference (cahier des charges)

Request

Path / queryTypeRequiredNotes
productIdUuidyes
variantIdUuidnoVariant to pre-select (from a scan or a deep link); only changes defaultVariantId in the response
resellerIdUuiddelegates: yes
If-None-Matchheaderno304 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"
}
FieldNotes
variants[].priceThe 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[].limitsDrive 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[].inCartQtyLets « Ajouter au panier » show what is already in the cart; maxQtyPerOrder applies to the cart total of the variant.
variants[].attributesTyped 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.
shareLink served by the reseller web (/p/{productId}, app link on mobile) and a text without price, safe to forward to end customers.
warrantyMonthsFrom the product data of cahier §15.4 (Garantie); also used for after-sales claims (tickets.create).

Errors

StatusCodeWhen
400VALIDATION_FAILEDresellerId missing for a delegate; variantId not a variant of this product.
403OUT_OF_SCOPEDelegate's resellerId outside scope.
404NOT_FOUNDUnknown product, or not visible to the caller (DRAFT, HIDDEN, ARCHIVED, or denied by a visibility rule).

Rules & side effects

  1. The first successful call by a reseller account records CATALOG_VIEWED (see catalog.home).
  2. « Commander maintenant » is client-side: add to the cart, then open the cart (there is no single-product checkout).
  3. 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 / queryTypeRequiredDefaultNotes
productIdUuidyes—
limitinteger 1–20no10Per 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

StatusCodeWhen
404NOT_FOUNDUnknown or invisible product.

Rules & side effects

  1. Similar: same sub-category, effective price within ±40 % of the product's (by recommendedPrice for guests), excluding the product itself, ranked by popularity; filled up from the parent category when fewer than 4.
  2. 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.
  3. IN_STOCK products 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.

Used by — RA-14, RW-04.

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

QueryTypeRequiredDefault
cursorstringno—
limitinteger 1–100no20

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

StatusCodeWhen
401UNAUTHENTICATEDGuest — the app shows the sign-up prompt of RA-41.
403PERMISSION_DENIEDCaller 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-03c53547a2be

Response — 204 No Content, also when the product was already a favorite.

Errors

StatusCodeWhen
401UNAUTHENTICATEDGuest — sign-up prompt (RA-41).
403PERMISSION_DENIEDCaller is not a reseller.
404NOT_FOUNDUnknown or invisible product.
422FAVORITES_LIMIT_REACHEDThe 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

StatusCodeWhen
401UNAUTHENTICATEDGuest.
403PERMISSION_DENIEDCaller 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.

On this page