Administration, settings & audit
Back-office administration — users, sessions, roles and permissions with guard rails, the activity journal, business settings, reference lists, app versions, support regions, data imports and global search.
These operations administer the platform (cahier §3.1, §8, §10, §13.2): staff users and their roles (BO-61), the roles × permissions matrix (BO-62), the activity journal (BO-63), business settings (BO-64), reference lists (BO-65), mobile app versions (BO-66), data imports (BO-67), support regions and WhatsApp numbers (BO-70) and the global search of the app shell (BO-03). Authorisation model: permissions matrix.
Who uses it. The Super administrateur (cahier §3: « paramétrage global, rôles, permissions et audit »); managers holding users.* for their teams.
| Permission | Unlocks on this page |
|---|---|
users.view · users.create · users.update · users.administer | Users read · create · edit, status, password reset · sessions |
roles.view · roles.administer | Roles and permission catalogue · editing permission sets |
audit.view · audit.export | Activity journal · its export |
settings.view · settings.administer | Settings, reference lists, app versions, support regions (read) · their changes and data imports |
Re-authentication for sensitive actions (roles, user status, settings) is an open decision (auth.reauthMinutes, default 15 min): when enabled, such calls answer 403 REAUTH_REQUIRED if the session's last password entry is older — see the decision register.
Audit. Everything here is journaled: CREATE/UPDATE/STATUS_CHANGE on users, PERMISSION_CHANGE on roles and user roles, UPDATE per setting key (before/after), IMPORT on apply, LOGOUT for revoked sessions.
| Method | Path | Operation | Access | Apps | Release |
|---|---|---|---|---|---|
| GET | /admin/users | admin.users.listStaff and delegate users | users.view | BO | V1 |
| POST | /admin/users | admin.users.createCreate a user with roles (invitation SMS/email with temporary password) | users.create | BO | V1 |
| GET | /admin/users/{userId} | admin.users.getA staff or delegate user (detail page, global search) | users.view | BO | V1 |
| PATCH | /admin/users/{userId} | admin.users.updateEdit user, roles | users.update | BO | V1 |
| POST | /admin/users/{userId}/status | admin.users.setStatusSuspend, reactivate or disable (sessions revoked; history kept) | users.update | BO | V1 |
| POST | /admin/users/{userId}/reset-password | admin.users.resetPasswordSend a reset code / temporary password | users.update | BO | V1 |
| GET · DELETE | /admin/users/{userId}/sessions[/{sessionId}] | admin.users.sessionsList or revoke a user's sessions | users.administer | BO | V1 |
| GET | /admin/staff/assignable | admin.staff.assignableActive staff for assignee pickers (name and roles only) | crm.view | BO | V1 |
| GET | /admin/roles | admin.roles.listRoles with their permissions | roles.view | BO | V1 |
| PUT | /admin/roles/{roleKey}/permissions | admin.roles.updatePermissionsReplace a role's permission set (super admin; system guard rails) | roles.administer | BO | V1 |
| GET | /admin/permissions | admin.permissions.listPermission catalogue (module × action) | roles.view | BO | V1 |
| GET | /admin/audit-logs | admin.audit.listActivity journal with filters (actor, action, entity, app, dates) | audit.view | BO | V1 |
| GET | /admin/settings | admin.settings.getBusiness parameters grouped by domain | settings.view | BO | V1 |
| PATCH | /admin/settings | admin.settings.updateUpdate parameters (validated, audited) | settings.administer | BO | V1 |
| GET · POST · PATCH | /admin/reference-lists/{listKey}[/{itemId}] | admin.referenceLists.crudEdit reference list items (deactivate, never delete in use) | settings.administer | BO | V1 |
| GET · PUT | /admin/app-versions[/{app}/{platform}] | admin.appVersions.crudVersion policy per app and platform; maintenance mode | settings.administer | BO | V1 |
| GET · POST · PATCH · DELETE | /admin/support-regions[/{regionId}] | admin.supportRegions.crudSupport regions: wilayas covered, WhatsApp number, hours | settings.administer | BO | V1 |
| POST | /admin/imports/{type} | admin.imports.createImport resellers, users or stock from Excel/CSV (dry-run then apply) | settings.administer | BO | V1 |
| GET | /admin/imports/{importId} | admin.imports.getImport job status and row-level report | settings.administer | BO | V1 |
| GET | /admin/imports | admin.imports.listImport history (jobs without their row report), newest first | settings.administer | BO | V1 |
| GET | /admin/imports/{type}/template | admin.imports.templateDownload the template of an import type (XLSX or CSV with the expected columns) | settings.administer | BO | V1 |
| GET | /admin/search | admin.searchGlobal search across resellers, orders, products, invoices (permission-filtered) | any | BO | V1 |
Schemas
AuditLog
interface AuditLog {
id: Uuid;
at: Iso;
actor: UserSummary | null; // null = SYSTEM (jobs)
actorRoles: string[]; // RoleKey[] at the time
app: ClientApp; // DASHBOARD | RESELLER_APP | RESELLER_WEB | DELEGATE_APP | SYSTEM
action: AuditAction; // LOGIN … OVERRIDE
entityType: string; // Prisma model name: "Order", "Reseller", "Setting"…
entityId: Uuid | null;
entityLabel?: string; // "CMD-2026-000158", "stock.deductOn"
summary: string; // French sentence built server-side
changes?: { field: string; before: unknown; after: unknown }[]; // secrets never logged; purchase prices masked without pricing.view
reason?: string;
context: { ip: string; userAgent: string; requestId: string; sessionId?: Uuid; deviceId?: string };
}ImportJob
interface ImportJob {
id: Uuid;
type: "PRODUCTS" | "RESELLERS" | "USERS" | "STOCK"; // ImportType
mode: "DRY_RUN" | "APPLY"; // ImportMode
status: JobStatus;
file: FileRef; // IMPORT_FILE: XLSX (first sheet) or CSV UTF-8, ≤ 10 MB
options: Record<string, unknown>;
dryRunId?: Uuid;
summary?: { totalRows: number; validRows: number; warningRows: number; errorRows: number; toCreate: number; toUpdate: number; unchanged: number; applied?: number; skipped?: number };
rows?: { items: { row: number; status: "VALID" | "WARNING" | "ERROR" | "APPLIED" | "SKIPPED"; action: "CREATE" | "UPDATE" | "UNCHANGED"; key: string; messages: { column: string; code: string; message: string }[] }[]; total: number; page: number; pageSize: number };
createdBy: UserSummary; createdAt: Iso; finishedAt?: Iso;
expiresAt?: Iso; // dry runs: + 24 h
}Flow: POST { fileId, mode: "DRY_RUN" } → 202 → poll admin.imports.get → POST { mode: "APPLY", dryRunId } → applies VALID and WARNING rows, re-validated (rows invalid by then become SKIPPED).
Users
GET /admin/users
admin.users.list · Permission users.view · Apps BO · Release V1 · Requirements ADM-01
Purpose. Staff and delegate users (reseller logins are managed from the CRM).
Request — type[] (STAFF, DELEGATE), role[] (RoleKey), status[] (AccountStatus), q (name, email, phone), sort (lastName, lastSignInAt, createdAt), page / pageSize.
Response 200 OK
{
"items": [
{ "id": "01929e55-2c3d-7e4f-9a1b-2c3d4e5f6a7b", "type": "STAFF", "firstName": "Nadia", "lastName": "Belkacem", "displayName": "Nadia Belkacem", "email": "n.belkacem@hoco.dz", "phone": "+213661001122", "roles": ["COMMERCIAL_ADMIN"], "status": "ACTIVE", "locale": "fr", "mustChangePassword": false, "lastSignInAt": "2026-11-04T07:31:00Z", "activeSessions": 2, "createdAt": "2026-10-20T09:00:00Z", "version": 3 }
],
"total": 19, "page": 1, "pageSize": 25
}Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks users.view |
Rules & side effects
- Read-only. There is no single-user GET in
endpoints.json; the list row carries every field of the edit drawer (gap noted).
Used by BO-61
POST /admin/users
admin.users.create · Permission users.create · Apps BO · Release V1 · Requirements ADM-01
Purpose. Creates a staff user with roles; a temporary password is sent by email or SMS.
Request
{ "firstName": "Walid", "lastName": "Rahmani", "email": "w.rahmani@hoco.dz", "phone": "0550 44 55 66", "roles": ["STOCK_MANAGER"], "locale": "fr", "sendInvitation": "EMAIL" }Response 201 Created — the user (status ACTIVE, mustChangePassword: true). The password is never returned.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | No role, staff without email, unknown role |
| 403 | PERMISSION_DENIED | Caller lacks users.create |
| 403 | PERMISSION_ESCALATION | A role grants permissions the caller does not hold — details: { permissions } |
| 409 | EMAIL_ALREADY_EXISTS · PHONE_ALREADY_EXISTS | Login taken |
| 422 | DELEGATE_PROFILE_REQUIRED | Role DELEGATE — use admin.delegates.crud |
Rules & side effects
- Audit
CREATE+PERMISSION_CHANGE. Notificationuser.invited(email for staff).
Used by BO-61
GET /admin/users/{userId}
admin.users.get · Permission users.view · Apps BO · Release V1 · Requirements ADM-01 · Added in contract 1.1.0 (SRV-41)
Purpose. One staff or delegate user — the user detail page that the users list and the global search link to.
Response 200 OK — the user, same shape as an item of admin.users.list:
{ "id": "0192f0a1-…", "type": "STAFF", "firstName": "Walid", "lastName": "Rahmani", "displayName": "Walid Rahmani", "email": "w.rahmani@hoco.dz", "phone": "+213550445566", "roles": ["STOCK_MANAGER"], "status": "ACTIVE", "locale": "fr", "mustChangePassword": false, "lastSignInAt": "2026-10-19T08:12:00Z", "activeSessions": 2, "createdAt": "2026-09-01T09:00:00Z", "version": 1760860000000 }Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks users.view |
| 404 | NOT_FOUND | Unknown id, or a reseller account (managed from the CRM) |
Used by BO-61, global search
PATCH /admin/users/{userId}
admin.users.update · Permission users.update · Apps BO · Release V1 · Requirements ADM-01
Purpose. Edits a user and their roles. Request — { firstName?, lastName?, email?, phone?, roles?, locale?, version }. Response 200 OK — the user.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_ESCALATION | Granting a role with permissions the caller lacks |
| 403 | SELF_MODIFICATION_FORBIDDEN | Changing one's own roles |
| 404 | NOT_FOUND | Unknown user |
| 409 | LAST_SUPER_ADMIN | Removing SUPER_ADMIN from the last active super administrator |
| 409 | EMAIL_ALREADY_EXISTS · PHONE_ALREADY_EXISTS · VERSION_CONFLICT |
Rules & side effects
- Role changes apply at the next request (permission cache evicted); SSE
me.permissions_changedto the user. AuditPERMISSION_CHANGE.
Used by BO-61
POST /admin/users/{userId}/status
admin.users.setStatus · Permission users.update · Apps BO · Release V1 · Requirements ADM-01 · SEC-06
Purpose. Suspends, reactivates or disables an account without deleting its history (cahier §10).
Request — { "status": "SUSPENDED", "reason": "Départ de l'entreprise — accès coupé en attendant la clôture.", "version": 3 }.
Response 200 OK — the user with status, statusReason, revokedSessions: 2.
Errors
| Status | Code | When |
|---|---|---|
| 403 | SELF_MODIFICATION_FORBIDDEN | Acting on oneself |
| 409 | LAST_SUPER_ADMIN | Last active super administrator |
| 409 | DELEGATE_HAS_OPEN_TOURS | Disabling a delegate with open tours |
| 409 | USER_TRANSITION_NOT_ALLOWED | DISABLED → anything (terminal) |
| 422 | REASON_REQUIRED | Empty reason |
Rules & side effects
- Transitions of AccountStatus: sessions revoked and push tokens removed on
SUSPENDED/DISABLED; a delegate's tourIN_PROGRESSis suspended. AuditSTATUS_CHANGE. Notificationaccount.suspended(SMS + in-app).
Used by BO-61
POST /admin/users/{userId}/reset-password
admin.users.resetPassword · Permission users.update · Apps BO · Release V1 · Requirements ADM-01
Purpose. Sends a temporary password (change forced at next sign-in). Request — { "channel": "SMS", "revokeSessions": true }. Response 200 OK — { "sentTo": "+213 55• •• •5 66", "revokedSessions": 1 }.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown user |
| 429 | RATE_LIMITED | More than 3 resets per hour for the user |
Rules & side effects
- Audit
UPDATE(no secret logged). Notificationsecurity.password_changed.
Used by BO-61
GET /admin/users/{userId}/sessions
admin.users.sessions (list) · Permission users.administer · Apps BO · Release V1 · Requirements AUTH-06
Purpose. A user's active sessions. Response 200 OK — { "items": [{ "id": "01931f00-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "app": "DASHBOARD", "deviceLabel": "Chrome 130 · Windows", "ip": "41.111.22.33", "createdAt": "2026-11-01T07:30:00Z", "lastActivityAt": "2026-11-04T10:02:00Z", "expiresAt": "2026-11-11T10:02:00Z" }] }.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks users.administer |
Rules & side effects
- Read-only.
Used by BO-61
DELETE /admin/users/{userId}/sessions/{sessionId}
admin.users.sessions (revoke one) · Permission users.administer · Apps BO · Release V1 · Requirements AUTH-06
Purpose. Revokes one session. Response 204 No Content.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown session |
Rules & side effects
- The device gets
401 UNAUTHENTICATEDat its next call. AuditLOGOUT(actor = admin).
Used by BO-61
DELETE /admin/users/{userId}/sessions
admin.users.sessions (revoke all) · Permission users.administer · Apps BO · Release V1 · Requirements AUTH-06
Purpose. Revokes every session of the user. Response 200 OK — { "revoked": 3 }.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown user |
Rules & side effects
- Audit
LOGOUT; push tokens removed.
Used by BO-61
GET /admin/staff/assignable
admin.staff.assignable · Permission crm.view or support.view · Apps BO · Release V1 · Requirements ADM-01 · Added in contract 1.1.0 (SRV-41)
Purpose. The active staff for the assignee pickers of CRM tasks and tickets, without users.view: only the name and the roles (no e-mail, phone, status or sessions).
Request — q (name contains), role (RoleKey[], repeatable).
Response 200 OK — { "items": [ { "id": "01929e77-…", "displayName": "Lamia Kaci", "roles": ["COMMERCIAL_ADMIN"] } ] }, sorted by name, at most 500.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Neither crm.view nor support.view |
Roles and permissions
GET /admin/roles
admin.roles.list · Permission roles.view · Apps BO · Release V1 · Requirements ADM-02
Purpose. The eight system roles with their permission sets. Response 200 OK — { "items": [{ "key": "FINANCE_MANAGER", "permissions": ["dashboard.view", "finance.view", "finance.create", "finance.update", "finance.validate", "finance.export", "finance.administer", "reports.view", "reports.export", "crm.view", "orders.view"], "userCount": 2, "editable": true }, { "key": "SUPER_ADMIN", "permissions": ["*"], "userCount": 1, "editable": false }] }.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks roles.view |
Rules & side effects
- Read-only; defaults are the role × permission matrix.
Used by BO-62
PUT /admin/roles/{roleKey}/permissions
admin.roles.updatePermissions · Permission roles.administer · Apps BO · Release V1 · Requirements ADM-02 · ACL-02
Purpose. Replaces a role's permission set (cahier §3.1).
Request — { "permissions": ["dashboard.view", "orders.view", "orders.validate", "delivery.view", "delivery.update"], "reason": "Le responsable livraison confirme aussi les commandes en V1.", "version": 4 }.
Response 200 OK — the role with the stored set (implied .view keys added) and affectedUsers.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Unknown permission key |
| 403 | PERMISSION_ESCALATION | Granting a key the caller does not hold — details: { permissions } |
| 409 | ROLE_PROTECTED | SUPER_ADMIN (always everything), DELEGATE and RESELLER (scope-based roles) |
| 409 | VERSION_CONFLICT | Stale version |
Rules & side effects
- Guard rails of the permissions matrix: any non-view action implies the module's
.view;roles.administeralways remains withSUPER_ADMIN. - Audit
PERMISSION_CHANGEwith added/removed keys and reason; SSEme.permissions_changedto the role's connected users.
Used by BO-62
GET /admin/permissions
admin.permissions.list · Permission roles.view · Apps BO · Release V1 · Requirements ACL-02
Purpose. The permission catalogue (module × action). Response 200 OK — { "items": [{ "key": "pricing.view", "module": "pricing", "action": "VIEW", "sensitive": true }, { "key": "orders.validate", "module": "orders", "action": "VALIDATE", "sensitive": false, "implies": ["orders.view"] }] } — the valid keys are those of the catalogue.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks roles.view |
Rules & side effects
- Read-only.
Used by BO-62
Activity journal
GET /admin/audit-logs
admin.audit.list · Permission audit.view · Apps BO · Release V1 · Requirements AUD-01 · AUD-03
Purpose. The activity journal of cahier §10 (« connexions, créations, modifications, validations, exports et suppressions »).
Request — actorId[], action[] (AuditAction), entityType[], entityId, app[] (ClientApp), from / to (timestamps), q (summary, entity label), page / pageSize (sorted by at desc).
Response 200 OK — Page<AuditLog>
{
"items": [
{ "id": "01930171-5e6f-7a7b-8c8d-9e0f1a2b3c4d", "at": "2026-11-03T09:52:30Z", "actor": { "id": "01929e66-3d4e-7f5a-8b6c-7d8e9f0a1b2c", "displayName": "Sofiane Hamdi" }, "actorRoles": ["SUPER_ADMIN"], "app": "DASHBOARD", "action": "OVERRIDE", "entityType": "Order", "entityId": "0193016e-4b2a-7c11-9d8e-7f6a5b4c3d2e", "entityLabel": "CMD-2026-000158", "summary": "Dérogation stock à la validation de CMD-2026-000158", "changes": [{ "field": "controls.stock", "before": "INSUFFICIENT", "after": "OVERRIDDEN" }], "reason": "Réassort ACEFAST reçu demain matin.", "context": { "ip": "41.111.22.33", "userAgent": "Mozilla/5.0", "requestId": "req_01J9Z6B2C3", "sessionId": "01931f00-2b3c-7d4e-9f5a-6b7c8d9e0f1a" } }
],
"total": 5821, "page": 1, "pageSize": 25
}Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks audit.view |
Rules & side effects
- Read-only, append-only journal; retention per BR-84. Export with
admin.exports.create(audit-logs,audit.export).
Used by BO-63
Settings
Settings keys
The binding list of keys, types, defaults and allowed values is the settings catalogue (orders, returns and after-sales, stock, catalog, pricing, credit, payments, billing, promotions, delivery, field, CRM, invitations, auth, support, notifications, exports, numbering, V2 loyalty). The API pages reference those keys verbatim. Keys these pages use that are not yet in the catalogue (proposed): orders.confirmationReminderHours (4 working hours, from the notifications matrix), stock.multiWarehouse, catalog.newProductDays, delivery.proofPhotoRequired, delivery.shippingSlaHours.
GET /admin/settings
admin.settings.get · Permission settings.view · Apps BO · Release V1 · Requirements ADM-03
Purpose. Business parameters grouped by domain.
Response 200 OK
{
"groups": [
{ "domain": "stock", "settings": [
{ "key": "stock.reserveOn", "type": "ENUM", "value": "VALIDATED", "default": "VALIDATED", "allowed": ["SUBMITTED", "VALIDATED"], "public": false, "updatedAt": "2026-10-20T08:00:00Z", "updatedBy": null },
{ "key": "stock.deductOn", "type": "ENUM", "value": "SHIPPED", "default": "SHIPPED", "allowed": ["READY", "SHIPPED", "DELIVERED"], "public": false, "updatedAt": "2026-10-20T08:00:00Z", "updatedBy": null }
] }
],
"version": 12
}Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks settings.view |
Rules & side effects
- Read-only;
publicsettings are also exposed to the apps throughappConfig.get.
Used by BO-64
PATCH /admin/settings
admin.settings.update · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-03
Purpose. Updates one or several parameters, validated per key, all-or-nothing.
Request — { "values": { "credit.overLimitPolicy": "REQUIRE_APPROVAL", "returns.windowDays": 10 }, "reason": "Décision comité commercial du 2 novembre.", "version": 12 }.
Response 200 OK — the updated groups (version 13).
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Unknown key, wrong type or value outside the allowed range — details.fields[{ path: "returns.windowDays", code: "OUT_OF_RANGE" }] |
| 403 | PERMISSION_DENIED | Caller lacks settings.administer |
| 409 | VERSION_CONFLICT | Stale version |
Rules & side effects
- Settings caches evicted; new values apply to events from now on (no retroactive stock movement or re-pricing). Changes of public settings reach the apps at their next
appConfig.get; field settings throughsync.delta. - Audit one
UPDATErow per key (before/after, reason).
Used by BO-64
Reference lists
Lists of ReferenceItem by listKey: BUSINESS_TYPE, PERSON_ROLE, VISIT_NOT_DONE_REASON, VISIT_ISSUE_TAG, TOUR_SUSPEND_REASON, ORDER_REFUSAL_REASON, ORDER_CANCEL_REASON, PAYMENT_REJECT_REASON. Item: { id, listKey, code (UPPER_SNAKE, immutable), labels: { fr, ar?, en? }, active, sortOrder, requiresComment, system (locked), usageCount, version }.
GET /admin/reference-lists/{listKey}
admin.referenceLists.crud (list) · Permission settings.view · Apps BO · Release V1 · Requirements ADM-05
Purpose. Items of a list, inactive ones included. Response 200 OK — { "listKey": "ORDER_REFUSAL_REASON", "items": [{ "id": "01928d01-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "code": "DUPLICATE_ORDER", "labels": { "fr": "Commande en double" }, "active": true, "sortOrder": 3, "requiresComment": false, "system": false, "usageCount": 4, "version": 1 }] }.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown listKey |
Rules & side effects
- Read-only (the apps read active items through
reference.lists).
Used by BO-65
GET /admin/reference-lists/{listKey}/{itemId}
admin.referenceLists.crud (get) · Permission settings.view · Apps BO · Release V1 · Requirements ADM-05
Purpose. One item. Response 200 OK — the item.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown list or item |
Rules & side effects
- Read-only.
Used by BO-65
POST /admin/reference-lists/{listKey}
admin.referenceLists.crud (create) · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-05
Purpose. Adds an item. Request — { "code": "STORE_MOVED", "labels": { "fr": "Magasin déménagé" }, "requiresComment": false }. Response 201 Created — the item.
Errors
| Status | Code | When |
|---|---|---|
| 409 | DUPLICATE_VALUE | Code already in the list |
Rules & side effects
- Audit
CREATE; caches (ETag) refreshed.
Used by BO-65
PATCH /admin/reference-lists/{listKey}/{itemId}
admin.referenceLists.crud (update) · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-05
Purpose. Edits labels or order, or deactivates an item (never deleted when used). Response 200 OK — the item.
Errors
| Status | Code | When |
|---|---|---|
| 409 | ROLE_PROTECTED | Deactivating a system item (e.g. « Délai de confirmation expiré ») |
| 409 | VERSION_CONFLICT | Stale version |
Rules & side effects
- Audit
UPDATE.
Used by BO-65
App versions
GET /admin/app-versions
admin.appVersions.crud (list) · Permission settings.view · Apps BO · Release V1 · Requirements ADM-07
Purpose. Version policy per app and platform. Response 200 OK — { "items": [{ "app": "DELEGATE_APP", "platform": "android", "minVersion": "1.0.2", "latestVersion": "1.0.3", "storeUrl": "https://play.google.com/store/apps/details?id=dz.hoco.delegate", "maintenance": { "enabled": false }, "updatedAt": "2026-11-01T08:00:00Z", "version": 3 }] }.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks settings.view |
Rules & side effects
- Read-only.
Used by BO-66
PUT /admin/app-versions/{app}/{platform}
admin.appVersions.crud (update) · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-07
Purpose. Sets minimum and latest versions, release notes and maintenance mode (app: RESELLER_APP, DELEGATE_APP, RESELLER_WEB; platform: android, ios, web).
Request — { "minVersion": "1.0.3", "latestVersion": "1.0.3", "releaseNotes": { "fr": "Correction de la synchronisation des photos." }, "maintenance": { "enabled": false }, "version": 3 }.
Response 200 OK — the policy.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Not semver, minVersion above latestVersion |
| 404 | NOT_FOUND | Unknown app/platform pair |
Rules & side effects
- Clients below
minVersionget426 UPGRADE_REQUIRED; maintenance answers503 MAINTENANCE(details.until) to that app. AuditUPDATE.
Used by BO-66
Support regions
Regions group wilayas and carry the WhatsApp support number and hours used by the WhatsApp button (BR-83, SUP-05, ADM-08); read by support.whatsapp. SupportRegion { id, name, whatsappNumber (E.164), phone?, hours: [{ weekdays: number[], from: "HH:mm", to: "HH:mm" }], wilayaCodes: string[], isDefault, active, version }; a wilaya belongs to one region at most; the default region serves unresolved locations, and support.defaultWhatsappNumber is the last fallback.
GET /admin/support-regions
admin.supportRegions.crud (list) · Permission settings.view · Apps BO · Release V1 · Requirements ADM-08
Purpose. Lists regions. Response 200 OK — { "items": [{ "id": "01928d10-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "name": "Ouest", "whatsappNumber": "+213770900100", "hours": [{ "weekdays": [0, 1, 2, 3, 4], "from": "08:30", "to": "17:00" }], "wilayaCodes": ["31", "46", "13", "29", "22", "27"], "isDefault": false, "active": true, "version": 2 }], "total": 4, "unassignedWilayas": ["11", "33"] }.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks settings.view |
Rules & side effects
- Read-only.
Used by BO-70
GET /admin/support-regions/{regionId}
admin.supportRegions.crud (get) · Permission settings.view · Apps BO · Release V1 · Requirements ADM-08
Purpose. One region. Response 200 OK — the region.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown region |
Rules & side effects
- Read-only.
Used by BO-70
POST /admin/support-regions
admin.supportRegions.crud (create) · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-08 · SUP-05
Purpose. Creates a region. Request — { "name": "Est", "whatsappNumber": "0770 90 02 00", "hours": [{ "weekdays": [0, 1, 2, 3, 4], "from": "08:30", "to": "17:00" }], "wilayaCodes": ["25", "19", "23"], "isDefault": false }. Response 201 Created — the region (number in E.164).
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Invalid number, overlapping hours, unknown wilaya |
| 409 | DUPLICATE_VALUE | Name taken |
| 409 | SUPPORT_REGION_OVERLAP | A wilaya already belongs to another region — details: { wilayas: [{ wilayaCode, regionId }] } |
Rules & side effects
isDefault: truemoves the default flag from the previous default region. AuditCREATE.
Used by BO-70
PATCH /admin/support-regions/{regionId}
admin.supportRegions.crud (update) · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-08
Purpose. Edits number, hours, wilayas, default flag or deactivates a region. Response 200 OK — the region.
Errors
| Status | Code | When |
|---|---|---|
| 409 | SUPPORT_REGION_OVERLAP · VERSION_CONFLICT | |
| 409 | DEFAULT_REGION_REQUIRED | Unsetting or deactivating the only default region |
Rules & side effects
- Applies at the next
support.whatsappread. AuditUPDATE.
Used by BO-70
DELETE /admin/support-regions/{regionId}
admin.supportRegions.crud (delete) · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-08
Purpose. Deletes a non-default region; its wilayas fall back to the default region. Response 204 No Content.
Errors
| Status | Code | When |
|---|---|---|
| 409 | DEFAULT_REGION_REQUIRED | It is the default region |
Rules & side effects
- Audit
DELETE.
Used by BO-70
Imports and search
POST /admin/imports/{type}
admin.imports.create · Permission settings.administer · Apps BO · Release V1 · Requirements ADM-06
Purpose. Initial imports of cahier §13.2 — resellers, users or stock — dry run then apply (type: resellers, users, stock; products: admin.products.import).
Request — { "mode": "DRY_RUN", "fileId": "01931f10-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "options": { "stockMode": "SET" } } or { "mode": "APPLY", "dryRunId": "…" }.
| Type | Template columns (* required) |
|---|---|
resellers | store_name, owner_name, phone, whatsapp, email, wilaya_code, commune* (name or code), address_line, landmark, lat, lng, business_type, customer_type (SUPER_GROS/DEMI_GROS/DETAIL), potential, delegate_code, status (default NEW_PROSPECT; existing buyers ACTIVE), segments, rc, nif, note — no login is created; owners claim their record at sign-up |
users | first_name, last_name, email (staff*), phone (delegates*), roles* (RoleKey list), delegate_code, zones, send_invitation (SMS/EMAIL/NONE) |
stock | sku* (or barcode), warehouse_code, quantity, alert_threshold — option stockMode: SET (ADJUSTMENT movements to reach the quantity) or ADD (RECEIPT movements) |
Response 202 Accepted — an ImportJob.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED · PERMISSION_ESCALATION | Missing settings.administer · user rows granting roles beyond the caller's |
| 404 | NOT_FOUND | Unknown type, file or dry run |
| 409 | IMPORT_DRY_RUN_EXPIRED · IMPORT_ALREADY_APPLIED | |
| 422 | IMPORT_FILE_INVALID · IMPORT_DRY_RUN_REQUIRED · FILE_NOT_READY | Missing columns or over 20 000 rows · dry run not succeeded · upload incomplete |
Rules & side effects
- Reseller rows follow the creation rules (unique phone BR-04, source
IMPORT, ValidationStatusAPPROVED, assignment BR-47). AuditIMPORT; notification and SSEimport.finishedto the requester.
Used by BO-67
GET /admin/imports/{importId}
admin.imports.get · Permission settings.administer (or catalog.administer for product imports) · Apps BO · Release V1 · Requirements ADM-06 · CAT-06
Purpose. Import job status and row-level report (?rowStatus=ERROR&page=1&pageSize=100).
Response 200 OK — an ImportJob with summary and rows, e.g. { "id": "01931f11-…", "type": "RESELLERS", "mode": "DRY_RUN", "status": "SUCCEEDED", "summary": { "totalRows": 412, "validRows": 398, "warningRows": 6, "errorRows": 8, "toCreate": 404, "toUpdate": 0, "unchanged": 0 }, "rows": { "items": [{ "row": 17, "status": "ERROR", "action": "CREATE", "key": "+213555123456", "messages": [{ "column": "phone", "code": "PHONE_ALREADY_EXISTS", "message": "Numéro déjà utilisé par REV-000123." }] }], "total": 8, "page": 1, "pageSize": 100 }, "expiresAt": "2026-11-05T08:00:00Z" }.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown import |
Rules & side effects
- Read-only.
GET /admin/imports
admin.imports.list · Permission settings.administer (with catalog.administer alone: product imports only) · Apps BO · Release V1 · Requirements ADM-06 · CAT-06 · Added in contract 1.1.0 (SRV-41)
Purpose. The import history of BO-67 and BO-32: past dry runs and applications with their summaries, newest first.
Request — type (ImportType[]: PRODUCTS, RESELLERS, USERS, STOCK), status (JobStatus[]), mode (DRY_RUN | APPLY), page, pageSize (25, max 200), order (desc by default, on createdAt).
Response 200 OK — { items, total, page, pageSize } of ImportJob without rows (the row report is read with admin.imports.get).
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Neither settings.administer nor catalog.administer |
GET /admin/imports/{type}/template
admin.imports.template · Permission settings.administer (catalog.administer for products) · Apps BO · Release V1 · Requirements ADM-06 · CAT-06 · Added in contract 1.1.0 (SRV-41)
Purpose. « Télécharger le modèle »: generates the template of an import type with the columns its import reads — the required ones first, highlighted in XLSX, with an Aide sheet describing every column.
Request
| Path / query | Type | Notes |
|---|---|---|
type | string | resellers | users | stock | products |
format | string | XLSX (default) | CSV (UTF-8 with BOM, ; separator) |
categoryId | Uuid | products only: adds the attr:<key> columns of the category and its parents (ENUM codes listed in the help) |
Response 200 OK
{
"type": "RESELLERS", "format": "XLSX", "fileName": "modele-import-resellers.xlsx",
"url": "https://files.hoco.dz/hoco-private/import-templates/resellers.xlsx?X-Amz-…", "expiresAt": "2026-11-04T09:05:00Z",
"columns": [ { "name": "store_name", "required": true, "description": "Nom du magasin" }, { "name": "whatsapp", "required": false, "description": "Numéro WhatsApp" } ]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Unknown type or format; categoryId on another type than products |
| 403 | PERMISSION_DENIED | Missing the type's permission |
| 404 | NOT_FOUND | Unknown category |
Rules & side effects
- The file is regenerated on each call and stored under a fixed key per type (and category); the signed URL is valid 5 minutes. Nothing is audited.
GET /admin/search
admin.search · Permission any back-office user (results filtered by permission) · Apps BO · Release V1 · Requirements ACL-05
Purpose. Global search of the app shell across resellers, orders, products, invoices, users, tickets and tours.
Request — q (min 2 chars; business numbers match exactly, phones after normalisation, names by similarity, SKU and barcode exactly), types (subset of RESELLER, ORDER, PRODUCT, INVOICE, USER, TICKET, TOUR, RETURN), limit (per group, 5, max 20).
Response 200 OK
{
"q": "158",
"groups": [
{ "type": "ORDER", "total": 1, "items": [{ "id": "0193016e-4b2a-7c11-9d8e-7f6a5b4c3d2e", "title": "CMD-2026-000158", "subtitle": "Phone Store El Bahia · 30 749,60 DA", "status": "VALIDATED" }] },
{ "type": "RESELLER", "total": 0, "items": [] }
]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | q shorter than 2 characters |
Rules & side effects
- A group is returned only when the caller holds its module's
.view(orders, crm, catalog, finance, users, support, field); the client routes fromtypeandid. Not audited; rate-limited 60 / min.
Used by BO-03
Reports & exports
Back-office report catalogue with the columns and filters of every essential report, report runs with totals, and asynchronous PDF, Excel and CSV exports.
Real-time events (SSE)
The back-office Server-Sent Events stream — connection, authentication, resume, heartbeat, event envelope, permission-filtered event catalogue and reconnection guidance.