# Home Page API Calls Document for the backend team. Describes every HTTP request the mobile/web client fires when the user opens `/home`, including call order, parameters, and what blocks the UI from rendering. **Context:** Home page load feels slow. The client currently waits on **3 sequential API calls** before showing content. A 4th call runs in the background; a 5th may fire when the user has no "your tenders". **Base URL:** `https://app.opplenz.com` **Auth:** All requests include `Authorization: Bearer `. --- ## Summary | # | Endpoint | Method | Blocking UI? | When | |---|----------|--------|--------------|------| | 1 | `/api/v1/tender-approvals/stats` | GET | **Yes** | Always on home load | | 2 | `/api/v1/tender-approvals?limit=10&offset=0` | GET | **Yes** | Always on home load | | 3 | `/api/v1/feedback?feedback_type=like&limit=1&offset=0` | GET | **Yes** | Always on home load | | 4 | `/api/v1/notifications?seen=false&limit=1&event_type=PUSH` | GET | No (background) | App shell mount (parallel) | | 5 | `/api/v1/tenders/recommend?limit=10&offset=0` | GET | No | Only when call #2 returns an empty tender list | **Minimum calls on first load:** 4 (calls 1–3 + 4) **Maximum calls on first load:** 5 (adds call 5 when user has no your-tenders) **Perceived load time (client-side):** Sum of response times for calls 1 → 2 → 3 (sequential `await` chain). --- ## Call sequence ``` User opens /home │ ├─ [parallel, background] App shell │ └─ GET /api/v1/notifications?seen=false&limit=1&event_type=PUSH │ └─ [blocking] HomeViewModel.init() │ ├─ 1. GET /api/v1/tender-approvals/stats ← await ├─ 2. GET /api/v1/tender-approvals?limit=10&offset=0 ← await │ └─ if tenders[] is empty (fire-and-forget, not awaited): │ GET /api/v1/tenders/recommend?limit=10&offset=0 └─ 3. GET /api/v1/feedback?feedback_type=like&limit=1&offset=0 ← await └─ UI renders (_isLoading = false) ``` Source: `lib/view_models/home_view_model.dart` → `init()` --- ## API details ### 1. Tender approval stats Partnership / self-apply progress rings on the home dashboard. | Field | Value | |-------|-------| | **Method** | `GET` | | **Path** | `/api/v1/tender-approvals/stats` | | **Query params** | None | | **Blocking** | Yes — first call in `init()` | | **Response fields used** | `data.partnership_count`, `data.self_apply_count` | **Example:** ```http GET /api/v1/tender-approvals/stats Authorization: Bearer Accept: application/json ``` --- ### 2. Your tenders (horizontal list) Main tender carousel and "Your Tenders" section. | Field | Value | |-------|-------| | **Method** | `GET` | | **Path** | `/api/v1/tender-approvals` | | **Query params** | `limit=10`, `offset=0` | | **Optional filters** | Not sent from home (`status`, `created_from`, `created_to` are empty) | | **Blocking** | Yes — second call in `init()` | | **Response fields used** | `data.tenders[].tender` (full tender objects), `data.metadata.pages` (pagination) | **Example:** ```http GET /api/v1/tender-approvals?limit=10&offset=0 Authorization: Bearer Accept: application/json ``` **Note:** This is likely the heaviest call — it returns up to 10 full tender objects with nested data. --- ### 3. Liked tenders count "Liked Tenders" stat card count. | Field | Value | |-------|-------| | **Method** | `GET` | | **Path** | `/api/v1/feedback` | | **Query params** | `feedback_type=like`, `limit=1`, `offset=0` | | **Blocking** | Yes — third call in `init()` | | **Response fields used** | `data.meta.total` only (client ignores the single returned item) | **Example:** ```http GET /api/v1/feedback?feedback_type=like&limit=1&offset=0 Authorization: Bearer Accept: application/json ``` **Optimization opportunity:** Client only needs a count. A dedicated count endpoint (or `meta.total` without loading feedback records) would be faster than a full feedback query. --- ### 4. Unread notification check (background) Red dot on the notification tab icon. | Field | Value | |-------|-------| | **Method** | `GET` | | **Path** | `/api/v1/notifications` | | **Query params** | `seen=false`, `limit=1`, `event_type=PUSH` | | **Blocking** | No — fired from app shell `postFrameCallback` | | **Response fields used** | Presence of any result (updates `NotificationStateService`) | **Example:** ```http GET /api/v1/notifications?seen=false&limit=1&event_type=PUSH Authorization: Bearer Accept: application/json ``` Source: `lib/core/routes/app_shell/app_shell_screen.dart` --- ### 5. Recommended tenders (conditional fallback) Shown when the user has no "your tenders". Replaces the horizontal list label with "Recommended Tenders". | Field | Value | |-------|-------| | **Method** | `GET` | | **Path** | `/api/v1/tenders/recommend` | | **Query params** | `limit=10`, `offset=0` | | **Blocking** | No — triggered inside `getYourTenders()` without `await` | | **Condition** | Fires only when call #2 returns `data.tenders` empty | | **Response fields used** | `data.tenders[]` (full tender objects) | **Example:** ```http GET /api/v1/tenders/recommend?limit=10&offset=0 Authorization: Bearer Accept: application/json ``` --- ## Pagination (scroll, not initial load) After the home page renders, scrolling the horizontal tender list near the end triggers additional calls: | Mode | Endpoint | Params | |------|----------|--------| | Your tenders | `GET /api/v1/tender-approvals` | `limit=10`, `offset=` | | Recommended (fallback mode) | `GET /api/v1/tenders/recommend` | `limit=10`, `offset=` | Page size is always **10**. --- ## Re-fetch triggers The full `init()` sequence (calls 1–3, and possibly 5) runs again when: - User switches away from Home tab and returns to it - User navigates to Liked Tenders and comes back (`homeViewModel.init()` on return) This means the same 3–5 API calls can repeat on every tab switch back to Home. --- ## APIs defined but NOT called on home load | Endpoint | Notes | |----------|-------| | `GET /api/v1/feedback/stats/company` | Defined in client (`HomeApi.statsCompany`) but never called from home | Some home stat cards show hardcoded `0` values (approved tenders count, tender value) — no API backing today. --- ## Performance notes for backend ### Client-side bottleneck The client **does not parallelize** calls 1, 2, and 3. Total time-to-interactive ≈ `T(stats) + T(your-tenders) + T(liked-count)`. ### Suggested backend investigations 1. **`GET /api/v1/tender-approvals?limit=10&offset=0`** — Likely the slowest call. Check query plans, N+1 joins, and payload size (10 full tender objects). 2. **`GET /api/v1/tender-approvals/stats`** — Runs before every home load; consider caching per company/user. 3. **`GET /api/v1/feedback?feedback_type=like&limit=1&offset=0`** — Client only reads `meta.total`. A lightweight `GET /api/v1/feedback/count?feedback_type=like` would avoid loading feedback rows. 4. **`GET /api/v1/tenders/recommend`** — May involve recommendation/AI logic; profile separately from tender-approvals. ### Suggested backend optimizations | Idea | Benefit | |------|---------| | Single aggregated `GET /api/v1/home` endpoint | One round-trip instead of 3 sequential | | Return stats + liked count alongside tender-approvals list | Eliminate calls 1 and 3 | | Slim tender DTO for list views (id, title, deadline, status only) | Smaller payloads on call #2 and #5 | | Cache stats and liked count with short TTL | Faster repeat visits / tab switches | ### Suggested client optimizations (for reference) These are frontend changes the mobile team could make independently: - Run calls 1, 2, and 3 in parallel (`Future.wait`) - Do not re-run full `init()` on every tab switch — refresh only stale data - Await the recommend fallback before clearing loading state (avoids UI flicker) --- ## Source files (client) | File | Role | |------|------| | `lib/view_models/home_view_model.dart` | Orchestrates all home API calls | | `lib/data/repositories/home_repository.dart` | Repository layer | | `lib/data/services/home_service.dart` | Stats, recommend, notifications | | `lib/data/services/your_tenders_service.dart` | Your tenders list | | `lib/data/services/liked_tenders_service.dart` | Liked count | | `lib/data/services/api/home_api.dart` | Endpoint path constants | | `lib/data/services/api/your_tenders_api.dart` | Tender-approvals URL builder | | `lib/data/services/api/liked_tenders_api.dart` | Feedback URL builder | | `lib/core/routes/app_shell/app_shell_screen.dart` | Background notification check | | `lib/views/home/pages/home_screen.dart` | Re-init on tab return | --- ## Quick test commands Replace `$TOKEN` with a valid access token. ```bash # 1. Stats curl -s -w "\n%{http_code} %{time_total}s\n" \ 'https://app.opplenz.com/api/v1/tender-approvals/stats' \ -H "Authorization: Bearer $TOKEN" | tail -1 # 2. Your tenders curl -s -w "\n%{http_code} %{time_total}s\n" \ 'https://app.opplenz.com/api/v1/tender-approvals?limit=10&offset=0' \ -H "Authorization: Bearer $TOKEN" | tail -1 # 3. Liked count curl -s -w "\n%{http_code} %{time_total}s\n" \ 'https://app.opplenz.com/api/v1/feedback?feedback_type=like&limit=1&offset=0' \ -H "Authorization: Bearer $TOKEN" | tail -1 # 4. Notifications (background) curl -s -w "\n%{http_code} %{time_total}s\n" \ 'https://app.opplenz.com/api/v1/notifications?seen=false&limit=1&event_type=PUSH' \ -H "Authorization: Bearer $TOKEN" | tail -1 # 5. Recommended (only when your tenders empty) curl -s -w "\n%{http_code} %{time_total}s\n" \ 'https://app.opplenz.com/api/v1/tenders/recommend?limit=10&offset=0' \ -H "Authorization: Bearer $TOKEN" | tail -1 ``` Run all five and compare `time_total` to identify the slowest endpoint.