--- description: "REST API conventions for {{PROJECT_NAME}}" alwaysApply: true --- # REST API Standards — {{PROJECT_NAME}} ## HTTP client setup (Dio) ```dart // In core/network/dio_client.dart Dio createDioClient({required AppConfig config}) { final dio = Dio(BaseOptions( baseUrl: config.baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 30), headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, )); dio.interceptors.addAll([ AuthInterceptor(tokenStorage: getIt()), LoggingInterceptor(), // debug builds only RetryInterceptor(dio), // 3 retries on network errors ]); return dio; } ``` ## DTOs (Data Transfer Objects) - DTOs live in the `data/` layer — **never** pass raw JSON maps to the domain layer - Use Freezed for DTOs if `codegen` includes `json_serializable` or `freezed` - `fromJson` factory must handle null fields gracefully — API response fields are not guaranteed ## Error handling ```dart // Map HTTP errors → AppError domain types AppError _mapDioError(DioException e) => switch (e.type) { DioExceptionType.connectionTimeout => const NetworkError(statusCode: null), DioExceptionType.receiveTimeout => const NetworkError(statusCode: null), DioExceptionType.badResponse => _mapStatusCode(e.response?.statusCode), DioExceptionType.connectionError => const NetworkError(statusCode: null), _ => UnknownError(e), }; ``` ## API versioning - Base URL includes version: `https://api.{{PROJECT_NAME}}.com/v1/` - When upgrading API version, keep old version working until all clients migrate ## Auth token interceptor - Inject `Authorization: Bearer ` automatically on every request - On 401: refresh token once, retry original request, then logout if refresh fails - On 403: map to `AppError.authError('Insufficient permissions')`