HOCO B2B
SRSAPI reference

Invitations & attribution (public + reseller)

Public operations behind the delegates' invitation links and QR codes: resolving a code, attributing the app install to the inviting delegate, and the /i/ landing page that opens the right app or store.

A délégué (field sales representative) invites a point of sale from the delegate app (DA-16): the server creates an Invitation for the reseller record and the delegate, with a code, a link and a QR code, shared by WhatsApp, SMS or link (IVT-01, IVT-02 — operations delegate.invitations.create and delegate.invitations.markShared). This page covers what happens on the reseller's side, before they have an account: opening the link, installing the app, and attributing the install and the sign-up to the delegate (IVT-03, IVT-04, cahier §6.6: « L'ouverture du lien conduit vers la bonne application et attribue correctement l'inscription »).

All three operations are public: they never return personal data beyond the store name and the delegate's first name, and they are limited to 30 requests per minute per IP.

MethodPathOperationAccessAppsRelease
GET/public/invitations/{code}invitations.resolve
Resolve an invitation code: store name, delegate first name, validity (no personal data beyond that)
publicRA · RWV1
POST/public/invitations/attributioninvitations.attribute
Report first app open with install referrer or invitation code (device-scoped, rate limited)
public
Idempotency-Key
RAV1
GET/i/{code}invitations.landing
Web landing for invitation links: opens the app, else the store, else the web sign-up (served by the reseller web app)
publicRWV1

Besides the errors listed per operation, the API operations can answer 426 UPGRADE_REQUIRED, 429 RATE_LIMITED, 500 INTERNAL_ERROR and 503 MAINTENANCE.

Codes and links

