---
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<TokenStorage>()),
    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 <token>` 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')`
