App Fixes — Change Log Author : Josh S. Sakweli, Backend Lead Team Last Updated : 2026-10-25 Version : v1.0 Backend version / branch : 1.2.4 · fet/location_service (+ notification server fet/location_service , File Thunder fet/shipping ) Deployed to : not yet — staging first Compared with : the staging branch as deployed ( 448f06a0 , 2026-10-06) — "Before" means what the app sees on staging today Base URL : https://api.nexgate.co/api/v1 (staging: https://dev.api.nexgate.co/api/v1 ) Short Description : Shops now have a real place on the map, buyers keep an address book, and every shop in a checkout is either pickup at the shop or delivery by a shipper with a real price. This changes existing shop, product, cart, checkout and order endpoints — the old fake shipping fields ( shippingAddressId , shippingMethodId , trackingNumber , carrier ) are gone. The release also carries app fixes: digital downloads, chat avatars, profile cover, donation target, the sign-in alert. Android, iOS and web must update before this goes to production; the admin dashboard has a few changes. Audience : mobile team · web team · admin dashboard Related docs — the new endpoints are not repeated here, they live in their own docs: docs/location/location_api_doc.md — pin → area, area lists, shop location, my addresses, recent pins docs/shipping/shipping_api_doc.md — delivery options, cart delivery, every pickup / delivery order action, shippers, admin docs/shipping/campaigns/campaigns_api_doc.md — delivery discounts run by shops, shippers and NexGate notifications_inapp_bell_api_doc.md — the bell list, targetType / targetId Also new since staging, each with its own doc (already shared, not repeated here): event_group_tickets_api_doc.md — event tickets "buy together": groupBuying on tickets, group on POST /e-events/checkout , ticket groups (open, by code, mine, transfer, withdraw), 4 notifications product_group_purchase_api_doc.md — product group buys: whole-payment transfer, withdraw, orders always created, true savings, 15-minute expiry This log covers what changed in endpoints, fields, rules and notifications that already existed , plus the few new endpoints that sit inside an existing service and have no other doc. How to read this Change types Badge Means App must act? NEW a new endpoint, field or notification only to use the new feature CHANGED an existing endpoint gained fields or behaves differently; old calls still work should update BREAKING old calls fail or give a different meaning — a field removed or renamed, a rule tightened must update before the deploy date REMOVED the endpoint / field / notification is gone must stop using it FIX a bug fixed; same shape, now correct values check screens that relied on the wrong value HTTP methods GET - GET POST - POST PUT - PUT PATCH - PATCH DELETE - DELETE Rules this log follows Grouped by service . Inside a service: what changed, the flow (when the order of steps changed), the endpoint table, then one block per endpoint. Every changed endpoint shows Before and After for the request and the response — only the parts that changed. Samples are short: the changed fields and one or two around them. Coded refusals are HTTP 409 with data.code (and now, sometimes, data.details ). New codes are listed per endpoint and again in the index (§16). 0. At a glance # Service New Changed Breaking Removed App work 1 Shops 4 6 2 6 fields location step after create; opening hours + pause screens; read location , availability ; show review replies 2 Products — 3 — — Directions on the product page; "Bulky" switch 3 Cart 1 4 — — group by shop, delivery per shop, ticks, selectedTotal 4 Checkout & payment — 4 1 2 request + 2 response fields send fulfillment ; handle price-changed codes 5 Orders — 6 2 3 fields read fulfillment ; pickup orders use the new order actions 6 Installments — 3 — 1 field delivery for "ships now" plans; cancel via the agreement 7 Group purchase — 1 — — pickup / delivery in the group sheet 8 Wallet & transactions — 1 — — new history titles 9 Events — 4 1 — venue pin required to publish; donation progress 10 Profile & account — 2 — — cover photo 11 Interests — 1 — — (admin) only admins edit categories 12 Chat — 2 — — avatars are now real links 13 Digital downloads — 1 — — save the file as received 14 Admin (existing screens) 1 2 — — review moderation, fee type, reconciliation lines 15 Notifications 26 9 — — handle new types and 4 new targets 0.1 Breaking changes — do these first Service Endpoint / item What breaks Do this Section Shops create / update shop city , region , streetAddress , landmark , latitude , longitude no longer exist stop sending them; add the Shop location step §1.2 Shops every shop response, shop search the same fields are gone from responses read hasLocation + location §1.3, §1.4 Checkout POST /e-commerce/checkout-sessions , PATCH …/{id} shippingAddressId / shippingMethodId removed; a session with physical items needs fulfillment send fulfillment §4.2 Checkout session response shippingAddress / shippingMethod removed read fulfillment §4.3 Orders order detail deliveryAddress , trackingNumber , carrier removed read fulfillment §5.2 Orders /orders/{id}/ship , /confirm-delivery , /regenerate-code refused for pickup orders and for orders a shipper delivers use the shipping order actions §5.4 Orders delivery code 6 digits → 4 digits , expires after 24 h, wrong code is a coded 409 4-digit input, handle the codes §5.5 Events publish / reveal-location an in-person or hybrid event needs a venue pin pin screen on the venue step §9.2 0.2 Removed Service Endpoint / field / notification Use instead Section Shops request + response fields city , region , streetAddress , landmark , latitude , longitude location (shop location) §1.2–§1.4 Checkout shippingAddressId , shippingMethodId (request) · shippingAddress , shippingMethod (response) fulfillment §4.2, §4.3 Orders deliveryAddress , trackingNumber , carrier (detail) · trackingNumber on /ship fulfillment §5.2 Installments shippingAddress on the agreement fulfillment §6.1 1. Shops What changed in this service : a shop's place is no longer typed text (city, region, landmark). It is a pin on the map , turned by the backend into ward / district / region, set with the shop location endpoint (location doc). Every shop response now carries that location , and an availability block (open now, paused, taking orders). Paused shops stay visible. Shop reviews finally show the owner's reply. 1.0 Flow — before and after Before flowchart LR A[Create shop
type city, region, street, landmark, lat/lng] --> B[Shop live] After flowchart LR A[Create shop
no location fields] --> B[Shop location step
drop a pin: PUT /shops/id/location] --> C[Shop can sell physical items] A -. no location yet .-> D[Physical items refused at checkout:
SHOP_LOCATION_REQUIRED] What the user notices : the seller drops a pin instead of typing an address; buyers see "Kariakoo, Ilala, Dar es Salaam" with a Directions and a Call button; shops show "Open now · closes 18:00" or "Paused until …". 1.1 Endpoints in this service Change Method Endpoint One line App BREAKING POST /e-commerce/shops location fields removed must update BREAKING PUT /e-commerce/shops/{shopId} location fields removed must update BREAKING GET every shop response (profile, detailed, lists, create / update / approve) old location fields out, hasLocation + location + availability in must update BREAKING GET /e-commerce/shops/search city → location must update CHANGED GET /e-commerce/search (global) shop.location is now the area name check display CHANGED — product pages, search, subscribe, event / post / clip shop links paused shops are accepted, not 404 no change CHANGED GET shop reviews (list, paged, my-review, create / update) gain replyText , repliedAt should update FIX GET shop rating, /summary counts only ACTIVE reviews check NEW PUT /e-commerce/shops/{shopId}/hours set the shop's opening hours to use NEW GET /e-commerce/shops/{shopId}/hours read them to use NEW PUT /e-commerce/shops/{shopId}/pause pause the shop to use NEW DELETE /e-commerce/shops/{shopId}/pause reopen it to use New, documented elsewhere: shop location PUT/GET /e-commerce/shops/{shopId}/location → location doc §6–§7. 1.2 BREAKING Create / update shop — location fields removed Endpoint : POST {base_url}/e-commerce/shops · PUT {base_url}/e-commerce/shops/{shopId} Change BREAKING in meaning — old bodies are still accepted (the fields are ignored) but nothing is saved from them Impact Must update Reference commerce_logistics_changes.md §5 In one line : the shop's place is no longer part of create / update; set it with the shop location step. Request Field Before After Note city , region required on create removed streetAddress , landmark , latitude , longitude optional removed landmark + pin now live in the shop location phoneNumber , countryCode , the rest unchanged unchanged phoneNumber is also the shop location's phone Before After { "shopName": "Mama Yoyo Shop", "phoneNumber": "+255744742944", "city": "Dar es Salaam", "region": "Dar es Salaam", "landmark": "Opposite the clock tower" } { "shopName": "Mama Yoyo Shop", "phoneNumber": "+255744742944" } App to do Remove city / region / street / landmark / lat / lng inputs from the shop form. After create, open the Shop location step ( PUT …/location , location doc §6); editing uses the same screen. 1.3 BREAKING Shop responses — location , hasLocation , availability Endpoint : every shop response — GET /e-commerce/shops/{shopId} , /{shopId}/detailed , /shops/all , /featured , /my-shops (and paged / category forms), create / update / approve. Change BREAKING — fields removed; new blocks added Impact Must update Reference §6, §21 Response Field Before After Note streetAddress , city , region present removed latitude , longitude , landmark present removed hasLocation — added true / false location — added same object as GET …/location (location doc §7), or null availability — added open now / paused / taking orders Before After { "shopName": "Mama Yoyo Shop", "city": "Dar es Salaam", "region": "Dar es Salaam", "landmark": "Opposite the clock tower", "latitude": -6.819, "longitude": 39.2768 } { "shopName": "Mama Yoyo Shop", "hasLocation": true, "location": { "lat": -6.819, "lng": 39.2768, "areaLabel": "Kariakoo, Ilala, Dar es Salaam", "landmark": "Opposite the clock tower", "phone": "+255744742944", "…": "…" }, "availability": { "status": "ACTIVE", "acceptingOrders": true, "paused": false, "pauseMode": null, "pausedUntil": null, "pauseReason": null, "hours": { "hasHours": true, "openNow": true, "closesAt": "2026-10-25T18:00:00+03:00", "nextOpensAt": null } } } Behaviour availability.acceptingOrders is false when the shop is paused with STOP_ORDERS , suspended or closed → Buy is off. pauseMode : KEEP_SELLING (shows "paused" but orders still come in, accepted later) or STOP_ORDERS . hours.openNow is null when the shop has set no hours. All times are East Africa Time with the offset. Paused shops stay visible. Product pages, product and shop search, subscribe, profile shop counts, linking a shop to an event and shop tags on posts / clips now accept a paused ( TEMPORARILY_OFFLINE ) shop — before, its products 404'd. App to do Show area / map / Directions ( lat,lng → Google / Apple Maps) / Call ( phone ) from location . Owner: when hasLocation is false show "Add your shop location to start receiving orders". Shop page: "Open now · closes 18:00" / "Opens Mon 08:00" / "Paused until …"; Buy off when acceptingOrders is false. 1.4 BREAKING Shop search and global search Endpoint : GET {base_url}/e-commerce/shops/search · GET {base_url}/e-commerce/search?query=… Response Field Before After shop search items city owner-typed text removed shop search items location — added (location object or null ) global search, type: "SHOP" shop.location (string) owner-typed landmark the area name, e.g. "Ubungo, Ubungo, Dar es Salaam" , or null Reference : §7, §8 1.5 CHANGED Shop reviews — the owner's reply is returned Endpoint : shop review list, /paged , /my-review , create / update responses. Change CHANGED (+ FIX on the rating) Impact Should update Reference §31 In one line : the reply the owner saved with POST /shops/reviews/{shopId}/{reviewId}/reply was never returned; now it is. Field Before After replyText , repliedAt — added (null when no reply) FIX : the shop's average, rating count and the 5★…1★ breakdown ( /summary , and the rating on shop responses) counted every review whatever its status; now only ACTIVE reviews count, so a hidden review leaves the stars at once. App to do Show the shop's reply under a review when replyText is set. 1.6 NEW Shop opening hours Purpose : the hours that drive availability.hours ("Open now · closes 18:00"), the seller's 24-opening-hour accept window and the buyer's pickup times. Endpoint : PUT {base_url}/e-commerce/shops/{shopId}/hours · GET {base_url}/e-commerce/shops/{shopId}/hours Access Level : 🔒 PUT: shop owner, or a super / staff admin · GET: any logged-in user Authentication : Bearer token Request JSON Sample (PUT — send the whole week every time; it replaces what was there): { "weekly": [ { "day": "MONDAY", "open": "08:00", "close": "18:00" }, { "day": "SATURDAY", "open": "09:00", "close": "13:00" }, { "day": "SATURDAY", "open": "14:00", "close": "17:00" } ], "specialDays": [ { "date": "2026-12-25", "closed": true, "ranges": [], "note": "Christmas" }, { "date": "2026-12-31", "closed": false, "ranges": [ { "open": "08:00", "close": "12:00" } ] } ] } Request Body Parameters : Parameter Type Required Description Validation weekly array Yes { day, open, close } ; several ranges per day allowed; a day not listed = closed max 21; day MONDAY … SUNDAY ; times HH:mm weekly[].close time Yes earlier than open = runs past midnight (e.g. 20:00 → 02:00) specialDays array No one date that differs from the week: closed: true , or its own ranges max 60; each date once; not closed → at least one range (max 3) specialDays[].note string No e.g. "Christmas" max 100 sameDayCutoff time No not used for shops (shippers only) Success Response JSON Sample (PUT and GET): { "success": true, "httpStatus": "OK", "message": "Opening hours saved", "data": { "weekly": [ { "day": "MONDAY", "open": "08:00:00", "close": "18:00:00" } ], "specialDays": [ { "date": "2026-12-25", "closed": true, "ranges": [], "note": "Christmas" } ], "sameDayCutoff": null, "timeZone": "Africa/Dar_es_Salaam", "updatedAt": "2026-10-25T10:12:00" } } Success Response Fields : Field Description weekly, specialDays as saved, weekly sorted by day then open time timeZone always East Africa Time Errors : Code / message Status When HOURS_INVALID 409 a special day listed twice, or not closed and with no hours "Only the shop owner can change the shop's hours or pause it" 403 not the owner "This shop has not set its opening hours yet" 404 GET before any hours were saved Rules : a shop with no hours counts as always open ( availability.hours.hasHours: false , openNow: null ). The status on shop responses updates at once after a save. App to do : Seller: "Opening hours" screen (a row per day, add a second range, special days) → PUT the whole thing. 1.7 NEW Pause / reopen the shop Purpose : the seller takes a break — with or without still taking orders. Endpoint : PUT {base_url}/e-commerce/shops/{shopId}/pause · DELETE {base_url}/e-commerce/shops/{shopId}/pause Access Level : 🔒 shop owner, or a super / staff admin Authentication : Bearer token Request JSON Sample (PUT): { "mode": "STOP_ORDERS", "until": "2026-10-28T09:00:00+03:00", "reason": "Stock taking" } Request Body Parameters : Parameter Type Required Description Validation mode enum Yes STOP_ORDERS — checkout refused with SHOP_CLOSED ; KEEP_SELLING — shows "paused" but orders still come in and wait until datetime with offset No reopens by itself at that time (checked every 5 minutes); leave out = until reopened by hand must be in the future reason string No shown to buyers ("Paused: Stock taking") max 255 Success Response JSON Sample (PUT "Shop paused", DELETE "Shop reopened") — the availability block (§1.3): { "success": true, "httpStatus": "OK", "message": "Shop paused", "data": { "status": "TEMPORARILY_OFFLINE", "acceptingOrders": false, "paused": true, "pauseMode": "STOP_ORDERS", "pausedUntil": "2026-10-28T09:00:00+03:00", "pauseReason": "Stock taking", "hours": { "hasHours": true, "openNow": true, "closesAt": "2026-10-25T18:00:00+03:00", "nextOpensAt": null } } } Errors : Code / message Status When SHOP_NOT_PAUSABLE 409 the shop is neither ACTIVE nor already paused (e.g. SUSPENDED, PENDING, closed) "Only the shop owner can change the shop's hours or pause it" 403 not the owner Rules : Pausing a paused shop replaces its mode / until / reason. DELETE on a shop that is not paused changes nothing and returns its availability. A paused shop stays visible everywhere (§1.3). KEEP_SELLING : orders are not auto-cancelled while paused; their 24-opening-hour accept deadline restarts when the pause ends. App to do : Seller: "Pause shop" sheet (stop orders / keep selling, optional until, optional reason) and a "Reopen" button. Buyer: "Paused until …" on the shop page from availability . 2. Products What changed in this service : product responses carry the shop's location (for "Pickup at …" and Directions), a new bulky flag tells shippers a parcel is big or heavy, and a refused save no longer half-saves. 2.1 Endpoints in this service Change Method Endpoint One line App CHANGED GET product detail, public view, by slug, shop product lists / filter / search gain shopLocation / shop.location should update CHANGED POST / PUT / PATCH create / update product, every product response new bulky should update (seller form) FIX POST / PUT / PATCH create / update / publish / delete / restore a refused request changes nothing no change FIX DELETE /e-commerce/products/{shopId}/{productId}/installment-plans/{planId} readable refusal for a used plan no change 2.2 CHANGED Product responses — shop location Response Field added GET …/products/{productId}/detailed (owner) shopLocation GET …/products/{productId} , by slug (public) shopLocation shop product lists / filter / search ( shop summary block) shop.location Same location object as the shop's, or null when the shop has none. Reference : §9 "shopLocation": { "lat": -6.7889, "lng": 39.2083, "areaLabel": "Ubungo, Ubungo, Dar es Salaam", "phone": "+255700111222", "…": "…" } App to do Product page: "Pickup at Ubungo, Ubungo" + Directions; optionally disable Buy for a physical product when shopLocation is null . 2.3 CHANGED Products — bulky Before After Create ( POST ) — optional "bulky": true (default false ) Update ( PUT / PATCH ) — "bulky" changes it; leaving it out keeps it Detailed, summary, public responses — "bulky": false | true (next to condition ) Shippers that do not carry bulky parcels are left out of its delivery options. The backend also ticks it by itself after two different shippers report the product too big. Reference : §19, §45 App to do Seller product form: "Bulky / heavy item" switch. 2.4 FIX Refused product saves / used installment plans Before (wrong) After (correct) update + publish with no image → refused "At least one product image is required", but the new name / price were saved a refused create / update / publish / delete / restore changes nothing deleting an installment plan buyers used → raw database error "Buyers have already used this plan, so it can't be deleted. Deactivate it instead." Reference : §27, §71 3. Cart What changed in this service : the cart now knows about delivery. It keeps one delivery address and, per shop with physical items, pickup or delivery with a held price. Each item has a tick : checkout buys only ticked items. Adding, changing or removing items answers with what that did to delivery. 3.0 Flow — before and after Before flowchart LR A[Add to cart] --> B[Cart: items only] --> C[Checkout: pick address + DHL placeholder] --> D[Pay the whole cart] After flowchart LR A[Add to cart
+ pickup / delivery for that shop] --> B[Cart: one address,
a choice per shop, held prices, ticks] B --> C[Checkout: takes the cart's choices,
ticked items only] --> D[Pay: items + delivery] D --> E[Bought items leave the cart;
unticked ones stay] What the user notices : the cart is grouped by shop, each shop shows "Pickup at the shop" or "Delivery by Dar Cargo · TZS 3,500 · Tue–Wed", the total includes delivery, and a change of address re-prices every shop and says what changed. 3.1 Endpoints in this service Change Method Endpoint One line App CHANGED POST /e-commerce/cart/add optional fulfillment + dropoff ; answers with the delivery change should update CHANGED PUT /e-commerce/cart/items/{itemId} answers with the delivery change should update CHANGED DELETE /e-commerce/cart/items/{itemId} answers with the delivery change should update CHANGED GET /e-commerce/cart gains destination , shops , ticks, delivery totals should update CHANGED DELETE /e-commerce/cart/clear keeps the delivery address no change NEW PUT /e-commerce/cart/selection tick / untick items to use FIX POST /e-commerce/cart/add two taps at once no longer fail one of them no change FIX — after a paid cart checkout the bought items now leave the cart re-read the cart New, documented elsewhere: PUT /e-commerce/cart/destination , POST /e-commerce/cart/destination/change-back , PUT /e-commerce/cart/shops/{shopId}/fulfillment → shipping doc §B. 3.2 CHANGED Add to cart / change / remove an item Endpoint : POST {base_url}/e-commerce/cart/add · PUT {base_url}/e-commerce/cart/items/{itemId} · DELETE {base_url}/e-commerce/cart/items/{itemId} Change CHANGED — old bodies still work Impact Should update Reference §32 In one line : the product sheet can send the shop's pickup / delivery choice, and every cart change says what it did to delivery. Request (add only) Field Before After Note productId , quantity required unchanged fulfillment — added , optional: { type: PICKUP | DELIVERY, quotationId, optionId } the choice from the product sheet dropoff — added , optional same shape as checkout; only when the buyer changed the address in the sheet — it becomes the cart's address Before After { "productId": "5f0e…", "quantity": 2 } { "productId": "5f0e…", "quantity": 2, "fulfillment": { "type": "DELIVERY", "quotationId": "3c1f…", "optionId": "o-1" }, "dropoff": { "addressId": "9b1d…" } } Response (add, change, remove) Field Before After Note data null the delivery change { deliveryBefore, deliveryAfter, switchedToPickup, canChangeBack, message, changes } Before After { "success": true, "message": "Product added to cart successfully", "data": null } { "success": true, "message": "Product added to cart successfully", "data": { "deliveryBefore": 10500, "deliveryAfter": 13000, "switchedToPickup": 1, "canChangeBack": false, "message": "Delivery 10,500 → 13,000, 1 shop switched to pickup", "changes": [ { "shopId": "cfb6…", "shopName": "My Shop", "before": { "type": "DELIVERY", "businessName": "Dar Cargo", "total": 3500 }, "after": { "type": "PICKUP", "businessName": null, "total": 0 }, "message": "…" } ] } } Behaviour A sheet quotation that no longer fits (other items, other address, expired) is not an error : the item is added and the shop falls back to the cart rules. FIX : two add-to-cart taps at the same moment for a new product used to fail one of them; now both succeed. App to do Send fulfillment (and dropoff when changed) from the product sheet. Show data.message after add / change / remove. 3.3 CHANGED Get the cart Endpoint : GET {base_url}/e-commerce/cart Change CHANGED — fields added; cartItems and totalAmount unchanged (items only) Impact Should update Reference §32, §48, §49, §65, §68 Response Field Before After Note destination — added the cart's delivery place (pin, areaLabel , label) or null shops[] — added one per shop with ticked physical items (below) cartItems[].selected — added the tick cartSummary.deliveryTotal — added delivery of ticked items, what the buyer pays cartSummary.totalWithDelivery — added cartSummary.selectedItems , selectedQuantity , selectedAmount — added ticked items only cartSummary.selectedTotal — added ticked items + their delivery — the amount to pay shops[] : { "shopId": "cfb6…", "shopName": "My Shop", "itemIds": ["…"], "type": "DELIVERY", "chosenBy": "BUYER", "quotationId": "3c1f…", "selected": { "optionId": "o-1", "businessId": "c4bc…", "name": "Dar Cargo", "logoUrl": "…", "reason": "BUYER_CHOICE", "cost": { "total": 7000 }, "eta": { "…": "…" }, "offer": { "listPrice": 7000, "price": 7000, "buyerPays": 3500, "shares": [ … ] } }, "pricesHeldUntil": "2026-10-25T14:30:00+03:00", "notice": { "code": "NO_ADDRESS", "message": "Add a delivery address to see delivery prices" }, "nudge": { "campaignId": "…", "funder": "SELLER", "kind": "FREE", "value": null, "addMore": 4995.05, "message": "Add 4,995 more from My Shop for free delivery" } } Behaviour Reading the cart refreshes expired or outdated quotations. No address → pickup + "Add a delivery address…"; an address nobody covers → pickup + "Delivery is not available to X yet". A pickup the buyer chose never changes by itself; a pickup set by default turns into delivery once a shipper covers. chosenBy: "BUYER" ↔ selected.reason: "BUYER_CHOICE" now always agree. A shipper picked on an earlier cart shows with the option label USED_BEFORE (not selected). (§49) selected.offer (delivery campaigns, campaigns doc): show buyerPays , not cost.total . deliveryTotal and the change messages count what the buyer pays. nudge appears only while a campaign's minimum order is not reached; it follows the ticked items. New items come in ticked; adding more of an unticked item ticks it again. DELETE /cart/clear keeps destination . FIX : after a paid cart checkout the bought quantities now leave the cart (before: nothing was removed). Units added after the checkout stay; unticked items stay. App to do Group the cart by shops ; show each shop's choice, notice , nudge , and "Used before" for USED_BEFORE . A checkbox per item, per shop and "select all" (§3.4); the pay button shows cartSummary.selectedTotal . After a cart payment, re-read the cart instead of clearing it locally. 3.4 NEW Tick / untick cart items Purpose : choose which cart items the next checkout buys. Endpoint : PUT {base_url}/e-commerce/cart/selection Access Level : 🔒 Protected (cart owner) Authentication : Bearer token Request JSON Sample : { "itemIds": ["a1b2…", "c3d4…"], "selected": false } Request Body Parameters : Parameter Type Required Description Validation itemIds array of uuid Yes one item, a whole shop's items, or every item items of my cart selected boolean Yes tick ( true ) or untick ( false ) Success Response : the same body as GET /e-commerce/cart (§3.3). Errors : unknown item → 404 "Cart item not found". Rules : cart checkout ( REGULAR_CART ) takes ticked items only; nothing ticked → "Tick at least one item to check out." A shop with nothing ticked leaves shops and keeps its pickup / delivery choice for when it is ticked again. Reference : §48 App to do : Checkbox per item, per shop, "select all" → one call each. 4. Checkout & payment What changed in this service : every shop with physical items in a checkout is PICKUP or DELIVERY . The fake address and DHL method are gone; fulfillment replaces them. Delivery has a real price from a real shipper, held from the quotation; if the price ran out and changed, checkout refuses instead of charging a new price silently. Group purchases and "ships now" installments can now be delivered. The payment response finally separates the seller's money from delivery. 4.0 Flow — before and after Before flowchart LR A[Create session
shippingAddressId + shippingMethodId: fake] --> B[Shipping = 0] --> C[Pay: one amount] After flowchart LR A[Create session
fulfillment: per shop PICKUP / DELIVERY
+ dropoff + chosen option] --> B{Held price still valid?} B -- yes --> C[Pay: items part + delivery part per shop] B -- changed / gone / campaign ended --> D[409 with before → after
re-quote, PATCH, pay] D --> C What the user notices : the buyer picks pickup or delivery per shop, an address, who receives, a note; the total includes delivery; if a price moved they see "was X, now Y" and confirm. 4.1 Endpoints in this service Change Method Endpoint One line App BREAKING POST /e-commerce/checkout-sessions fulfillment replaces shippingAddressId / shippingMethodId must update BREAKING PATCH /e-commerce/checkout-sessions/{id} same must update BREAKING GET checkout session response fulfillment replaces shippingAddress / shippingMethod ; real delivery in pricing must update CHANGED POST /e-commerce/checkout-sessions/{id}/process-payment , /retry-payment new refusals; deliveryAmount + parts should update FIX POST same sellerAmount no longer includes the shipper's money check 4.2 BREAKING Create / update a checkout session — fulfillment Endpoint : POST {base_url}/e-commerce/checkout-sessions · PATCH {base_url}/e-commerce/checkout-sessions/{id} Change BREAKING — two fields removed; fulfillment required when the session has physical items Impact Must update before the deploy Reference §11c, §33, §55, §56 In one line : tell the backend, per shop, pickup or delivery — and where, who receives, and which shipper option. Request Field Before After Note shippingAddressId fake: any uuid gave "John Doe, 123 Main Street" removed shippingMethodId fake: always "DHL, Standard Shipping, 0" removed fulfillment.byShop — added shopId → PICKUP | DELIVERY , one per shop with physical items fulfillment.deliveries — added shopId → { quotationId, optionId } — the option from the sheet / cart; missing → today's default shipper fulfillment.dropoff — added required if any shop is DELIVERY — exactly one of addressId / recentId / lat + lng (+ saveAs , customLabel , directions ) fulfillment.receiver — added , optional { name, phone } ; omit = me fulfillment.note — added , optional this order only, max 300 Before After { "sessionType": "REGULAR_DIRECTLY", "items": [ { "productId": "5f0e…", "quantity": 1 } ], "shippingAddressId": "1111…", "shippingMethodId": "2222…" } { "sessionType": "REGULAR_DIRECTLY", "items": [ { "productId": "5f0e…", "quantity": 1 } ], "fulfillment": { "byShop": { "cfb6…": "DELIVERY" }, "deliveries": { "cfb6…": { "quotationId": "3c1f…", "optionId": "o-1" } }, "dropoff": { "addressId": "9b1d…", "directions": "Blue gate" }, "receiver": { "name": "Mama Json", "phone": "0712345678" }, "note": "Call when you arrive" } } Behaviour Digital-only checkouts need no fulfillment . Cart checkout ( REGULAR_CART ) may send no fulfillment : pickup / delivery, the address and the held shipper come from the cart. Sending fulfillment.dropoff overrides the cart's address. Only ticked items are bought. Group purchase accepts DELIVERY (same shape). The group's member totals count items + tax only; each member's delivery is on their own session / order. (§55) Installment : a plan that ships after the down payment ( IMMEDIATE ) accepts DELIVERY — pricing = down payment + the whole delivery. A layaway plan ( AFTER_PAYMENT ) refuses delivery at checkout: LAYAWAY_DELIVERY_LATER . (§56) A new pin becomes a recent place (and a saved / recent one is marked used) only after payment ; saveAs saves the address at once. The fulfillment is frozen when the session is created — later edits to the shop location or the address never change it. Errors Code Status When Before / After FULFILLMENT_REQUIRED 409 a shop with physical items has no PICKUP / DELIVERY choice NEW DROPOFF_REQUIRED 409 a shop is DELIVERY but no dropoff NEW DROPOFF_INVALID 409 not exactly one of addressId / recentId / pin, or it isn't yours NEW RECEIVER_INCOMPLETE 409 receiver name without phone, or the reverse, or a bad phone NEW DELIVERY_NOT_AVAILABLE 409 DELIVERY to an address no shipper covers NEW SHOP_LOCATION_REQUIRED 409 a physical product from a shop with no location (any session type) NEW SHOP_CLOSED 409 an item from a shop paused with STOP_ORDERS, suspended or closed; details { shopId, shopName, reopensAt } NEW — before, checkout never checked the shop DELIVERY_PRICE_CHANGED / DELIVERY_OPTION_GONE 409 the held price ran out and changed / the shipper can't take it now; details { shopId, businessId, businessName, before, after } NEW LAYAWAY_DELIVERY_LATER 409 DELIVERY on a layaway installment NEW OUTSIDE_COVERAGE , LABEL_ALREADY_USED , ADDRESS_LIMIT_REACHED 409 from a saveAs pin NEW App to do Per shop "Pickup at shop / Delivery"; when any delivery: saved address / recent / new pin (+ "Save as"), receiver "Me / Someone else", optional note. Send fulfillment (with deliveries from the sheet / cart); for cart checkout it can be left out. Stop sending shippingAddressId / shippingMethodId . On DELIVERY_PRICE_CHANGED / DELIVERY_OPTION_GONE : show before → after, get a fresh quotation (sheet: ask again; cart: re-read the cart), retry. 4.3 BREAKING Checkout session response — fulfillment and real delivery prices Endpoint : the checkout session (create, get, patch). Change BREAKING — two fake blocks removed Impact Must update Reference §1, §11c, §33, §53, §65 Response Field Before After Note shippingAddress fake John Doe block removed shippingMethod fake DHL block removed fulfillment — added shopId → that shop's fulfillment (below) pricing.shippingByShop — added shopId → delivery , DELIVERY shops only, what the buyer pays pricing.shippingCost , total shipping always 0 real sum of shippingByShop The fulfillment object (same on the session, the order and the installment agreement): { "type": "DELIVERY", "pickup": { "source": "SHOP_LOCATION", "label": "Yoyo Test Shop", "lat": -6.7889, "lng": 39.2083, "areaLabel": "Ubungo, Ubungo, Dar es Salaam", "phone": "+255700111222", "…": "…" }, "dropoff": { "source": "SAVED_ADDRESS", "label": "Home", "lat": -6.77, "lng": 39.25, "areaLabel": "Mikocheni, Kinondoni, Dar es Salaam", "directions": "Blue gate", "…": "…" }, "receiver": { "name": "Mama Json", "phone": "+255712345678" }, "note": "Call when you arrive", "delivery": { "quotationId": "3c1f…", "optionId": "o-1", "businessId": "c4bc…", "businessName": "Dar Cargo", "total": 7000, "minMinutes": 120, "maxMinutes": 240, "arrivalFrom": "…", "arrivalTo": "…", "pricesHeldUntil": "…", "route": null, "offer": { "listPrice": 7000, "price": 7000, "buyerPays": 3500, "shares": [ … ] } } } dropoff and delivery are absent for PICKUP. delivery.total is what the shipper is paid; offer shows who paid the rest (campaigns). route is set for station routes (shipping doc §1.8). 4.4 CHANGED Process / retry payment Endpoint : POST {base_url}/e-commerce/checkout-sessions/{id}/process-payment · /retry-payment Change CHANGED + FIX Impact Should update Reference §33, §34, §47, §65, §67 Response Field Before After Note sellerAmount every held part added together (seller + shipper) the seller's share only FIX deliveryAmount — added the delivery parts' full value parts — added [{ escrowId, escrowNumber, part: ITEMS | LEG, orderId, amount }] — one per held part, every shop's order escrowId , escrowNumber , orderId , amountPaid unchanged Before (wrong) After (correct) { "escrowId": "e1…", "amountPaid": 16000.99, "sellerAmount": 16000.99 } { "escrowId": "e1…", "amountPaid": 16000.99, "sellerAmount": 1000.99, "deliveryAmount": 15000, "parts": [ { "part": "ITEMS", "amount": 1000.99, "orderId": "…", "…": "…" }, { "part": "LEG", "amount": 15000, "orderId": "…", "…": "…" } ] } Behaviour The money is held per shop in parts: ITEMS (the seller; platform fee on items + tax only) and LEG (the shipper, one per leg). A free delivery has no LEG part. Buyer transaction history for a split payment reads "N parts" instead of "N sellers". Errors Code Status When Before / After DELIVERY_OPTION_GONE 409 the chosen shipper stopped taking jobs after the session was made — the session stays open: change the shipper with PATCH , pay NEW CAMPAIGN_ENDED 409 a delivery campaign ended / ran out since checkout; details { shopId, businessId, businessName, before, after } . Nothing charged: PATCH the session (same pick) for today's price, then pay NEW DELIVERY_PRICE_CHANGED 409 now also when only the buyer's part changed CHANGED App to do Show items from sellerAmount and delivery from deliveryAmount ; list each shop's order from parts . Handle CAMPAIGN_ENDED like DELIVERY_PRICE_CHANGED (before → after, PATCH, pay). 5. Orders What changed in this service : every physical order has a fulfillment — pickup or delivery — with its own status ladder, steps and times. Pickup orders and orders a shipper delivers no longer use the old ship / confirm-delivery / regenerate-code endpoints: they use the shipping order actions (shipping doc §C). The delivery code is 4 digits, comes by its own SMS and lasts 24 h. A buyer's cancel follows clearer rules. 5.0 Flow — before and after Before flowchart LR A[Paid] --> B[Seller: /ship
fake tracking number] --> C[Buyer types 6-digit code:
/confirm-delivery] --> D[Money to seller] After — pickup flowchart LR A[PLACED] --> B[Seller accepts
ACCEPTED] --> C[Packed & ready
READY + pickup code to buyer] --> D[Seller enters buyer's code
COLLECTED] --> E[Buyer: It's what I ordered
COMPLETED, money released] After — delivery flowchart LR A[PLACED] --> B[ACCEPTED] --> C[READY
job offer to shipper] --> D[SHIPPER_ACCEPTED] --> E[HEADING_TO_SHOP] --> F[Hand-over at the shop
ON_THE_WAY, code to receiver] --> G[Rider enters code
DELIVERED] --> H[Buyer confirms
COMPLETED: seller + shipper paid] What the user notices : order screens show where the parcel is, the shipper's name and phone, "Code sent by SMS · valid until …", and buttons that depend on the step (Accept, Ready, Hand over, Collect, "It's what I ordered", Report a problem). 5.1 Endpoints in this service Change Method Endpoint One line App BREAKING GET /e-commerce/orders/{orderId} (buyer + seller) deliveryAddress / trackingNumber / carrier → fulfillment must update CHANGED GET order lists (buyer /my-orders… , shop /shop/{shopId}/orders… ) gain fulfillmentType , fulfillmentStatus should update BREAKING POST /orders/{orderId}/ship , /confirm-delivery , /regenerate-code refused for pickup orders and shipper orders; 4-digit code must update CHANGED POST /orders/{orderId}/regenerate-code "I lost my code": new fields, code shown once should update CHANGED POST /orders/{orderId}/cancel pickup / shipper orders follow the new cancel rules; installments refused should update CHANGED — shippingFee , totalAmount on an order can change after payment check New, documented elsewhere: every pickup / delivery order action under /shipping/orders/{orderId}/… (accept, ready, pickup-time, code, hand-over, hand-over-rider, collect, confirm, report-problem, delivery-options, switch-to-delivery, change-destination, switch-to-pickup, delivery-decision, cancel) → shipping doc §C. 5.2 BREAKING Order detail and lists — fulfillment Endpoint : GET {base_url}/e-commerce/orders/{orderId} and the order lists Change BREAKING — three fields removed Impact Must update Reference §11c, §24, §26, §37–§41, §45, §53, §59, §63 Response Field Before After Note deliveryAddress , trackingNumber , carrier text / fake removed fulfillment — added (detail) the object from §4.3 plus status, steps and times (below) fulfillmentType — added (lists) PICKUP / DELIVERY , null for digital fulfillmentStatus — added (lists) productOrderStatus kept still works; for pickup / shipper orders it follows the fulfillment steps Before After { "orderNumber": "ORD-2026-94070", "productOrderStatus": "SHIPPED", "deliveryAddress": "123 Main Street", "trackingNumber": "TRACK-8F2A", "carrier": "DHL" } { "orderNumber": "ORD-2026-94070", "productOrderStatus": "SHIPPED", "fulfillment": { "type": "DELIVERY", "status": "ON_THE_WAY", "parcelNumber": "NX-48213", "placedAt": "…", "acceptedAt": "…", "readyAt": "…", "pickedUpAt": "…", "code": { "status": "ACTIVE", "expiresAt": "2026-10-26T09:12:00" }, "shippers": [ { "leg": 1, "name": "Dar Cargo", "phone": "+255745051250" } ], "delivery": { "businessName": "Dar Cargo", "total": 7000, "…": "…" }, "decision": null, "needsShipperSince": null, "…": "…" } } fulfillment keys added beyond §4.3 Key Meaning status pickup: PLACED → ACCEPTED → READY → COLLECTED → COMPLETED ; delivery: PLACED → ACCEPTED → READY → SHIPPER_ACCEPTED → HEADING_TO_SHOP → ON_THE_WAY → (AT_STATION) → DELIVERED → COMPLETED ; or CANCELLED / EXPIRED parcelNumber , pickupTime parcel number from Accept; buyer's planned pickup time placedAt , acceptedAt , readyAt , collectedAt , headingAt , pickedUpAt , deliveredAt , completedAt , closedAt step times, absent until set acceptBy , collectBy deadlines (24 opening hours to accept; 14 days to collect) closeReason e.g. NOT_ACCEPTED_IN_TIME , NOT_COLLECTED_IN_TIME , ADMIN_REFUNDED , ADMIN_CANCELLED code { status: ACTIVE | USED | LOCKED | EXPIRED | REPLACED, expiresAt } — detail only, never the code itself packedConfirmedAt , labelConfirmedAt the seller's Ready ticks needsShipperSince the shipper said no and a new one is being found decision the buyer must choose (new shipper costs more, nobody found, …) — shipping doc §1.11 refusal the latest shipper refusal at the shop ( TOO_BIG / BADLY_PACKED ) shippers one per leg whose shipper accepted: { leg, businessId, name, phone } (not for Bolt) rider Bolt only: driverName , driverPhone , driverPicture , plate , vehicleModel , trackingUrl , etaToShopMinutes , state Behaviour Existing orders were copied over: PENDING_SHIPMENT → PLACED , SHIPPED / DELIVERED / DISPUTED → READY , COMPLETED → COMPLETED , CANCELLED / REFUNDED → CANCELLED . Automatic closing: a pickup or delivery order not accepted within 24 opening hours is cancelled; a pickup not collected 14 days after Ready becomes EXPIRED . Both refund in full; the order shows CANCELLED with no cancelledBy . shippingFee and totalAmount can change after payment (another shipper at another price; 0 after switching to pickup). shippingFee is what the buyer paid for delivery. For now the seller sees the full drop-off (pin, directions, receiver phone). App to do Read fulfillment instead of deliveryAddress / trackingNumber / carrier ; pickup orders show the shop's place + Directions. Show fulfillment.status and the step times; lists show the fulfillmentType badge and fulfillmentStatus . A Call button per entry in shippers ; a rider card (photo, plate, Track) for Bolt. 5.3 CHANGED Cancel an order Endpoint : POST {base_url}/e-commerce/orders/{orderId}/cancel Change CHANGED — rules for pickup orders and orders a shipper delivers Impact Should update Reference §25, §34, §43, §69 Who Before After (pickup orders and shipper orders) Buyer any time before shipped only before the seller accepts ( PLACED ), or while the delivery has no shipper ( needsShipperSince / a decision waiting) Seller any time before delivered pickup: until collected; delivery: only before Ready. Body { "informedBuyer": true, "reason"? } Refund the order's single escrow every part (items + delivery), stock back Old orders without a shipper keep the old rules. Errors Code Status When Before / After CANCEL_NOT_ALLOWED 409 after those windows ("You can switch to pickup instead" / "Contact support to cancel") NEW (was FULFILLMENT_STATE ) INFORM_BUYER_FIRST 409 seller without informedBuyer: true NEW INSTALLMENT_ORDER 409 an installment order — cancel the agreement instead NEW App to do Buyer: show Cancel only while PLACED or while "We're finding a new shipper" / a decision shows; otherwise "Collect it myself instead". Seller: hide Cancel on delivery orders from Ready on; send informedBuyer . Installment order: the Cancel button calls the agreement cancel (§6.2). 5.4 BREAKING Old ship / confirm-delivery / regenerate-code Endpoint : POST {base_url}/e-commerce/orders/{orderId}/ship · /confirm-delivery · /regenerate-code Change BREAKING — refused for most new orders Impact Must update Reference §25, §40 Order Before After Pickup order seller ships, buyer confirms with the code 409 USE_PICKUP_FLOW — use the shipping order actions Order a shipper delivers — 409 SHIPPER_DELIVERS Old order without a shipper unchanged (old flow) POST /ship no longer takes or returns trackingNumber (it was a fake "TRACK-…"). App to do For pickup and shipper orders, stop calling these three; use the buttons from shipping doc §C. 5.5 BREAKING Delivery code — 4 digits, own SMS, 24 h, "I lost my code" Endpoint : /orders/{orderId}/ship , /confirm-delivery , /regenerate-code (old orders) — and the same code rules for the shipping …/code action. Change BREAKING Impact Must update Reference §20, §26 Before After Length 6 digits 4 digits ( confirmationCode must match ^[0-9]{4}$ ) Lifetime 30 days 24 hours ; a new code kills the old one; max 5 per order per 24 h Delivery of the code inside the "Order shipped" email / SMS / push its own SMS to the receiver; the bell says "Your delivery code was sent by SMS", never the code Wrong code HTTP 400 with prose 409 coded refusal, details.triesLeft Old codes gone — a buyer with a SHIPPED order taps "I lost my code" POST /regenerate-code response — fields added (old ones stay): Before After { "orderId": "…", "orderNumber": "ORD-2026-94070", "codeSent": true, "destination": "email", "codeExpiresAt": null, "maxAttempts": 5 } { "orderId": "…", "orderNumber": "ORD-2026-94070", "codeSent": true, "destination": "sms", "codeExpiresAt": "2026-10-18T05:03:05", "maxAttempts": 5, "message": "New code sent by SMS to +255*****2944", "code": "4821", "sentTo": "+255*****2944" } code is shown once , on that screen; it is never stored readable, so it cannot be shown again. Errors Code Status When Before / After CODE_WRONG 409 wrong code; details.triesLeft NEW (was 400) CODE_LOCKED 409 after 5 wrong tries — even the right code, until a new one is asked for NEW CODE_NOT_ACTIVE 409 no active code, or already used NEW CODE_EXPIRED 409 older than 24 h NEW CODE_LIMIT 409 more than 5 new codes in 24 h NEW App to do 4-digit input; show triesLeft ; on CODE_LOCKED / CODE_EXPIRED offer "I lost my code". Order screen: "Code sent by SMS to +255•••2944 · valid until …" from fulfillment.code . "I lost my code" → show code big, once. Never cache it. 6. Installments What changed in this service : the agreement carries the fulfillment like an order; "ships now" plans can be delivered; installment orders are cancelled only through their agreement; and two money bugs are fixed. Change Method Endpoint One line App CHANGED GET installment agreement shippingAddress → fulfillment should update CHANGED POST /e-commerce/installments/agreements/{id}/cancel also cancels the order and refunds every part no change FIX POST /installments/agreements/{id}/payments/{paymentId}/pay instalments after the down payment can be paid no change FIX GET / POST /installments/agreements/{id}/early-payoff no "/ by zero" on 0 % plans no change FIX — confirming delivery releases every instalment held so far to the seller no change 6.1 CHANGED Agreement — fulfillment , layaway, cancel Field / rule Before After shippingAddress always empty removed fulfillment — added (same object as orders) Layaway ( AFTER_PAYMENT ) refuses delivery at checkout ( LAYAWAY_DELIVERY_LATER ); when paid off the order is pickup and the buyer may choose delivery from the order INSTALLMENT_AGREEMENT_COMPLETED gains nextStep ("Collect it at the shop, or choose delivery in your order." for layaway) Agreement cancel when the order exists it is cancelled too and everything refunded (down payment, delivery, instalments) — refused once the parcel left the shop Order cancel on an installment order allowed (left the agreement ACTIVE) refused INSTALLMENT_ORDER everywhere Reference : §17, §56, §69 6.2 FIX Installment money Before (wrong) After (correct) every instalment after the down payment failed to pay (database error) pays normally confirming delivery released only the down payment + delivery; held instalments stayed held for good every instalment held so far is released; one paid after confirmation goes to the seller at once early payoff on a 0 % plan crashed "/ by zero" works a "ships now" plan sometimes never got its order always gets it Reference : §56, §57, §70, §71 App to do Offer pickup / delivery in the installment sheet for "ships now" plans; layaway: "choose delivery after the last payment". Installment order: Cancel = POST /e-commerce/installments/agreements/{agreementId}/cancel . 7. Group purchase 7.1 CHANGED Group checkout with delivery Rule Before After GROUP_PURCHASE with DELIVERY not possible accepted — byShop , deliveries , dropoff like a direct buy Member totals ( totalPaid , amountPaid ) items + tax only — the same for every member; each member's delivery is on their own session / order Leaving ( POST /group-purchases/{id}/withdraw ) / group runs out of time refund the items refund items and delivery Moving a payment to another group keeps its delivery choice Changing the delivery while the group fills not possible; once the order exists the usual changes apply Reference : §55 App to do The group checkout sheet offers pickup / delivery like a direct buy; the group screen keeps showing the group price. 8. Wallet & transactions 8.1 CHANGED Transaction history titles Where Before After Buyer, split payment "N sellers" "N parts" Shipper payout — "Delivery Earnings" ("Payment received for delivery"); type still SALE Extra delivery payment after ordering — "Delivery Payment" Money back (cheaper shipper, switch to pickup) — "Refund Received" Known, not changed: a refund also writes "Sale Refund Issued" (debit) into the payee's history although the payee never received that money. Reference : §34, §40, §41 9. Events What changed in this service : a venue is a pin turned into ward / district / region (like a shop); an in-person event needs that pin to publish. Donation tickets can have a target and show progress. Change Method Endpoint One line App CHANGED PATCH /e-events/drafts/{draftId}/location pin outside Tanzania → OUTSIDE_COVERAGE should update BREAKING PATCH /e-events/{eventId}/publish , /e-events/{eventId}/published/reveal-location in-person / hybrid needs a pin must update CHANGED GET event responses venue gains ward , areaLabel , matchType should update CHANGED POST / PUT / PATCH ticket create / draft update / published update optional donationTarget to use CHANGED GET ticket details, ticket list, event tickets donation progress on donation tickets should update 9.1 CHANGED Venue — area from the pin Endpoint : PATCH {base_url}/e-events/drafts/{draftId}/location · event responses Field Before After request venue unchanged; venue.coordinates outside Tanzania's wards → OUTSIDE_COVERAGE response venue.ward , venue.areaLabel , venue.matchType — added (null without a pin) locationSummary , share preview, ticket / booking location, event on a post or clip typed address the area when no address was typed "venue": { "name": "Mlimani City Hall", "address": null, "coordinates": { "latitude": -6.77, "longitude": 39.22 }, "areaLabel": "Mikocheni, Kinondoni, Dar es Salaam", "matchType": "CONTAINED", "ward": { "…": "…" } } Reference : §78 9.2 BREAKING Publish / reveal location needs a pin Endpoint : PATCH {base_url}/e-events/{eventId}/publish · {base_url}/e-events/{eventId}/published/reveal-location Before After an IN_PERSON / HYBRID event published with a venue name only refused 422 "Drop a pin on the map for the venue" ( details.stage LOCATION_DETAILS ) without coordinates TBA and online events unchanged; drafts can still be saved without a pin. App to do Venue step: the same pin screen as the shop location (resolve, show areaLabel , Confirm off when not covered); the address field stays optional ("directions"). 9.3 CHANGED Donation target and progress Endpoint : POST {base_url}/e-events/tickets/{eventId} · PUT {base_url}/e-events/tickets/{ticketId} · PATCH {base_url}/e-events/tickets/{ticketId}/published Change CHANGED — optional field in, optional block out Impact To use (the progress bar) Reference §79 Request Field Before After Note donationTarget — added , optional > 0, at most 10 digits + 2 decimals; donation tickets only Response (ticket details, ticket list, tickets in the event response) Field Before After Note donation — added { target, raised, donations } on donation tickets; null on others; target null when not set Before After { "name": "Support the choir", "ticketPricingType": "DONATION", "price": 0.00 } { "name": "Support the choir", "ticketPricingType": "DONATION", "price": 0.00, "donation": { "target": 500000.00, "raised": 210000.00, "donations": 11 } } raised = confirmed donations only (cancelled / refunded bookings do not count). The organiser can set or raise the target after publishing. Errors Message Status When "A donation target can only be set on a donation ticket" 422 donationTarget on a paid / free ticket App to do Organiser: optional "Target" field on donation tickets. Event page: progress bar from donation.raised / donation.target (hide the bar when target is null; still show raised ). 10. Profile & account 10.1 CHANGED Profile cover photo Endpoint : PUT {base_url}/profile/update · GET {base_url}/profile/me , /profile/id/{userId} , /profile/u/{username} Change CHANGED — field in, field out Impact To use Reference §79 Where Field Before After PUT /profile/update coverPhotoFileId — added , optional — a File Thunder upload with context COVER_PHOTO profile responses coverMedia — added — same shape as profileMedia ( fileId , status , variants , mimeType ), null when not set Before After { "userName": "joshuasakweli", "profileMedia": { "fileId": "…", "status": "READY", "variants": { "thumb": "https://…/thumb.webp" } } } { "userName": "joshuasakweli", "profileMedia": { "fileId": "…", "status": "READY", "variants": { "thumb": "https://…/thumb.webp" } }, "coverMedia": { "fileId": "…", "status": "READY", "variants": { "medium": "https://…/medium.webp", "large": "https://…/large.webp" } } } Errors : a file uploaded with another context → "File was not uploaded as a cover photo"; someone else's file → 403. App to do Profile banner: upload as COVER_PHOTO , send coverPhotoFileId , show coverMedia . 10.2 CHANGED New-device sign-in alert AUTH_NEW_DEVICE_LOGIN is now sent by password, SMS-code and Google sign-in (was password only) and never for an account's first device. Its data gains the details the app can show in the bell item: Key Before After device.name , device.platform , ipAddress yes unchanged device.type — Phone / Tablet / Computer device.browser — e.g. Chrome , Safari , NexGate app device.os — Windows , macOS , iOS , Android , Linux , ChromeOS device.location , location — e.g. "Dar es Salaam, TZ" ( "Unknown location" when not found) timestamp — e.g. "Oct 25, 2026 at 8:15 AM EAT" Browser and system come from the User-Agent header — no new request fields. Email, push and SMS now show them. Reference : §79 11. Interests 11.1 CHANGED Category management is admin only Endpoint Before After POST /interests/categories , PUT /{categoryId} , DELETE /{categoryId} , PATCH /{categoryId}/toggle-active , GET /{categoryId}/usage any signed-in user SUPER_ADMIN / STAFF_ADMIN only (403 for others) GET /interests/categories/all public unchanged Picking interests (unchanged, for the onboarding screen): load GET /interests/categories/all , then save at least 3 of those ids with POST /onboarding/secondary/interests { "interestIds": [ … ] } . One unknown or switched-off id → 404 "Some categories are inactive or do not exist" and nothing is saved — always send ids from the list you just loaded. The categories are created by the system at startup. Reference : §79 12. Chat 12.1 FIX Chat avatars and group icons are real links Endpoint : GET {base_url}/chat/conversations ( display.avatar ) · group responses ( iconMedia : create, get, PUT /chat/groups/{conversationId}/icon ) Before (wrong) After (correct) variants held storage paths — "thumb": "messages/4a8c…/thumb.webp" — which no image can load "thumb": "https://…/thumb.webp" — full links; same object shape Applies to groups, people and shops in the chat list. An icon set while its upload was still processing now upgrades to its final images (it stayed stuck before). A failed icon upload now says "Upload failed" instead of "still in progress". The chat doc said avatar was a plain link string — it is the object { fileId, status, mimeType, variants } . Reference : §80 App to do Read the image from display.avatar.variants.thumb / iconMedia.variants.thumb (no prefixing). On CHAT_GROUP_UPDATED (§15) refresh the chat list row and the group header. 13. Digital downloads 13.1 FIX Files keep their name and type Endpoint : GET {base_url}/e-commerce/digital-files/my-downloads/{accessId}/url — then the app downloads the returned url Before (wrong) After (correct) the file arrived named original , type application/octet-stream — devices could not open it, and reading it as text corrupted it (and roughly doubled its size) the link answers Content-Disposition: attachment; filename="notes pedi.zip" and the real Content-Type ( application/zip , video/mp4 , …) Response body unchanged ( url , expiresInSeconds ). Works for files already uploaded. Reference : §80 App to do Download the url as bytes and stream it to a file; never read the body as text / string. Use the filename from Content-Disposition (or fileName from the order downloads list). 14. Admin dashboard (existing screens) Change Method Endpoint One line NEW PUT /admin/shops/reviews/{reviewId}/status moderate a shop review: { "status": "HIDDEN", "reason": "…" } (SUPER_ADMIN, reason required, audit-logged) → { reviewId, shopId, status } CHANGED GET fees, current + history lists a new fee type DELIVERY_COMMISSION (PERCENTAGE) CHANGED GET /api/admin/reconciliation/position gains owedToProviders , campaignMoney , ownerTopUps ; expectedSelcomFloat includes them CHANGED GET /api/admin/reconciliation/trial-balance new lines PROVIDER , CAMPAIGN_BUDGET , CAMPAIGN_POT CHANGED — interest categories management is admin only (§11) Reference : §30, §31, §58, §64 New admin endpoints for shipping, Bolt, campaigns, the operations board, reports and the audit log → shipping doc §K and campaigns doc §7–§8. 15. Notifications New types are listed so the bell list and push taps can handle them; each opens the screen in its targetType / targetId . Four new targets : SHIPPING_JOB , SHIPPER_BUSINESS , SHIPPING_CAMPAIGN , ADMIN_SHIPPING_BOARD — an app that does not know a target yet opens the bell list. Change Type To When Opens ( targetType ) App NEW HANDOVER_CODE receiver a pickup / delivery code was sent (in-app + SMS; never the code in-app) ORDER_TRACKING bell item NEW ORDER_ACCEPTED buyer seller accepted (parcel number) ORDER_TRACKING refresh the order NEW ORDER_ACCEPT_REMINDER seller 2 h before the accept deadline SHOP_ORDER NEW PICKUP_READY buyer pickup order ready (place, collect-by) ORDER_TRACKING NEW PICKUP_REMINDER buyer not collected: day 7 and 12 ORDER_TRACKING NEW PICKUP_LAST_DAY buyer (+ SMS) 24 h before the collect deadline ORDER_TRACKING NEW SHIPPER_FOUND buyer every shipper accepted / Bolt rider assigned ORDER_TRACKING refresh NEW SHIPPER_CHANGED buyer automatic swap to an equal or cheaper shipper ORDER_TRACKING refresh NEW DELIVERY_DECISION_NEEDED buyer the buyer must choose (costs more, nobody found, …) ORDER_TRACKING open the decision NEW PARCEL_AT_STATION buyer the parcel reached the buyer's station ORDER_TRACKING NEW RIDER_COMING seller (+ SMS) shipper heading to the shop / Bolt rider (plate to check) SHOP_ORDER NEW PARCEL_REFUSED seller badly packed / Bolt left without the parcel SHOP_ORDER refresh NEW JOB_OFFER shipper a new job offer SHIPPING_JOB NEW JOB_CANCELLED shipper an offer or accepted job was taken away SHIPPING_JOB refresh NEW DELIVERY_EARNINGS shipper delivery money released TRANSACTION refresh NEW SHIPPER_NEW_REVIEW shipper new review SHIPPER_BUSINESS NEW SHIPPER_REVIEW_REPLY reviewer the shipper replied (in-app) SHIPPER_BUSINESS NEW SHIPPER_STANDING shipper owner warning / automatic pause / suspension SHIPPER_BUSINESS banner NEW SHIPPER_DOCUMENT_EXPIRING shipper owner a KYC document expires in 14 / 3 days SHIPPER_BUSINESS NEW SHIPPING_CAMPAIGN_RAN_OUT campaign runner a campaign ran out SHIPPING_CAMPAIGN NEW SHIPPING_CAMPAIGN_ENDED campaign runner a campaign ended (what it paid) SHIPPING_CAMPAIGN NEW SHIPPING_ADMIN_DIGEST staff + super admins 07:30 when anything is stuck ADMIN_SHIPPING_BOARD admin app NEW CHAT_GROUP_UPDATED other group members (in-app only) the group photo changed; data conversationId , group.name , change: "ICON" CONVERSATION refresh the chat row + header CHANGED ORDER_AWAITING_CONFIRM buyer now sent when a delivery is delivered : "tap Received so the seller gets paid" ORDER CHANGED ORDER_DELIVERED was: buyer at completion → now: seller at delivery ("You are paid when they confirm") SHOP_ORDER move the handler CHANGED ORDER_SHIPPED buyer no carrier, tracking number or code; no SMS ORDER_TRACKING CHANGED AUTH_NEW_DEVICE_LOGIN user all sign-in methods; new data keys (§10.2) ACTIVE_SESSIONS show the details CHANGED INSTALLMENT_AGREEMENT_COMPLETED buyer gains nextStep for layaway AGREEMENT FIX ESCROW_RELEASED_TO_SELLER seller was blank (" released to your wallet / From order "); now amount + order number TRANSACTION FIX ESCROW_REFUNDED , ORDER_CANCELLED buyer blank amount / order number / reason filled FIX DIGITAL_PRODUCT_READY buyer said only " is ready"; now the product name ("Notes Pedi and 2 more") and days the link lasts; sent only after access exists DOWNLOAD FIX order "delivered" to the buyer buyer crashed when the buyer had no email 16. Error codes index All HTTP 409, data.code ; some carry data.details . Code Change Service Endpoint(s) Meaning SHOP_LOCATION_REQUIRED NEW Checkout create session a physical product from a shop with no location SHOP_CLOSED NEW Checkout create session shop paused (stop orders), suspended or closed; details.reopensAt FULFILLMENT_REQUIRED / DROPOFF_REQUIRED / DROPOFF_INVALID NEW Checkout create / patch session missing choice / missing drop-off / not exactly one drop-off source RECEIVER_INCOMPLETE NEW Checkout create / patch session receiver name and phone go together DELIVERY_NOT_AVAILABLE NEW Checkout create session no shipper covers the address DELIVERY_PRICE_CHANGED / DELIVERY_OPTION_GONE NEW Checkout create session, process / retry payment held price changed / shipper can't take it; show before → after CAMPAIGN_ENDED NEW Checkout process payment a delivery campaign ended since checkout LAYAWAY_DELIVERY_LATER NEW Checkout create session (installment) layaway: choose delivery after the last payment HOURS_INVALID NEW Shops PUT /shops/{shopId}/hours a special day twice, or open with no hours SHOP_NOT_PAUSABLE NEW Shops PUT /shops/{shopId}/pause only an ACTIVE (or already paused) shop can be paused OUTSIDE_COVERAGE NEW Checkout, Events saveAs pin, venue pin pin outside Tanzania's wards LABEL_ALREADY_USED / ADDRESS_LIMIT_REACHED NEW Checkout saveAs pin second HOME / WORK; 11th address USE_PICKUP_FLOW NEW Orders ship / confirm-delivery / regenerate-code pickup order — use the shipping actions SHIPPER_DELIVERS NEW Orders ship / confirm-delivery / regenerate-code a shipper delivers this order CODE_WRONG / CODE_LOCKED / CODE_NOT_ACTIVE NEW (was 400) Orders confirm-delivery wrong code ( details.triesLeft ) / locked / none active CODE_EXPIRED / CODE_LIMIT NEW Orders confirm-delivery / regenerate-code older than 24 h / more than 5 in 24 h CANCEL_NOT_ALLOWED NEW (was FULFILLMENT_STATE ) Orders cancel outside the cancel window INFORM_BUYER_FIRST NEW Orders cancel (seller) send informedBuyer: true INSTALLMENT_ORDER NEW Orders cancel cancel the installment agreement instead The codes of the new location, shipping and campaign endpoints are in their docs (location §Part 3, shipping §4.5, campaigns §Part 3). 17. App checklist by screen Screen What to change Sections Shop create / edit (seller) drop location inputs; add the Shop location (pin) step §1.2 Shop page / lists / search location , Directions, Call; availability (open now / paused); Buy off when not accepting §1.3, §1.4 Shop settings (seller) opening hours screen; pause / reopen §1.6, §1.7 Shop reviews show replyText §1.5 Product page "Pickup at …" + Directions from shopLocation §2.2 Product form (seller) "Bulky / heavy item" switch §2.3 Cart group by shops , delivery per shop, notice , nudge , "Used before", ticks, selectedTotal , change messages §3.2–§3.4 Checkout pickup / delivery per shop, drop-off, receiver, note; send fulfillment ; price-changed codes §4.2–§4.4 Payment result sellerAmount + deliveryAmount , parts §4.4 Order detail (buyer + seller) fulfillment status, steps, times, shippers' phones, Bolt rider, code status, "I lost my code" §5.2, §5.5 Order lists fulfillmentType badge, fulfillmentStatus §5.2 Order cancel new windows; installment → agreement cancel §5.3, §6.1 Delivery code entry 4 digits, triesLeft , locked / expired §5.5 Installment sheet / agreement delivery for "ships now"; layaway text; fulfillment §6.1 Group purchase sheet pickup / delivery §7.1 Wallet history new titles §8.1 Event venue step (organiser) pin screen; pin required to publish §9.1, §9.2 Donation ticket (organiser + event page) donationTarget ; progress bar from donation §9.3 Profile cover banner ( COVER_PHOTO , coverMedia ) §10.1 Interests (onboarding) send ids from the loaded list §11.1 Chat list / group header avatars from variants ; refresh on CHAT_GROUP_UPDATED §12.1 Digital downloads download as bytes, keep the filename §13.1 Bell list / push the new types and four new targets §15 Admin dashboard review moderation, DELIVERY_COMMISSION , reconciliation lines, admin-only interests §14