ItemFormatExample
Code8 characters from an alphabet without ambiguous characters — no 0, O, 1, I, L (Invitation model)AB12CD34 (the documentation's illustrative code; a generated code looks like K7QM4XPA)
DisplayedTwo groups of fourAB12-CD34
LinkReseller web domain + /i/ + codehttps://app.hoco.dz/i/AB12CD34
QR codeThe link, error correction level Mrendered by the delegate app (qr_flutter) and the printed flyer (DQA-07)
Signed tokenHMAC(INVITE_SECRET, invitationId), base64url; only its SHA-256 is stored (Invitation.tokenHash)carried by the Play referrer and the app link query
Play Store linkStore URL with the install referrer inv=<code>&t=<token>https://play.google.com/store/apps/details?id=dz.hoco.reseller&referrer=inv%3DAB12CD34%26t%3D9xQ2…

Input normalisation (every operation that receives a code): upper-case, remove spaces and dashes. ab12-cd34 and AB12 CD34 both resolve to AB12CD34.

Why a token as well. A code can be typed by anyone who saw it; the token proves that the install or the app opening came from the real link. Attribution through a valid token is recorded with source INSTALL_REFERRER or APP_LINK; through a code alone with source CODE_ENTERED.

Validity. An invitation is valid invitations.validityDays days after creation (default 30). A point of sale has at most one live invitation: creating a new one revokes the previous one (revokedAt). An invitation is used once its point of sale has a user account.

Activation funnel

ValueUI label (fr)Meaning
SENTInvitation envoyée
APP_INSTALLEDApplication téléchargée
ACCOUNT_CREATEDCompte créé
PHONE_VERIFIEDTéléphone vérifié
CATALOG_VIEWEDCatalogue consulté
FIRST_ORDERPremière commande
StepRecorded byNotes
SENTdelegate.invitations.create / markSharedWith the share channel (ShareChannel).
APP_INSTALLEDinvitations.attribute; back-filled (inferred) by a later stepOnly the first device per invitation counts toward the delegate's APP_INSTALLS objective.
ACCOUNT_CREATEDauth.signup.resellerAlso moves the reseller to ACCOUNT_CREATED.
PHONE_VERIFIEDauth.signup.reseller (the phone is verified by OTP just before)Same timestamp as ACCOUNT_CREATED.
CATALOG_VIEWEDFirst signed-in call to catalog.home or catalog.products.listGuest browsing before sign-up does not count.
FIRST_ORDERFirst order submitted (cart.checkout, or an assisted order confirmed by the reseller)Attributed to the reseller and the source delegate (ORD-09).

Each step is an InvitationEvent (step, time, device id when known, source INSTALL_REFERRER, APP_LINK, CODE_ENTERED, DELEGATE or SYSTEM); the invitation keeps the first time of each step (appInstalledAt, accountCreatedAt…) and its currentStep. The funnel is monotonic: reaching a later step back-fills the missing earlier ones with inferred: true. Every step emits invitation.step_completed to the delegate (event catalogue) and updates the funnel on DA-16 and the back-office invitation report (admin.invitations.list). The reseller statuses APP_INSTALLED and ACCOUNT_CREATED follow automatically (CRM-04), never moving a reseller backwards.

Attribution precedence

When a reseller signs up, the invitation credited to a delegate is chosen as follows (AUTH-05 R6):

Sign-upOrder of precedence
Claim (the verified phone matches a known point of sale)1. the live invitation of that point of sale · 2. a valid code typed on RA-03 or carried by the link · 3. none — the creating delegate keeps the credit through createdByDelegateId
New record1. a valid code typed or carried by the link (invitationCode) · 2. the invitation recorded by invitations.attribute for the same X-Device-Id in the last 30 days · 3. none

A code never grants ownership of a record — only the verified phone does. A code belonging to another point of sale's invitation is recorded as an InvitationEvent on that invitation for review and does not move the attribution; the sign-up reports it in invitationCodeIgnored: "OTHER_POINT_OF_SALE", and the validator sees the possible duplicate on BO-13.

Operations

GET /public/invitations/{code}

invitations.resolve · Access public · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements IVT-04

Purpose. Tells the reseller app, the web sign-up and the landing page whether a code is valid, and gives the two facts shown to the invitee: the store name and the delegate's first name.

Request

Path parameterTypeNotes
codestringAny accepted spelling (see normalisation).

Response — 200 OK, Cache-Control: private, no-store.

{
  "code": "AB12CD34",
  "displayCode": "AB12-CD34",
  "storeName": "Magasin El Wafa",
  "delegateFirstName": "Yacine",
  "expiresAt": "2026-12-01T10:05:00Z"
}
FieldNotes
storeNameThe point of sale the invitation was created for — RA-03 shows « Invitation de Yacine pour Magasin El Wafa ».
delegateFirstNameFirst name only; never the delegate's phone or last name.

Errors

StatusCodeWhen
400VALIDATION_FAILEDNot 8 characters of the code alphabet after normalisation (field code INVALID_INVITATION_CODE).
404INVITATION_NOT_FOUNDNo invitation with this code.
409INVITATION_ALREADY_USEDThe point of sale already has an account — offer « Se connecter ».
410INVITATION_EXPIREDPast its validity or replaced by a newer invitation (details.reason: EXPIRED, REVOKED) — « Invitation expirée : demandez un nouveau lien à votre délégué. »

Rules & side effects — read-only: no step is recorded and no personal data is returned. Failed lookups count toward the per-IP limit (30 per minute), which makes guessing codes impractical (31⁸ ≈ 8.5 × 10¹¹ codes).

Used by — RA-03 (code typed or pre-filled: show the inviter before « Recevoir le code »), RA-02 when the app was opened by a link, the sign-up page of RW-01, and the /i/{code} landing (server-side).

POST /public/invitations/attribution

invitations.attribute · Access public (device-scoped) · Apps RA · Idempotency-Key required · Release V1 · Requirements IVT-03, IVT-04

Purpose. Reports that the reseller app was opened for the first time thanks to an invitation — from the Play install referrer, an app link or a typed code — so the install is credited to the delegate even before sign-up.

Request

HeaderNotes
X-Device-IdRequired; must equal deviceId in the body.
X-App-Version, X-App-PlatformAs on every mobile call.
interface AttributionRequest {
  code?: string;                     // from an app link (/i/AB12CD34) or typed on RA-03
  token?: string;                    // the t parameter of an app link (/i/AB12CD34?t=…)
  installReferrer?: string;          // raw Play Install Referrer string, e.g. "inv=AB12CD34&t=9xQ2…"
  referrerClickedAt?: Iso;           // Install Referrer API: referrerClickTimestampSeconds
  installBeganAt?: Iso;              // Install Referrer API: installBeginTimestampSeconds
  deviceId: string;
  platform: "android" | "ios";
  appVersion: string;
}
{
  "installReferrer": "inv=AB12CD34&t=9xQ2bW7kR4mN1pL6vT3c&utm_source=hoco_invite&utm_medium=whatsapp",
  "referrerClickedAt": "2026-11-02T16:41:07Z",
  "installBeganAt": "2026-11-02T16:42:30Z",
  "deviceId": "5f8c2a1e-7b3d-4e9f-a6c0-1d2e3f4a5b6c",
  "platform": "android",
  "appVersion": "1.4.2"
}

Response — 200 OK.

{
  "attributed": true,
  "alreadyRecorded": false,
  "step": "APP_INSTALLED",
  "invitation": {
    "code": "AB12CD34",
    "displayCode": "AB12-CD34",
    "storeName": "Magasin El Wafa",
    "delegateFirstName": "Yacine"
  }
}

An install without invitation (organic referrer such as utm_source=google-play&utm_medium=organic) answers 200 with { "attributed": false, "alreadyRecorded": false, "step": null, "invitation": null }.

FieldNotes
alreadyRecordedtrue when this device was already attributed to this invitation (replay, reinstall): nothing new is recorded.
invitationThe app keeps it locally to pre-fill and display the code on RA-03 and to send invitationCode at sign-up.

Errors

StatusCodeWhen
400VALIDATION_FAILEDNeither code nor installReferrer; deviceId differs from X-Device-Id (DEVICE_ID_MISMATCH); malformed code.
404INVITATION_NOT_FOUNDThe code (given or found in the referrer) does not exist.
409INVITATION_ALREADY_USEDThe point of sale already has an account.
410INVITATION_EXPIREDExpired or revoked.

Rules & side effects

  1. The code comes from code when given, otherwise from the inv parameter of installReferrer; the token from token or the referrer's t. A token that does not match Invitation.tokenHash is ignored (the call then counts as CODE_ENTERED).
  2. Idempotent per device and invitation: the first call records an InvitationEvent APP_INSTALLED (device id, source INSTALL_REFERRER, APP_LINK or CODE_ENTERED, and in metadata the raw referrer, the referrer timestamps and the app version); later calls for the same pair return alreadyRecorded: true. The idempotency record is scoped to the device (device:<X-Device-Id>).
  3. Effects of a first record: the invitation reaches APP_INSTALLED; the reseller status moves to APP_INSTALLED if it was NEW_PROSPECT, TO_CONTACT, INTERESTED, TO_FOLLOW_UP or NOT_INTERESTED (automatic ResellerStatusChange); invitation.step_completed is sent to the delegate (« Magasin El Wafa a installé l'application »).
  4. Only the first device of an invitation counts toward the delegate's APP_INSTALLS objective; every device is kept for the attribution precedence of sign-up. A gap of less than 10 seconds between referrerClickedAt and installBeganAt, or many devices on one invitation, is flagged on the back-office invitation report (fraud hint, not a block).
  5. Client flow: on the first launch (flag in local storage), the Android app reads the Install Referrer API once and calls this operation; an app opened through an app link calls it with code; a code typed on RA-03 and resolved successfully is also sent here, so the install counts even if the sign-up is abandoned. Errors are not shown to the user, except on RA-03 where the typed code is concerned.

Used by — first launch of the reseller app (before RA-02), app-link handling, and RA-03 after a typed code is resolved.

GET /i/{code}

invitations.landing · Access public · Apps RW · Idempotency-Key — · Release V1 · Requirements IVT-04

Purpose. The page behind every invitation link and QR code: it opens the reseller app when installed, otherwise sends the visitor to the store with the code attached, otherwise to the web sign-up with the code pre-filled.

This is not an API route: it is a page of the reseller web app, served at https://app.hoco.dz/i/{code} (not under /v1). It calls invitations.resolve server-side.

Request

Path parameterNotes
codeAny accepted spelling; the page redirects to the canonical /i/AB12CD34 when the spelling differs.

Response — by platform (detected from the User-Agent):

VisitorBehaviour
Android, reseller app installedThe OS opens the app directly through the verified App Link (intent filter https://app.hoco.dz/i/*, autoVerify); the page is not even loaded. The app reads the code from the path (and t from the query when the shared link carries it), calls invitations.attribute and opens RA-03 pre-filled.
Android, app not installed200 HTML page « Yacine vous invite à rejoindre HOCO — Magasin El Wafa » that redirects after 300 ms to the Play Store URL with referrer=inv%3DAB12CD34%26t%3D<token> (the page rebuilds the token from the invitation id); button « Installer l'application » as fallback. After the install, the app finds inv and t in the install referrer.
Android, in-app browser that ignores App LinksThe « Ouvrir l'application » button uses intent://i/AB12CD34#Intent;scheme=https;package=dz.hoco.reseller;S.browser_fallback_url=<Play Store URL>;end.
iOSUniversal Link (apple-app-site-association, path /i/*) opens the app when installed. Otherwise the page offers the App Store (when the iOS app is published — HOCO's decision) and « Continuer sur le web » → /inscription?code=AB12CD34. There is no install referrer on iOS: the page shows the code AB12-CD34 large with « Saisissez ce code lors de votre inscription ».
Desktop302 to /inscription?code=AB12CD34 (web sign-up RW-01 with the code pre-filled), which also shows a QR code of the link to continue on a phone.
Invalid, expired or used code200 page « Invitation invalide ou expirée » (or « Ce magasin a déjà un compte » with « Se connecter »), with buttons to install the app or sign up without a code; the store name is not shown.

Response headers: Cache-Control: private, no-store, X-Robots-Tag: noindex. Open Graph tags are generic (« Invitation HOCO — rejoignez le réseau des revendeurs ») so link previews in WhatsApp never show the store name.

Errors — none as JSON; invalid codes render the page above. More than 30 requests per minute from one IP answer 429 with a plain page.

Rules & side effects

  1. The page records nothing: attribution happens in the app (invitations.attribute) or at sign-up (invitationCode).
  2. The reseller web must serve /.well-known/assetlinks.json (package dz.hoco.reseller, SHA-256 of the Play signing certificate) and /.well-known/apple-app-site-association (team id + bundle id, /i/*). Imad provides the fingerprints; Anis deploys the files (WEB-01, MRA-09).
  3. Assumption — the landing page and the .well-known files go live with V1, even though the full reseller web opens at generalisation (P3): invitations are used from the pilot.

Used by — every shared invitation link and QR code (DA-16, the printed flyer); the reseller app's link handling on RA-02 and RA-03; the web sign-up RW-01.

On this page