07 - ApiService
The single class that talks to the backend. ~1100 lines, ~60 methods, grouped by stage. Every network call in the app goes through this file.
Overview
ApiService is instantiated with a StorageService dependency. It exposes one public method per backend endpoint. Internally, all methods delegate to four generic helpers: _post, _get, _put, _delete.
flutter_app/lib/services/api_service.dartDartclass header + constructor
class ApiService {
final StorageService _storage;
ApiService({required StorageService storage}) : _storage = storage;
}
Headers Injection
Every request gets standard headers automatically. Auth bearer token, X-Lead-Id, X-Session-Id are read from StorageService.
flutter_app/lib/services/api_service.dartDart_headers getter
Map<String, String> get _headers {
final headers = <String, String>{
'Content-Type': 'application/json',
'Accept': 'application/json',
};
final token = _storage.token;
if (token != null) headers['Authorization'] = 'Bearer $token';
final leadId = _storage.leadId;
if (leadId != null) headers['X-Lead-Id'] = leadId;
final sessionId = _storage.sessionId;
if (sessionId != null) headers['X-Session-Id'] = sessionId;
return headers;
}
Generic _post Helper
All POST methods use this. It handles timeout, auth, leadId injection into body, response parsing, error handling, and network exceptions.
flutter_app/lib/services/api_service.dartDart_post<T>
Future<ApiResponse<T>> _post<T>(
String url,
Map<String, dynamic> body, {
required T Function(Map<String, dynamic>) fromJson,
}) async {
// Auto-inject leadId if missing from body
body['leadId'] ??= _storage.leadId;
try {
final response = await http.post(
Uri.parse(url),
headers: _headers,
body: jsonEncode(body),
).timeout(ApiConfig.apiTimeout);
await _storage.updateActivity(); // reset session timer
final json = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode == 200 && json['success'] == true) {
return ApiResponse.ok(
fromJson(json['data'] as Map<String, dynamic>),
json['message'] as String?,
);
}
return ApiResponse.fail(
json['message'] as String? ?? 'Unknown error',
json['errorCode'] as String?,
);
} on SocketException {
return ApiResponse.fail('No internet connection');
} on TimeoutException {
return ApiResponse.fail('Request timed out');
} catch (e) {
debugPrint('API error: $e');
return ApiResponse.fail('Something went wrong');
}
}
Cross-Cutting Concerns
Every call goes through _post/_get, so all of these are handled in one place:
| Concern | How |
| Auth | _headers adds Authorization: Bearer <token> |
| Lead tracking | _headers adds X-Lead-Id, and body['leadId'] ??= ... in _post |
| Session activity | _storage.updateActivity() resets the session timer |
| Timeout | .timeout(ApiConfig.apiTimeout) = 30 seconds |
| Network errors | SocketException → "No internet connection" |
| Response wrapping | JSON parsed and wrapped in ApiResponse<T> |
| Stage progression | Individual methods call _storage.setCurrentStage(N) on success |
API Method Reference (Grouped by Stage)
Below is every public method in ApiService, what it does, and the backend handler it hits. Use this as a quick lookup when you need to find where an endpoint is defined.
Stage 0 - Arrival
| Method | HTTP | Backend Controller |
journeyInit | GET /api/v1/journey/init | Common/ArrivalController.cs |
appVersionCheck | POST /api/v1/app/version-check | Common/JourneyController.cs |
getJourneyStatus | GET /api/v1/journey/status | Common/JourneyController.cs |
Stage 1 - Registration
| Method | HTTP | Backend Controller |
registrationSignup | POST /api/v1/registration/signup | Stage1/RegistrationController.cs |
getResumeState | GET /api/v1/registration/resume/{leadId} | Stage1/RegistrationController.cs |
Stage 2 - Mobile OTP
| Method | HTTP | Backend Controller |
verifyMobileOtp | POST /api/v1/registration/otp/verify | Stage2/OtpVerificationController.cs |
resendMobileOtp | POST /api/v1/registration/otp/resend | Stage2/OtpVerificationController.cs |
Stage 3 - Email
| Method | HTTP | Backend Controller |
getEmailDetails | GET /api/v1/email | Stage3/EmailController.cs |
generateEmailOtp | POST /api/v1/email/otp/generate | Stage3/EmailController.cs |
verifyEmailOtp | POST /api/v1/email/otp/verify | Stage3/EmailController.cs |
resendEmailOtp | POST /api/v1/email/otp/resend | Stage3/EmailController.cs |
Stage 4 - PAN
| Method | HTTP | Backend Controller |
getPanDetails | GET /api/v1/pan/details | Stage4/PanController.cs |
validatePan | POST /api/v1/pan/validate | Stage4/PanController.cs |
confirmPan | POST /api/v1/pan/verify | Stage4/PanController.cs |
Stage 5 - Aadhaar / DigiLocker
| Method | HTTP | Backend Controller |
getAadhaarDetails | GET /api/v1/aadhaar/details | Stage5/AadhaarController.cs |
initiateDigilocker | POST /api/v1/aadhaar/digilocker/initiate | Stage5/AadhaarController.cs |
digilockerCallback | POST /api/v1/aadhaar/digilocker/callback | Stage5/AadhaarController.cs |
uploadAadhaar | POST /api/v1/aadhaar/upload | Stage5/AadhaarController.cs |
Stage 6 - Bank
| Method | HTTP | Backend Controller |
getBankDetails | GET /api/v1/bank/details | Stage6/BankController.cs |
submitBankAccount | POST /api/v1/bank/account | Stage6/BankController.cs |
initiateReversePenny | POST /api/v1/bank/reverse-penny/initiate | Stage6/BankController.cs |
completeReversePenny | POST /api/v1/bank/reverse-penny/complete | Stage6/BankController.cs |
getBankByIfsc | GET /api/v1/bank/list/{ifsc} | Stage6/BankController.cs |
Stage 7 - Liveness
| Method | HTTP | Backend Controller |
initiateLiveness | POST /api/v1/selfie/liveness/initiate | Stage7/LivenessController.cs |
livenessCallback | POST /api/v1/selfie/liveness/callback | Stage7/LivenessController.cs |
Stages 8-15 (abbreviated)
| Stage | Key methods | Backend Controller |
| 8 Signature | getSignatureDetails, uploadSignature | Stage8/SignatureController.cs |
| 9 Personal | getPersonalDetails, savePersonalDetails, addNominee, updateNominee, deleteNominee, verifyNomineePan, declareNoNominee, uploadIncomeProof | Stage9/PersonalDetailsController.cs, NomineeController.cs |
| 11 Validation | runFinalValidation | Stage11/FinalValidationController.cs |
| 12 Doc Gen | generateAof, getDocumentPdf | Stage12/DocumentGenerationController.cs |
| 13 eSign | getEsignDetails, initiateEsign, esignCallback | Stage13/EsignController.cs |
| 14 Account | createAccount, getAccountStatus | Stage14/AccountCreationController.cs |
| 15 Activation + Payment | activateAccount, getPaymentDefaults, initiatePayment, paymentCallback | Stage15/ActivationController.cs, PaymentController.cs |
Backend route constants
backend/src/MO.Ekyc.Shared/Constants/ApiRoutes.cs - source of truth for all route strings
- Backend docs (pending) - placeholder
How to Add a New API Method
Example: adding a method getStageConfig that GETs /api/v1/config/stage/{n}.
Step 1 - Add the URL to ApiConfig
flutter_app/lib/config/api_config.dartDart
static String stageConfig(int stage) => '$_base/config/stage/$stage';
Step 2 - Create a Model (if new response shape)
flutter_app/lib/models/stage_config.dartDart
class StageConfig {
final int stageNumber;
final String displayName;
final int progressPercent;
StageConfig({required this.stageNumber, required this.displayName, required this.progressPercent});
factory StageConfig.fromJson(Map<String, dynamic> json) => StageConfig(
stageNumber: json['stageNumber'] as int,
displayName: json['displayName'] as String,
progressPercent: json['progressPercent'] as int,
);
}
Step 3 - Add the Method to ApiService
flutter_app/lib/services/api_service.dartDart
// Under the appropriate stage section
Future<ApiResponse<StageConfig>> getStageConfig(int stage) async {
return _get<StageConfig>(
ApiConfig.stageConfig(stage),
fromJson: StageConfig.fromJson,
);
}
Step 4 - Call from a Screen
Any screen fileDart
final result = await api.getStageConfig(6);
if (result.success) {
debugPrint('Stage 6: ${result.data!.displayName}');
}
That's it - four steps, no new files beyond the model.
StorageService Reference
ApiService depends on StorageService for auth/session state. Here are its main getters and setters.
flutter_app/lib/services/storage_service.dartDart
class StorageService {
// Singleton
static Future<StorageService> getInstance();
// Session identifiers
String? get leadId;
String? get sessionId;
String? get token;
int get currentStage;
// Setters (all async, persist immediately)
Future<void> setLeadId(String id);
Future<void> setSessionId(String id);
Future<void> setToken(String t);
Future<void> setCurrentStage(int stage);
// Derived
bool get hasActiveLead => leadId != null;
// Activity tracking (for session timer)
Future<void> updateActivity();
DateTime? get lastActivity;
// Clear all (used on journey exit)
Future<void> clearAll();
}