openapi: 3.0.3 info: title: 'CEMAC POS API' description: 'REST API for CEMAC POS — the offline-capable, OHADA-compliant point of sale by Opesware SARL. Multi-tenant: a token only ever sees its own shop.' version: 1.0.0 servers: - url: 'https://cemacpos.com' tags: - name: Endpoints description: '' components: securitySchemes: default: type: http scheme: bearer description: 'Obtain a token via POST /api/v1/auth/login (email + password), then send it as Authorization: Bearer <token>.' security: - default: [] paths: /api/v1/auth/login: post: summary: 'Issue a Sanctum token for valid credentials.' operationId: issueASanctumTokenForValidCredentials description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'Le champ value doit être une adresse e-mail valide.' example: gbailey@example.net password: type: string description: '' example: '|]|{+-' device_name: type: string description: '' example: architecto nullable: true required: - email - password /api/v1/auth/register: post: summary: 'Public self-signup: creates a shop with a 30-day trial and returns a token.' operationId: publicSelfSignupCreatesAShopWithA30DayTrialAndReturnsAToken description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: shop_name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b business_type: type: string description: '' example: architecto nullable: true phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: 'n' nullable: true city: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: g nullable: true tax_id: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: z nullable: true locale: type: string description: '' example: en enum: - fr - en nullable: true owner_name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: m owner_email: type: string description: 'Le champ value doit être une adresse e-mail valide.' example: gulgowski.asia@example.com password: type: string description: 'Le champ value doit contenir au moins 8 caractères.' example: 'vYgxwmi/#' referral_code: type: string description: "Sales-partner referral. Free text on purpose: an unknown code is\nignored, never a signup blocker. Le champ value ne peut pas dépasser 60 caractères." example: m nullable: true required: - shop_name - owner_name - owner_email - password /api/v1/auth/forgot-password: post: summary: '' operationId: postApiV1AuthForgotPassword description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'Le champ value doit être une adresse e-mail valide.' example: gbailey@example.net required: - email /api/v1/auth/reset-password: post: summary: '' operationId: postApiV1AuthResetPassword description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: token: type: string description: '' example: architecto email: type: string description: 'Le champ value doit être une adresse e-mail valide.' example: zbailey@example.net password: type: string description: 'Le champ value doit contenir au moins 8 caractères.' example: '-0pBNvYgxw' required: - token - email - password /api/v1/device-pairings: post: summary: 'A device with no credentials starts a pairing session.' operationId: aDeviceWithNoCredentialsStartsAPairingSession description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: b nullable: true platform: type: string description: '' example: desktop enum: - pwa - desktop nullable: true '/api/v1/device-pairings/{code}': get: summary: 'Polled by the requesting device until an owner claims the code.' operationId: polledByTheRequestingDeviceUntilAnOwnerClaimsTheCode description: '' parameters: [] responses: 404: description: '' content: application/json: schema: type: object example: message: 'No pairing request with this code.' properties: message: type: string example: 'No pairing request with this code.' tags: - Endpoints parameters: - in: path name: code description: '' example: architecto required: true schema: type: string /api/v1/device/sync/pull: get: summary: "Pull records changed since the client's cursor, per entity, paginated." operationId: pullRecordsChangedSinceTheClientsCursorPerEntityPaginated description: "Query params:\n - since: ISO-8601 cursor. Omitted on a device's first sync.\n - limit: page size, default DEFAULT_LIMIT, clamped to [1, MAX_LIMIT].\n - include_sales: sales (with items + payments) are only built and\n serialised when this is truthy (\"1\"). This block is by far the most\n expensive part of the response — every sale in the window, each\n eager-loading its line items and payments — and nothing currently\n consumes it: the desktop app is just this same SPA loaded in an\n Electron shell, the client's sync engine (resources/js/lib/sync.js)\n already discards data.sales, and there is no local sales mirror to\n populate. Defaulting it off keeps ordinary pulls (catalogue +\n customers) cheap on memory-constrained shared hosting. When omitted,\n `data.sales` is still present as an empty array and\n `has_more.sales` as false, so the response shape never changes." parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: "Identifiants de l'appareil manquants." properties: message: type: string example: "Identifiants de l'appareil manquants." tags: - Endpoints /api/v1/device/sync/push: post: summary: '' operationId: postApiV1DeviceSyncPush description: '' parameters: [] responses: { } tags: - Endpoints /api/v1/partner/dashboard: get: summary: 'Headline totals.' operationId: headlineTotals description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/partner/referrals: get: summary: 'Referred shops with the commission earned on each.' operationId: referredShopsWithTheCommissionEarnedOnEach description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/partner/commissions: get: summary: 'Individual commission lines.' operationId: individualCommissionLines description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/admin/partners: get: summary: 'All partners with referral + commission totals.' operationId: allPartnersWithReferral+CommissionTotals description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: 'Create a partner (and its cross-tenant login).' operationId: createAPartnerandItsCrossTenantLogin description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b email: type: string description: 'Le champ value doit être une adresse e-mail valide.' example: zbailey@example.net password: type: string description: 'Le champ value doit contenir au moins 8 caractères.' example: '-0pBNvYgxw' phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: a nullable: true code: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: 'y' nullable: true commission_rate: type: number description: 'Le champ value doit être au moins 0. Le champ value ne peut pas dépasser 100.' example: 18 nullable: true commission_months: type: integer description: 'Le champ value doit être au moins 1. Le champ value ne peut pas dépasser 120.' example: 8 nullable: true payout_method: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: m nullable: true payout_phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: 'y' nullable: true payout_account: type: string description: 'Le champ value ne peut pas dépasser 80 caractères.' example: u nullable: true payout_name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: w nullable: true required: - name - email - password '/api/v1/admin/partners/{id}': put: summary: '' operationId: putApiV1AdminPartnersId description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b commission_rate: type: number description: 'Le champ value doit être au moins 0. Le champ value ne peut pas dépasser 100.' example: 22 commission_months: type: integer description: 'Le champ value doit être au moins 1. Le champ value ne peut pas dépasser 120.' example: 7 status: type: string description: '' example: active enum: - active - suspended payout_method: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: z nullable: true payout_phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: m nullable: true payout_account: type: string description: 'Le champ value ne peut pas dépasser 80 caractères.' example: i nullable: true payout_name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: 'y' nullable: true notes: type: string description: 'Le champ value ne peut pas dépasser 2000 caractères.' example: v nullable: true parameters: - in: path name: id description: 'The ID of the partner.' example: architecto required: true schema: type: string /api/v1/admin/partner-commissions: get: summary: 'Commission ledger across all partners.' operationId: commissionLedgerAcrossAllPartners description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/admin/partner-commissions/pay: post: summary: 'Mark commissions paid out (or payable).' operationId: markCommissionsPaidOutorPayable description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: ids: type: array description: 'Le champ value doit être un UUID valide.' example: - 6ff8f7f6-1eb3-3525-be4a-3932c805afed items: type: string partner_id: type: string description: 'This field is required when ids is not present. Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 status: type: string description: '' example: paid enum: - pending - payable - paid nullable: true payout_reference: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: m nullable: true /api/v1/auth/me: get: summary: '' operationId: getApiV1AuthMe description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/auth/logout: post: summary: '' operationId: postApiV1AuthLogout description: '' parameters: [] responses: { } tags: - Endpoints /api/v1/dashboard: get: summary: '' operationId: getApiV1Dashboard description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/profile: put: summary: '' operationId: putApiV1Profile description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: 'n' nullable: true locale: type: string description: '' example: en enum: - fr - en /api/v1/profile/password: post: summary: '' operationId: postApiV1ProfilePassword description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: current_password: type: string description: '' example: architecto password: type: string description: 'Le champ value doit contenir au moins 8 caractères.' example: ']|{+-0pBNvYg' required: - current_password - password /api/v1/billing/plans: get: summary: 'Available plans to subscribe to.' operationId: availablePlansToSubscribeTo description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/billing: get: summary: 'Current subscription state + recent charges.' operationId: currentSubscriptionState+RecentCharges description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/billing/subscribe: post: summary: 'Initiate a Mobile Money charge to start/renew a plan.' operationId: initiateAMobileMoneyChargeToStartrenewAPlan description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: plan_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed method: type: string description: '' example: orange_money enum: - mtn_momo - orange_money phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: g required: - plan_id - method - phone '/api/v1/billing/confirm/{reference}': post: summary: 'Poll a charge; activates the subscription if the payment succeeded.' operationId: pollAChargeActivatesTheSubscriptionIfThePaymentSucceeded description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: reference description: '' example: architecto required: true schema: type: string /api/v1/categories: get: summary: '' operationId: getApiV1Categories description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Categories description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/categories/{id}': put: summary: '' operationId: putApiV1CategoriesId description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1CategoriesId description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the category.' example: 019fbec2-b6a9-701b-b2b5-0b1eb8944664 required: true schema: type: string /api/v1/products/barcodes/generate: post: summary: '' operationId: postApiV1ProductsBarcodesGenerate description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: product_ids: type: array description: 'Le champ value doit être un UUID valide.' example: - 6ff8f7f6-1eb3-3525-be4a-3932c805afed items: type: string category_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 nullable: true include_variants: type: boolean description: '' example: true nullable: true /api/v1/products: get: summary: '' operationId: getApiV1Products description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Products description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/products/{id}': get: summary: '' operationId: getApiV1ProductsId description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints put: summary: '' operationId: putApiV1ProductsId description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1ProductsId description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the product.' example: 019fbec2-b6b4-722c-908b-618839613174 required: true schema: type: string '/api/v1/products/{product_id}/adjust': post: summary: 'Manual stock adjustment / stocktake: set on-hand to a counted figure.' operationId: manualStockAdjustmentStocktakeSetOnHandToACountedFigure description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: new_quantity: type: number description: 'Le champ value doit être au moins 0.' example: 27 reason: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: 'n' nullable: true required: - new_quantity parameters: - in: path name: product_id description: 'The ID of the product.' example: 019fbec2-b6b4-722c-908b-618839613174 required: true schema: type: string '/api/v1/products/{product_id}/movements': get: summary: 'Per-product stock-movement ledger (purchases, sales, adjustments, returns).' operationId: perProductStockMovementLedgerpurchasesSalesAdjustmentsReturns description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: product_id description: 'The ID of the product.' example: 019fbec2-b6b4-722c-908b-618839613174 required: true schema: type: string /api/v1/reports/stock-valuation: get: summary: 'Inventory valuation: on-hand quantity × last cost, per tracked product.' operationId: inventoryValuationOnHandQuantityLastCostPerTrackedProduct description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints '/api/v1/products/{product_id}/options': get: summary: '' operationId: getApiV1ProductsProduct_idOptions description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: "Replace the product's axes wholesale." operationId: replaceTheProductsAxesWholesale description: "Sent as a whole set rather than patched one value at a time: the editor\nis a small form the shop fills in and saves, and a partial update would\nmake \"I removed XXL\" indistinguishable from \"I did not mention XXL\"." parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: options: type: array description: 'Le champ value doit contenir au moins 1 éléments. Le champ value ne doit pas contenir plus de 4 éléments.' example: - [] items: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: b values: type: array description: 'Le champ value ne peut pas dépasser 60 caractères.' example: - 'n' items: type: string required: - name - values required: - options parameters: - in: path name: product_id description: 'The ID of the product.' example: 019fbec2-b6b4-722c-908b-618839613174 required: true schema: type: string '/api/v1/products/{product_id}/options/{option_id}': delete: summary: '' operationId: deleteApiV1ProductsProduct_idOptionsOption_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: product_id description: 'The ID of the product.' example: 019fbec2-b6b4-722c-908b-618839613174 required: true schema: type: string - in: path name: option_id description: 'The ID of the option.' example: 019fbff7-8d4a-7009-b2be-ca7f9ae75978 required: true schema: type: string '/api/v1/products/{product_id}/variants/generate': post: summary: 'Create every missing combination.' operationId: createEveryMissingCombination description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: default_price_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 27 nullable: true parameters: - in: path name: product_id description: 'The ID of the product.' example: 019fbec2-b6b4-722c-908b-618839613174 required: true schema: type: string '/api/v1/products/{product_id}/variants': get: summary: '' operationId: getApiV1ProductsProduct_idVariants description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1ProductsProduct_idVariants description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: product_id description: 'The ID of the product.' example: 019fbec2-b6b4-722c-908b-618839613174 required: true schema: type: string '/api/v1/variants/{variant_id}': put: summary: '' operationId: putApiV1VariantsVariant_id description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1VariantsVariant_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: variant_id description: 'The ID of the variant.' example: 019fbff7-8d8a-71b2-8e17-66314fb6955e required: true schema: type: string /api/v1/customers/find-by-card: get: summary: "Find the customer wearing a given loyalty card — the till's scan-to-lookup." operationId: findTheCustomerWearingAGivenLoyaltyCardTheTillsScanToLookup description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: card_no: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: b required: - card_no /api/v1/loyalty-cards/register: post: summary: "Registers a physical card — one already numbered (bought pre-printed\nin bulk) rather than one this system minted — against a customer." operationId: registersAPhysicalCardOneAlreadyNumberedboughtPrePrintedInBulkRatherThanOneThisSystemMintedAgainstACustomer description: "Either an existing customer_id, or a name to create one on the spot: a\nshop handing out cards at a counter is not going to open a separate\n\"new customer\" screen first for someone standing right there." parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: card_no: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: b customer_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: a4855dc5-0acb-33c3-b921-f4291f719ca0 nullable: true name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: z nullable: true required: - card_no /api/v1/customers: get: summary: '' operationId: getApiV1Customers description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Customers description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/customers/{id}': get: summary: '' operationId: getApiV1CustomersId description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints put: summary: '' operationId: putApiV1CustomersId description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1CustomersId description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the customer.' example: 019fbec2-b6df-70a4-9d78-ce57e285e388 required: true schema: type: string /api/v1/suppliers: get: summary: '' operationId: getApiV1Suppliers description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Suppliers description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/suppliers/{id}': put: summary: '' operationId: putApiV1SuppliersId description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1SuppliersId description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the supplier.' example: architecto required: true schema: type: string /api/v1/purchases: get: summary: '' operationId: getApiV1Purchases description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: 'Receive goods: increases stock, refreshes cost, posts the purchase entry.' operationId: receiveGoodsIncreasesStockRefreshesCostPostsThePurchaseEntry description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: supplier_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed nullable: true reference: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: g nullable: true note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: z nullable: true vat_rate: type: number description: 'Le champ value doit être au moins 0. Le champ value ne peut pas dépasser 100.' example: 17 nullable: true items: type: array description: 'Le champ value doit contenir au moins 1 éléments.' example: - [] items: type: object properties: product_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed nullable: true variant_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 nullable: true name: type: string description: 'Le champ value ne peut pas dépasser 200 caractères.' example: m nullable: true quantity: type: number description: '' example: 4326.41688 unit_cost_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 77 batch_number: type: string description: 'Le champ value ne peut pas dépasser 80 caractères.' example: i nullable: true expiry_date: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:16' nullable: true required: - quantity - unit_cost_xaf required: - items '/api/v1/purchases/{purchase_id}': get: summary: '' operationId: getApiV1PurchasesPurchase_id description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: purchase_id description: 'The ID of the purchase.' example: architecto required: true schema: type: string '/api/v1/purchases/{purchase_id}/pay': post: summary: 'Settle (part of) a supplier invoice: Debit 401 / Credit treasury.' operationId: settlepartOfASupplierInvoiceDebit401CreditTreasury description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: amount_xaf: type: integer description: 'Le champ value doit être au moins 1.' example: 16 method: type: string description: '' example: bank enum: - cash - mtn_momo - orange_money - card - bank reference: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: 'n' nullable: true required: - amount_xaf - method parameters: - in: path name: purchase_id description: 'The ID of the purchase.' example: architecto required: true schema: type: string '/api/v1/purchases/{purchase_id}/returns': post: summary: 'Send goods back to the supplier.' operationId: sendGoodsBackToTheSupplier description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: items: type: array description: 'Le champ value doit contenir au moins 1 éléments.' example: - [] items: type: object properties: purchase_item_id: type: string description: 'Le champ value doit être un UUID valide.' example: a4855dc5-0acb-33c3-b921-f4291f719ca0 quantity: type: number description: '' example: 4326.41688 required: - purchase_item_id - quantity reason: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: b nullable: true required: - items parameters: - in: path name: purchase_id description: 'The ID of the purchase.' example: architecto required: true schema: type: string /api/v1/reports/payables: get: summary: '' operationId: getApiV1ReportsPayables description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/reports/receivables: get: summary: 'Who owes what, with ageing buckets.' operationId: whoOwesWhatWithAgeingBuckets description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints '/api/v1/customers/{customer_id}/statement': get: summary: 'Balance, ceiling, unsettled sales and settlement history.' operationId: balanceCeilingUnsettledSalesAndSettlementHistory description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: customer_id description: 'The ID of the customer.' example: 019fbec2-b6df-70a4-9d78-ce57e285e388 required: true schema: type: string '/api/v1/customers/{customer_id}/payments': post: summary: 'Record a settlement against the balance (allocated oldest sale first).' operationId: recordASettlementAgainstTheBalanceallocatedOldestSaleFirst description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: amount_xaf: type: integer description: 'Le champ value doit être au moins 1.' example: 16 method: type: string description: '' example: architecto note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: 'n' nullable: true required: - amount_xaf - method parameters: - in: path name: customer_id description: 'The ID of the customer.' example: 019fbec2-b6df-70a4-9d78-ce57e285e388 required: true schema: type: string /api/v1/loyalty/tiers: get: summary: "The shop's loyalty tier ladder (Bronze/Silver/Gold or custom)." operationId: theShopsLoyaltyTierLadderBronzeSilverGoldOrCustom description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints put: summary: "Replace the shop's tier ladder. Read via tenant.settings.loyalty_tiers\n(LoyaltyService::tiers()) since it was configured months ago; nothing\never wrote to that path — a shop wanting a custom ladder had no way to\nset one short of a database edit." operationId: replaceTheShopsTierLadderReadViaTenantsettingsloyaltyTiersLoyaltyServicetiersSinceItWasConfiguredMonthsAgoNothingEverWroteToThatPathAShopWantingACustomLadderHadNoWayToSetOneShortOfADatabaseEdit description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: tiers: type: array description: 'Le champ value doit contenir au moins 1 éléments. Le champ value ne doit pas contenir plus de 8 éléments.' example: - [] items: type: object properties: key: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: b name: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: 'n' min_points: type: integer description: 'Le champ value doit être au moins 0.' example: 84 multiplier: type: number description: 'Le champ value doit être au moins 0. Le champ value ne peut pas dépasser 10.' example: 6 required: - key - name - min_points - multiplier required: - tiers /api/v1/loyalty/report: get: summary: "What the loyalty programme actually costs and does: points issued,\nredeemed and adjusted, the top holders of unredeemed points (a\nliability the shop is carrying), and the split by tier. Nothing about\nloyalty had a reporting screen before this — a shop could see one\ncustomer's ledger, never the programme as a whole." operationId: whatTheLoyaltyProgrammeActuallyCostsAndDoesPointsIssuedRedeemedAndAdjustedTheTopHoldersOfUnredeemedPointsaLiabilityTheShopIsCarryingAndTheSplitByTierNothingAboutLoyaltyHadAReportingScreenBeforeThisAShopCouldSeeOneCustomersLedgerNeverTheProgrammeAsAWhole description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints '/api/v1/customers/{customer_id}/card': get: summary: "Print/download data for the customer's loyalty card." operationId: printdownloadDataForTheCustomersLoyaltyCard description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: customer_id description: 'The ID of the customer.' example: 019fbec2-b6df-70a4-9d78-ce57e285e388 required: true schema: type: string '/api/v1/customers/{customer_id}/points': post: summary: 'A manager correcting a balance outside any sale — a goodwill point, or fixing a mistake.' operationId: aManagerCorrectingABalanceOutsideAnySaleAGoodwillPointOrFixingAMistake description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: delta: type: integer description: 'Must not be one of 0.' example: 16 reason: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: 'n' required: - delta - reason parameters: - in: path name: customer_id description: 'The ID of the customer.' example: 019fbec2-b6df-70a4-9d78-ce57e285e388 required: true schema: type: string /api/v1/promotions: get: summary: '' operationId: getApiV1Promotions description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Promotions description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/promotions/{promotion_id}': put: summary: '' operationId: putApiV1PromotionsPromotion_id description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1PromotionsPromotion_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: promotion_id description: 'The ID of the promotion.' example: 019fbfac-cf48-710f-858f-765b3d797b71 required: true schema: type: string /api/v1/quotes: get: summary: '' operationId: getApiV1Quotes description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Quotes description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/quotes/{quote_id}': get: summary: '' operationId: getApiV1QuotesQuote_id description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints put: summary: '' operationId: putApiV1QuotesQuote_id description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1QuotesQuote_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: quote_id description: 'The ID of the quote.' example: architecto required: true schema: type: string '/api/v1/quotes/{quote_id}/status': post: summary: 'Mark sent / declined without converting.' operationId: markSentDeclinedWithoutConverting description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: status: type: string description: '' example: expired enum: - draft - sent - declined - expired required: - status parameters: - in: path name: quote_id description: 'The ID of the quote.' example: architecto required: true schema: type: string '/api/v1/quotes/{quote_id}/convert': post: summary: 'Accept the quote and raise the real sale.' operationId: acceptTheQuoteAndRaiseTheRealSale description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: payments: type: array description: '' example: null items: type: object nullable: true properties: method: type: string description: 'This field is required when payments is present.' example: null amount_xaf: type: integer description: 'This field is required when payments is present. Le champ value doit être au moins 0.' example: 27 reference: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: 'n' nullable: true parameters: - in: path name: quote_id description: 'The ID of the quote.' example: architecto required: true schema: type: string /api/v1/sales: get: summary: '' operationId: getApiV1Sales description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Sales description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: id: type: string description: 'Le champ value doit être un UUID valide.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed nullable: true customer_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 nullable: true device_id: type: string description: 'Le champ value doit être un UUID valide.' example: 977e5426-8d13-3824-86aa-b092f8ae52c5 nullable: true note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: 'y' nullable: true redeem_points: type: integer description: 'Le champ value doit être au moins 0.' example: 60 nullable: true items: type: array description: 'Le champ value doit contenir au moins 1 éléments.' example: - [] items: type: object properties: product_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed nullable: true variant_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 nullable: true name: type: string description: 'Le champ value ne peut pas dépasser 200 caractères.' example: m nullable: true quantity: type: number description: '' example: 4326.41688 unit_price_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 77 nullable: true discount_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 8 nullable: true vat_rate: type: number description: 'Le champ value doit être au moins 0. Le champ value ne peut pas dépasser 100.' example: 8 nullable: true required: - quantity payments: type: array description: '' example: null items: type: object nullable: true properties: method: type: string description: 'This field is required when payments is present.' example: null amount_xaf: type: integer description: 'This field is required when payments is present. Le champ value doit être au moins 0.' example: 60 reference: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: d nullable: true required: - items '/api/v1/sales/{sale_id}': get: summary: '' operationId: getApiV1SalesSale_id description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/sales/{sale_id}/void': post: summary: '' operationId: postApiV1SalesSale_idVoid description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: reason: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: b required: - reason parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/sales/{sale_id}/refund': post: summary: 'Partial return / refund of selected items from a completed sale.' operationId: partialReturnRefundOfSelectedItemsFromACompletedSale description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: method: type: string description: '' example: architecto reason: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: 'n' nullable: true items: type: array description: 'Le champ value doit contenir au moins 1 éléments.' example: - [] items: type: object properties: sale_item_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed quantity: type: number description: '' example: 4326.41688 required: - sale_item_id - quantity required: - method - items parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/sales/{sale_id}/attribute-loyalty': post: summary: "A customer shows their loyalty card after paying. Credits the points\nthat sale would have earned had she been on the ticket, without\nrewriting the sale itself — see\nLoyaltyService::attributeToCompletedSale() for why." operationId: aCustomerShowsTheirLoyaltyCardAfterPayingCreditsThePointsThatSaleWouldHaveEarnedHadSheBeenOnTheTicketWithoutRewritingTheSaleItselfSeeLoyaltyServiceattributeToCompletedSaleForWhy description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: card_no: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: b required: - card_no parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/sales/{sale_id}/receipt': get: summary: "Returns the receipt as base64 ESC/POS bytes ready to stream to a thermal\nprinter. ?width=58|80 selects paper (32 or 48 columns); ?locale=fr|en." operationId: returnsTheReceiptAsBase64ESCPOSBytesReadyToStreamToAThermalPrinterwidth5880SelectsPaper32Or48Columnslocalefren description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/sales/{sale_id}/receipt/text': get: summary: "The receipt as plain text, for sharing by WhatsApp or any other channel\nthe client can reach. Returned rather than sent: WhatsApp has no server\nAPI here, and a share link opened on the cashier's own phone is both\nfree and the way shops in the region actually send these." operationId: theReceiptAsPlainTextForSharingByWhatsAppOrAnyOtherChannelTheClientCanReachReturnedRatherThanSentWhatsAppHasNoServerAPIHereAndAShareLinkOpenedOnTheCashiersOwnPhoneIsBothFreeAndTheWayShopsInTheRegionActuallySendThese description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/sales/{sale_id}/receipt/email': post: summary: 'E-mail the receipt to the customer (or any address supplied).' operationId: eMailTheReceiptToTheCustomerorAnyAddressSupplied description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: email: type: string description: 'Le champ value doit être une adresse e-mail valide. Le champ value ne peut pas dépasser 160 caractères.' example: gbailey@example.net nullable: true locale: type: string description: '' example: fr enum: - fr - en nullable: true parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/sales/{sale_id}/invoice': get: summary: "A full, self-contained HTML invoice ready for the browser's print dialogue." operationId: aFullSelfContainedHTMLInvoiceReadyForTheBrowsersPrintDialogue description: "No PDF library on purpose: shared hosting in the region rarely has the\nextensions those need, and the browser already renders @page CSS to PDF\nfor free. ?format=a4|a3 picks the sheet, ?locale=fr|en the language." parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: sale_id description: 'The ID of the sale.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string /api/v1/expenses: get: summary: '' operationId: getApiV1Expenses description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Expenses description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: category: type: string description: '' example: transport enum: - rent - utilities - salaries - transport - supplies - other amount_xaf: type: integer description: 'Le champ value doit être au moins 1.' example: 16 payment_method: type: string description: '' example: orange_money enum: - cash - mtn_momo - orange_money - card - bank description: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: 'Et animi quos velit et fugiat.' nullable: true spent_at: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:16' nullable: true required: - category - amount_xaf - payment_method '/api/v1/expenses/{expense_id}': delete: summary: '' operationId: deleteApiV1ExpensesExpense_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: expense_id description: 'The ID of the expense.' example: architecto required: true schema: type: string /api/v1/shift: get: summary: "The cashier's open shift (with live expected-cash), or null." operationId: theCashiersOpenShiftwithLiveExpectedCashOrNull description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/shifts: get: summary: '' operationId: getApiV1Shifts description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/shift/open: post: summary: '' operationId: postApiV1ShiftOpen description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: opening_float_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 27 required: - opening_float_xaf /api/v1/shift/close: post: summary: '' operationId: postApiV1ShiftClose description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: counted_cash_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 27 note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: 'n' nullable: true counted_by_method: type: array description: 'Le champ value doit être au moins 0.' example: - 84 items: type: integer nullable: true required: - counted_cash_xaf /api/v1/shift/cash: post: summary: 'Record cash in or out of the drawer that is not a sale.' operationId: recordCashInOrOutOfTheDrawerThatIsNotASale description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: direction: type: string description: '' example: out enum: - in - out amount_xaf: type: integer description: 'Le champ value doit être au moins 1.' example: 16 reason: type: string description: "Required, because an unexplained withdrawal is exactly the thing\nthis record exists to stop being unexplained. Le champ value ne peut pas dépasser 160 caractères." example: 'n' required: - direction - amount_xaf - reason /api/v1/reports/z: get: summary: '' operationId: getApiV1ReportsZ description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/reports/sales-summary: get: summary: '' operationId: getApiV1ReportsSalesSummary description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: from: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:16' to: type: string description: 'Le champ value doit être une date valide. Le champ value doit être une date postérieure ou égale à from.' example: '2052-08-27' required: - from - to /api/v1/reports/low-stock: get: summary: 'Products at or below their reorder level.' operationId: productsAtOrBelowTheirReorderLevel description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/reports/expiring: get: summary: '' operationId: getApiV1ReportsExpiring description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: within_days: type: integer description: 'Le champ value doit être au moins 1. Le champ value ne peut pas dépasser 3650.' example: 1 nullable: true /api/v1/alerts: get: summary: '' operationId: getApiV1Alerts description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: dead_days: type: integer description: 'Le champ value doit être au moins 1. Le champ value ne peut pas dépasser 3650.' example: 1 nullable: true expiring_days: type: integer description: 'Le champ value doit être au moins 1. Le champ value ne peut pas dépasser 3650.' example: 22 nullable: true limit: type: integer description: 'Le champ value doit être au moins 1.' example: 67 nullable: true /api/v1/reports/margin: get: summary: '' operationId: getApiV1ReportsMargin description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: from: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' nullable: true to: type: string description: 'Le champ value doit être une date valide. Le champ value doit être une date postérieure ou égale à from.' example: '2052-08-27' nullable: true /api/v1/reports/losses: get: summary: '' operationId: getApiV1ReportsLosses description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: from: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' nullable: true to: type: string description: 'Le champ value doit être une date valide. Le champ value doit être une date postérieure ou égale à from.' example: '2052-08-27' nullable: true /api/v1/stocktakes: get: summary: '' operationId: getApiV1Stocktakes description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Stocktakes description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: b nullable: true '/api/v1/stocktakes/{stocktake_id}': get: summary: '' operationId: getApiV1StocktakesStocktake_id description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: stocktake_id description: 'The ID of the stocktake.' example: 019fbfaf-5d33-7199-bdf5-7a88af9cb364 required: true schema: type: string '/api/v1/stocktakes/{stocktake_id}/counts': post: summary: '' operationId: postApiV1StocktakesStocktake_idCounts description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: counts: type: array description: 'Le champ value doit contenir au moins 1 éléments.' example: - [] items: type: object properties: item_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed counted_quantity: type: number description: 'Le champ value doit être au moins 0.' example: 84 nullable: true required: - item_id required: - counts parameters: - in: path name: stocktake_id description: 'The ID of the stocktake.' example: 019fbfaf-5d33-7199-bdf5-7a88af9cb364 required: true schema: type: string '/api/v1/stocktakes/{stocktake_id}/commit': post: summary: '' operationId: postApiV1StocktakesStocktake_idCommit description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: stocktake_id description: 'The ID of the stocktake.' example: 019fbfaf-5d33-7199-bdf5-7a88af9cb364 required: true schema: type: string '/api/v1/stocktakes/{stocktake_id}/cancel': post: summary: '' operationId: postApiV1StocktakesStocktake_idCancel description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: stocktake_id description: 'The ID of the stocktake.' example: 019fbfaf-5d33-7199-bdf5-7a88af9cb364 required: true schema: type: string /api/v1/reports/analytics: get: summary: '' operationId: getApiV1ReportsAnalytics description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: from: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' nullable: true to: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' nullable: true /api/v1/reports/vat-declaration: get: summary: 'Period VAT declaration: collected (4431) − deductible (4452).' operationId: periodVATDeclarationCollected4431Deductible4452 description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/reports/trial-balance: get: summary: '' operationId: getApiV1ReportsTrialBalance description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/reports/income-statement: get: summary: '' operationId: getApiV1ReportsIncomeStatement description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/reports/journal.csv: get: summary: "SYSCOHADA journal as CSV for the shop's accountant." operationId: sYSCOHADAJournalAsCSVForTheShopsAccountant description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/reports/balance-sheet: get: summary: '' operationId: getApiV1ReportsBalanceSheet description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints '/api/v1/reports/ledger/{accountCode}': get: summary: '' operationId: getApiV1ReportsLedgerAccountCode description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: accountCode description: '' example: architecto required: true schema: type: string /api/v1/accounting/close-period: post: summary: 'Close the books through a date: posts drafts, books the stock variation, moves the watermark.' operationId: closeTheBooksThroughADatePostsDraftsBooksTheStockVariationMovesTheWatermark description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: through: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' required: - through /api/v1/accounting/journal-entries: post: summary: 'A manual journal entry — capital, a loan, a fixed asset, an opening balance.' operationId: aManualJournalEntryCapitalALoanAFixedAssetAnOpeningBalance description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: date: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' memo: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: b lines: type: array description: 'Le champ value doit contenir au moins 2 éléments.' example: - [] items: type: object properties: account_code: type: string description: 'Le champ value ne peut pas dépasser 10 caractères.' example: bngzmi debit_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 76 nullable: true credit_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 60 nullable: true required: - account_code required: - date - memo - lines /api/v1/tables: get: summary: '' operationId: getApiV1Tables description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Tables description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/tables/{id}': put: summary: '' operationId: putApiV1TablesId description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1TablesId description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the table.' example: architecto required: true schema: type: string /api/v1/kitchen: get: summary: '' operationId: getApiV1Kitchen description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/orders: get: summary: 'Open orders (optionally for one table).' operationId: openOrdersoptionallyForOneTable description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Orders description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: table_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed nullable: true customer_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 nullable: true guest_count: type: integer description: 'Le champ value doit être au moins 1.' example: 27 nullable: true note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: i nullable: true '/api/v1/orders/{order_id}': get: summary: '' operationId: getApiV1OrdersOrder_id description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: order_id description: 'The ID of the order.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/orders/{order_id}/items': post: summary: '' operationId: postApiV1OrdersOrder_idItems description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: product_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed nullable: true variant_id: type: string description: 'Le champ value doit être un UUID valide.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 nullable: true name: type: string description: 'Le champ value ne peut pas dépasser 200 caractères.' example: m nullable: true quantity: type: number description: '' example: 4326.41688 unit_price_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 77 nullable: true discount_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 8 nullable: true item_note: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: 'y' nullable: true required: - quantity parameters: - in: path name: order_id description: 'The ID of the order.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/orders/{order_id}/items/{item_id}': patch: summary: '' operationId: patchApiV1OrdersOrder_idItemsItem_id description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: quantity: type: number description: 'Le champ value doit être au moins 0.' example: 27 required: - quantity delete: summary: '' operationId: deleteApiV1OrdersOrder_idItemsItem_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: order_id description: 'The ID of the order.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string - in: path name: item_id description: 'The ID of the item.' example: 019fbfad-7fa1-73f6-87f1-bf5771424bc1 required: true schema: type: string '/api/v1/orders/{order_id}/fire': post: summary: '' operationId: postApiV1OrdersOrder_idFire description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: order_id description: 'The ID of the order.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/orders/{order_id}/close': post: summary: '' operationId: postApiV1OrdersOrder_idClose description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: redeem_points: type: integer description: 'Le champ value doit être au moins 0.' example: 27 nullable: true payments: type: array description: 'Le champ value doit contenir au moins 1 éléments.' example: - [] items: type: object properties: method: type: string description: '' example: architecto amount_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 39 reference: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: g nullable: true required: - method - amount_xaf required: - payments parameters: - in: path name: order_id description: 'The ID of the order.' example: 569e3716-b7cf-4f01-8186-92f2d7e82f7c required: true schema: type: string '/api/v1/order-items/{item_id}/status': patch: summary: '' operationId: patchApiV1OrderItemsItem_idStatus description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: status: type: string description: '' example: served enum: - pending - sent - ready - served - void required: - status parameters: - in: path name: item_id description: 'The ID of the item.' example: 019fbfad-7fa1-73f6-87f1-bf5771424bc1 required: true schema: type: string /api/v1/sync/pull: get: summary: "Pull records changed since the client's cursor, per entity, paginated." operationId: pullRecordsChangedSinceTheClientsCursorPerEntityPaginated description: "Query params:\n - since: ISO-8601 cursor. Omitted on a device's first sync.\n - limit: page size, default DEFAULT_LIMIT, clamped to [1, MAX_LIMIT].\n - include_sales: sales (with items + payments) are only built and\n serialised when this is truthy (\"1\"). This block is by far the most\n expensive part of the response — every sale in the window, each\n eager-loading its line items and payments — and nothing currently\n consumes it: the desktop app is just this same SPA loaded in an\n Electron shell, the client's sync engine (resources/js/lib/sync.js)\n already discards data.sales, and there is no local sales mirror to\n populate. Defaulting it off keeps ordinary pulls (catalogue +\n customers) cheap on memory-constrained shared hosting. When omitted,\n `data.sales` is still present as an empty array and\n `has_more.sales` as false, so the response shape never changes." parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/sync/push: post: summary: '' operationId: postApiV1SyncPush description: '' parameters: [] responses: { } tags: - Endpoints /api/v1/sync/conflicts: get: summary: '' operationId: getApiV1SyncConflicts description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints '/api/v1/sync/conflicts/{conflict_id}/resolve': post: summary: '' operationId: postApiV1SyncConflictsConflict_idResolve description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: conflict_id description: 'The ID of the conflict.' example: architecto required: true schema: type: string /api/v1/attendance/me: get: summary: "The signed-in user's own current clock status — the self-service widget." operationId: theSignedInUsersOwnCurrentClockStatusTheSelfServiceWidget description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/attendance/clock-in: post: summary: '' operationId: postApiV1AttendanceClockIn description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: b nullable: true /api/v1/attendance/clock-out: post: summary: '' operationId: postApiV1AttendanceClockOut description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: b nullable: true /api/v1/attendance: get: summary: "Owner/manager roster view — every employee's sessions, filterable." operationId: ownermanagerRosterViewEveryEmployeesSessionsFilterable description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints '/api/v1/attendance/{attendance_id}': put: summary: "A manager's correction — e.g. a forgotten clock-out." operationId: aManagersCorrectionEgAForgottenClockOut description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: clock_in: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' clock_out: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' nullable: true note: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: b nullable: true parameters: - in: path name: attendance_id description: 'The ID of the attendance.' example: architecto required: true schema: type: string /api/v1/audit-logs: get: summary: '' operationId: getApiV1AuditLogs description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/devices: get: summary: '' operationId: getApiV1Devices description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Devices description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: b platform: type: string description: '' example: pwa enum: - pwa - desktop nullable: true priority: type: integer description: 'Le champ value doit être au moins 1.' example: 22 nullable: true required: - name '/api/v1/devices/{device_id}/regenerate': post: summary: '' operationId: postApiV1DevicesDevice_idRegenerate description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: device_id description: 'The ID of the device.' example: 019fc952-e363-7110-95bc-46cb53173445 required: true schema: type: string '/api/v1/devices/{device_id}': put: summary: '' operationId: putApiV1DevicesDevice_id description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1DevicesDevice_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: device_id description: 'The ID of the device.' example: 019fc952-e363-7110-95bc-46cb53173445 required: true schema: type: string /api/v1/device-pairings/claim: post: summary: 'An owner, already signed in, scans or types the code to approve it.' operationId: anOwnerAlreadySignedInScansOrTypesTheCodeToApproveIt description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'Le champ value doit contenir 8 caractères.' example: bngzmiyv name: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: d nullable: true required: - code /api/v1/settings: get: summary: '' operationId: getApiV1Settings description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints put: summary: '' operationId: putApiV1Settings description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b business_type: type: string description: '' example: null phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: 'n' nullable: true email: type: string description: 'Le champ value doit être une adresse e-mail valide. Le champ value ne peut pas dépasser 160 caractères.' example: ashly64@example.com nullable: true address: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: v nullable: true city: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: d nullable: true tax_id: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: l nullable: true rccm: type: string description: 'Le champ value ne peut pas dépasser 60 caractères.' example: j nullable: true vat_rate: type: number description: 'Le champ value doit être au moins 0. Le champ value ne peut pas dépasser 100.' example: 17 locale: type: string description: '' example: en enum: - fr - en loyalty_enabled: type: boolean description: '' example: false points_per_currency: type: number description: 'Le champ value doit être au moins 0.' example: 8 currency_per_point: type: number description: 'Le champ value doit être au moins 0.' example: 75 loyalty_points_expiry_months: type: integer description: "Null/absent = expiry off. A shop must opt in; existing balances\nmust not start ageing out the moment this ships. Le champ value doit être au moins 1. Le champ value ne peut pas dépasser 60." example: 14 nullable: true /api/v1/users: get: summary: '' operationId: getApiV1Users description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Users description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b email: type: string description: 'Le champ value doit être une adresse e-mail valide.' example: zbailey@example.net password: type: string description: 'Le champ value doit contenir au moins 8 caractères.' example: '-0pBNvYgxw' role: type: string description: '' example: architecto phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: 'n' nullable: true required: - name - email - password - role '/api/v1/users/{id}': put: summary: '' operationId: putApiV1UsersId description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: 'n' nullable: true role: type: string description: '' example: null is_active: type: boolean description: '' example: true password: type: string description: 'Le champ value doit contenir au moins 8 caractères.' example: '|{+-0pBNvYgx' nullable: true delete: summary: '' operationId: deleteApiV1UsersId description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the user.' example: architecto required: true schema: type: string /api/v1/employees: get: summary: '' operationId: getApiV1Employees description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1Employees description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/employees/{id}': get: summary: '' operationId: getApiV1EmployeesId description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints put: summary: '' operationId: putApiV1EmployeesId description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: "Soft delete only — a departed employee's payslips and attendance must stay real." operationId: softDeleteOnlyADepartedEmployeesPayslipsAndAttendanceMustStayReal description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the employee.' example: architecto required: true schema: type: string /api/v1/payroll-runs: get: summary: '' operationId: getApiV1PayrollRuns description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1PayrollRuns description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: period_start: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' period_end: type: string description: 'Le champ value doit être une date valide. Le champ value doit être une date postérieure ou égale à period_start.' example: '2052-08-27' required: - period_start - period_end '/api/v1/payroll-runs/{payrollRun_id}': get: summary: '' operationId: getApiV1PayrollRunsPayrollRun_id description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: payrollRun_id description: 'The ID of the payrollRun.' example: architecto required: true schema: type: string '/api/v1/payroll-runs/{payrollRun_id}/finalize': post: summary: '' operationId: postApiV1PayrollRunsPayrollRun_idFinalize description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: payrollRun_id description: 'The ID of the payrollRun.' example: architecto required: true schema: type: string '/api/v1/payroll-runs/{payrollRun_id}/pay': post: summary: '' operationId: postApiV1PayrollRunsPayrollRun_idPay description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: payment_method: type: string description: '' example: card enum: - cash - mtn_momo - orange_money - card - bank required: - payment_method parameters: - in: path name: payrollRun_id description: 'The ID of the payrollRun.' example: architecto required: true schema: type: string '/api/v1/payslips/{payslip_id}': put: summary: '' operationId: putApiV1PayslipsPayslip_id description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: deductions_xaf: type: integer description: 'Le champ value doit être au moins 0.' example: 27 notes: type: string description: 'Le champ value ne peut pas dépasser 255 caractères.' example: 'n' nullable: true required: - deductions_xaf parameters: - in: path name: payslip_id description: 'The ID of the payslip.' example: architecto required: true schema: type: string '/api/v1/payslips/{payslip_id}/verify-url': get: summary: 'The signed link the client renders as a QR before requesting the sheet — same two-step shape as sale invoices.' operationId: theSignedLinkTheClientRendersAsAQRBeforeRequestingTheSheetSameTwoStepShapeAsSaleInvoices description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: payslip_id description: 'The ID of the payslip.' example: architecto required: true schema: type: string '/api/v1/payslips/{payslip_id}/print': get: summary: "A full, self-contained HTML payslip ready for the browser's print\ndialogue. Same no-PDF-library approach as the sale invoice — the\nbrowser's own @page CSS does the job." operationId: aFullSelfContainedHTMLPayslipReadyForTheBrowsersPrintDialogueSameNoPDFLibraryApproachAsTheSaleInvoiceTheBrowsersOwnpageCSSDoesTheJob description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: payslip_id description: 'The ID of the payslip.' example: architecto required: true schema: type: string /api/v1/admin/dashboard: get: summary: '' operationId: getApiV1AdminDashboard description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/admin/plans: get: summary: '' operationId: getApiV1AdminPlans description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints post: summary: '' operationId: postApiV1AdminPlans description: '' parameters: [] responses: { } tags: - Endpoints '/api/v1/admin/plans/{plan_id}': put: summary: '' operationId: putApiV1AdminPlansPlan_id description: '' parameters: [] responses: { } tags: - Endpoints delete: summary: '' operationId: deleteApiV1AdminPlansPlan_id description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: plan_id description: 'The ID of the plan.' example: 019fbec2-b453-719c-98f8-fa398f0b6949 required: true schema: type: string /api/v1/admin/tenants: post: summary: 'Opesware onboards a shop directly from the console.' operationId: opeswareOnboardsAShopDirectlyFromTheConsole description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: shop_name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: b business_type: type: string description: '' example: architecto nullable: true phone: type: string description: 'Le champ value ne peut pas dépasser 40 caractères.' example: 'n' nullable: true city: type: string description: 'Le champ value ne peut pas dépasser 120 caractères.' example: g nullable: true owner_name: type: string description: 'Le champ value ne peut pas dépasser 160 caractères.' example: z owner_email: type: string description: 'Le champ value doit être une adresse e-mail valide.' example: rempel.chadrick@example.org password: type: string description: 'Le champ value doit contenir au moins 8 caractères.' example: 'NvYgxwmi/#iw/kX' plan_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 7212c28d-f9ab-3dd7-af8a-06584a0d4cb7 nullable: true required: - shop_name - owner_name - owner_email - password get: summary: '' operationId: getApiV1AdminTenants description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints '/api/v1/admin/tenants/{id}/users': get: summary: '' operationId: getApiV1AdminTenantsIdUsers description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: id description: 'The ID of the tenant.' example: architecto required: true schema: type: string '/api/v1/admin/tenants/{id}/users/{user}/password': post: summary: '' operationId: postApiV1AdminTenantsIdUsersUserPassword description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: password: type: string description: 'Le champ value doit contenir au moins 12 caractères.' example: '|]|{+-' required: - password parameters: - in: path name: id description: 'The ID of the tenant.' example: architecto required: true schema: type: string - in: path name: user description: 'The user.' example: architecto required: true schema: type: string '/api/v1/admin/tenants/{id}/users/{user}/active': post: summary: '' operationId: postApiV1AdminTenantsIdUsersUserActive description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: is_active: type: boolean description: '' example: true required: - is_active parameters: - in: path name: id description: 'The ID of the tenant.' example: architecto required: true schema: type: string - in: path name: user description: 'The user.' example: architecto required: true schema: type: string '/api/v1/admin/tenants/{id}': get: summary: '' operationId: getApiV1AdminTenantsId description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints parameters: - in: path name: id description: 'The ID of the tenant.' example: architecto required: true schema: type: string '/api/v1/admin/tenants/{id}/suspend': post: summary: '' operationId: postApiV1AdminTenantsIdSuspend description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the tenant.' example: architecto required: true schema: type: string '/api/v1/admin/tenants/{id}/activate': post: summary: '' operationId: postApiV1AdminTenantsIdActivate description: '' parameters: [] responses: { } tags: - Endpoints parameters: - in: path name: id description: 'The ID of the tenant.' example: architecto required: true schema: type: string '/api/v1/admin/tenants/{id}/plan': post: summary: '' operationId: postApiV1AdminTenantsIdPlan description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: plan_id: type: string description: 'Le champ value doit être un UUID valide. Must match an existing stored value.' example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed required: - plan_id parameters: - in: path name: id description: 'The ID of the tenant.' example: architecto required: true schema: type: string /api/v1/admin/billing: get: summary: '' operationId: getApiV1AdminBilling description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/admin/subscriptions: get: summary: '' operationId: getApiV1AdminSubscriptions description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/admin/audit-logs: get: summary: '' operationId: getApiV1AdminAuditLogs description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints /api/v1/admin/analytics: get: summary: '' operationId: getApiV1AdminAnalytics description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: from: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' nullable: true to: type: string description: 'Le champ value doit être une date valide.' example: '2026-08-04T06:23:17' nullable: true