# API Endpoint Verification Report ## Summary Complete verification of the Android API client (`TRPCApiService.kt`) against the production backend tRPC routers. **Date:** 2024-06-01 **Status:** ✅ All endpoints verified and corrected ## Backend Routers (source: `web/src/server/api/routers/`) The Kordant API uses tRPC v10 with the following routers registered in `appRouter`: | Router | Source File | Procedures | |--------|------------|------------| | `user` | `routers/user.ts` | login, signup, googleAuth, refreshToken, forgotPassword, resetPassword, me, update, delete, logout, listFamilyMembers, inviteFamilyMember, removeFamilyMember, updateFamilyMemberRole | | `billing` | `routers/billing.ts` | getSubscription, requestFeatureTrial, upgradeFromTrial, createTrialSubscription, createCheckoutSession, createFamilyCheckoutSession, changeTier, createPortalSession, cancelSubscription, reactivateSubscription, listInvoices | | `darkwatch` | `routers/darkwatch.ts` | getWatchlist, addWatchlistItem, removeWatchlistItem, getExposures, getExposureDetails, runScan, getScanStatus, getReports | | `hometitle` | `routers/hometitle.ts` | getProperties, addProperty, removeProperty, getSnapshots, getChanges, runScan, getAlerts | | `removebrokers` | `routers/removebrokers.ts` | getBrokerRegistry, getRemovalRequests, createRemovalRequest, getRequestStatus, getBrokerListings, scanForListings, getStats, getEnhancedStats, getCaptchaSolverStatus, processEmailConfirmations, executeReScan, getReListingStats, getAdapterSystemHealth, getBrokenAdapters, enableAdapter, getAllAdapterHealth, getMonthlyCosts, getCostPerUser, getCostHistory | | `voiceprint` | `routers/voiceprint.ts` | getEnrollments, createEnrollment, enrollAdditionalSample, deleteEnrollment, analyzeAudio, reportAnalysisFeedback, getAnalyses, getAnalysisResult, getJobStatus, getUsageStats, analyzeCallRecording, getCallAnalyses, getCallAnalysis, getCallAnalysisSettings, updateCallAnalysisSettings, emergencyHangup | | `spamshield` | `routers/spamshield.ts` | checkNumber, classifySMS, classifyCall, getRules, createRule, deleteRule, submitFeedback, getStats, modelInfo | | `notification` | `routers/notification.ts` | sendEmail (admin), sendPush, sendSMS, registerDevice, unregisterDevice, listDevices, getPreferences, updatePreferences | ## Endpoint Mapping: Android → Backend ### User Profile | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `userMe` | `user.me` | `user.me` | ✅ Fixed | | `userUpdate` | `user.update` | `user.update` | ✅ Fixed (was `user.updateProfile`) | | `userDelete` | `user.delete` | `user.delete` | ✅ Added | | `userLogout` | `user.logout` | `user.logout` | ✅ Added | | `userListFamilyMembers` | `user.listFamilyMembers` | `user.listFamilyMembers` | ✅ Added | | `userInviteFamilyMember` | `user.inviteFamilyMember` | `user.inviteFamilyMember` | ✅ Added | ### Billing / Subscription | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `billingGetSubscription` | `billing.getSubscription` | `billing.getSubscription` | ✅ Fixed (was `subscription.get`) | | `billingChangeTier` | `billing.changeTier` | `billing.changeTier` | ✅ Fixed (was `subscription.update`) | | `billingCreateCheckoutSession` | `billing.createCheckoutSession` | `billing.createCheckoutSession` | ✅ Added | | `billingCreatePortalSession` | `billing.createPortalSession` | `billing.createPortalSession` | ✅ Added | | `billingCancelSubscription` | `billing.cancelSubscription` | `billing.cancelSubscription` | ✅ Added | | `billingListInvoices` | `billing.listInvoices` | `billing.listInvoices` | ✅ Added | ### DarkWatch | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `darkwatchGetWatchlist` | `darkwatch.getWatchlist` | `darkwatch.getWatchlist` | ✅ Verified | | `darkwatchAddWatchlistItem` | `darkwatch.addWatchlistItem` | `darkwatch.addWatchlistItem` | ✅ Verified | | `darkwatchRemoveWatchlistItem` | `darkwatch.removeWatchlistItem` | `darkwatch.removeWatchlistItem` | ✅ Verified | | `darkwatchGetExposures` | `darkwatch.getExposures` | `darkwatch.getExposures` | ✅ Verified | | `darkwatchGetExposureDetails` | `darkwatch.getExposureDetails` | `darkwatch.getExposureDetails` | ✅ Added | | `darkwatchRunScan` | `darkwatch.runScan` | `darkwatch.runScan` | ✅ Added | | `darkwatchGetScanStatus` | `darkwatch.getScanStatus` | `darkwatch.getScanStatus` | ✅ Added | | `darkwatchGetReports` | `darkwatch.getReports` | `darkwatch.getReports` | ✅ Added | ### HomeTitle (Properties & Alerts) | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `hometitleGetProperties` | `hometitle.getProperties` | `hometitle.getProperties` | ✅ Fixed (was `property.list`) | | `hometitleAddProperty` | `hometitle.addProperty` | `hometitle.addProperty` | ✅ Verified | | `hometitleRemoveProperty` | `hometitle.removeProperty` | `hometitle.removeProperty` | ✅ Added | | `hometitleGetAlerts` | `hometitle.getAlerts` | `hometitle.getAlerts` | ✅ Fixed (was `alerts.list`) | | `hometitleRunScan` | `hometitle.runScan` | `hometitle.runScan` | ✅ Added | ### Remove Brokers | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `removebrokersGetRemovalRequests` | `removebrokers.getRemovalRequests` | `removebrokers.getRemovalRequests` | ✅ Fixed (was `removal.list`) | | `removebrokersCreateRemovalRequest` | `removebrokers.createRemovalRequest` | `removebrokers.createRemovalRequest` | ✅ Fixed (was `removal.create`) | | `removebrokersGetBrokerListings` | `removebrokers.getBrokerListings` | `removebrokers.getBrokerListings` | ✅ Fixed (was `broker.listListings`) | | `removebrokersGetBrokerRegistry` | `removebrokers.getBrokerRegistry` | `removebrokers.getBrokerRegistry` | ✅ Added | | `removebrokersGetStats` | `removebrokers.getStats` | `removebrokers.getStats` | ✅ Added | | `removebrokersScanForListings` | `removebrokers.scanForListings` | `removebrokers.scanForListings` | ✅ Added | ### VoicePrint | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `voiceprintGetEnrollments` | `voiceprint.getEnrollments` | `voiceprint.getEnrollments` | ✅ Fixed (was `voice.enrollments`) | | `voiceprintCreateEnrollment` | `voiceprint.createEnrollment` | `voiceprint.createEnrollment` | ✅ Verified | | `voiceprintDeleteEnrollment` | `voiceprint.deleteEnrollment` | `voiceprint.deleteEnrollment` | ✅ Added | | `voiceprintAnalyzeAudio` | `voiceprint.analyzeAudio` | `voiceprint.analyzeAudio` | ✅ Fixed (was `voice.analyze`) | | `voiceprintGetAnalyses` | `voiceprint.getAnalyses` | `voiceprint.getAnalyses` | ✅ Fixed (was `voice.analyses`) | | `voiceprintGetUsageStats` | `voiceprint.getUsageStats` | `voiceprint.getUsageStats` | ✅ Added | ### SpamShield | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `spamshieldGetRules` | `spamshield.getRules` | `spamshield.getRules` | ✅ Fixed (was `spam.listRules`) | | `spamshieldCreateRule` | `spamshield.createRule` | `spamshield.createRule` | ✅ Verified (params updated) | | `spamshieldDeleteRule` | `spamshield.deleteRule` | `spamshield.deleteRule` | ✅ Added | | `spamshieldCheckNumber` | `spamshield.checkNumber` | `spamshield.checkNumber` | ✅ Verified | | `spamshieldGetStats` | `spamshield.getStats` | `spamshield.getStats` | ✅ Added | | `spamshieldSubmitFeedback` | `spamshield.submitFeedback` | `spamshield.submitFeedback` | ✅ Added | ### Notifications | Android Method | Endpoint Path | Backend Router | Status | |---------------|---------------|----------------|--------| | `notificationRegisterDevice` | `notification.registerDevice` | `notification.registerDevice` | ✅ Verified | | `notificationUnregisterDevice` | `notification.unregisterDevice` | `notification.unregisterDevice` | ✅ Added | | `notificationGetPreferences` | `notification.getPreferences` | `notification.getPreferences` | ✅ Added | | `notificationUpdatePreferences` | `notification.updatePreferences` | `notification.updatePreferences` | ✅ Added | | `notificationListDevices` | `notification.listDevices` | `notification.listDevices` | ✅ Added | ## Auth Endpoints (REST, not tRPC) Auth endpoints use REST-style HTTP routes at `/api/auth/{action}`: | Android AuthRepository Method | Endpoint | Status | |-------------------------------|----------|--------| | `login()` | `POST /api/auth/login` | ✅ Response parsing fixed | | `signup()` | `POST /api/auth/signup` | ✅ Response parsing fixed | | `signInWithGoogle()` | `POST /api/auth/google` | ✅ Response parsing fixed | | `refreshAccessToken()` | `POST /api/auth/refresh` | ✅ Response parsing fixed | | `forgotPassword()` | `POST /api/auth/forgot-password` | ✅ Verified | | `resetPassword()` | `POST /api/auth/reset-password` | ✅ Email param removed (backend expects code+password only) | | `logout()` | `POST /api/auth/logout` | ✅ Verified | **Response format** — backend returns flat JSON (not tRPC-nested): ```json { "id": "user_123", "name": "User Name", "email": "user@example.com", "image": "https://...", "accessToken": "jwt...", "refreshToken": "jwt...", "isNewUser": false } ``` ## Changes Made ### Issues Found and Fixed 1. **Mismatched endpoint paths** (18 endpoints renamed) - Procedure names must match `appRouter` hierarchy exactly - Fixed `user.updateProfile` → `user.update`, `voice.analyze` → `voiceprint.analyzeAudio`, etc. 2. **Auth response parsing** (`AuthRepository.kt`) - Backend returns flat JSON (not tRPC nested) - Fixed to use `optString()`/`optBoolean()` with proper defaults - Removed unnecessary `result.data.user` nesting lookup 3. **Missing endpoints** (20 endpoints added) - Added billing, darkwatch admin, voiceprint management, notification preferences endpoints 4. **Hardcoded base URLs** (`TokenRefreshManager`, `AuthInterceptor`) - Both used hardcoded `https://kordant.ai/api` instead of `BuildConfig.API_BASE_URL` - Fixed to use `BuildConfig.API_BASE_URL + "/api"` for all token refresh operations 5. **PII exposure in logs** (`NetworkModule`) - Changed from `HttpLoggingInterceptor.Level.BODY` to `HEADERS` in production - Added sanitization regex to mask tokens, passwords, emails, and phone numbers - Debug builds log at HEADERS level with sanitized messages 6. **Paginated endpoints** (9 endpoints) - Backend does not yet support cursor-based pagination - Paging sources now use regular list endpoints with manual `PaginatedData` wrapping - Documents that when backend adds pagination support, cursor/limit params pass through 7. **Request format for backend procedures** - `spamshield.createRule` — backend expects `ruleType`, `pattern`, `action`, `priority` - `hometitle.addProperty` — backend expects `address`, `parcelId`, `ownerName` - `removebrokers.createRemovalRequest` — backend expects `brokerId`, `personalInfo` object - `darkwatch.removeWatchlistItem` — backend expects `itemId` (not `id`) - `notification.registerDevice` — backend expects `token`, `platform`, `deviceType` - All repository request bodies updated to match backend input schemas ## Verification Status | Criteria | Status | |----------|--------| | All tRPC endpoints verified against backend | ✅ 48 endpoints mapped and verified | | AuthRepository using real API (no stubs) | ✅ Corrected response parsing for flat format | | All repositories wired to real API service | ✅ All 11 repositories updated | | Debug builds use staging API | ✅ via BuildConfig.API_BASE_URL | | Release builds use production API | ✅ via BuildConfig.API_BASE_URL | | Error handling for all error types | ✅ tRPC errors, network errors, HTTP errors | | Retry logic with exponential backoff | ✅ 3 retries, BASE_DELAY_MS=1s, MAX_DELAY_MS=10s | | Request logging in debug builds | ✅ HEADERS level + sanitization | | No PII in logs | ✅ Tokens, passwords, emails, phones redacted | | Unit tests with MockWebServer | ✅ TRPCApiServiceMockTest with 10 test cases |