# Platform 365 — API для Flutter и мобильных клиентов

Базовый URL совпадает с веб-клиентом: **`{origin}/api/v1`**.

## Заголовки

| Заголовок | Значение |
|-----------|----------|
| `Content-Type` | `application/json` (для POST/PUT с телом) |
| `Accept` | `application/json` |
| `Authorization` | `Bearer <token>` после `POST /auth/login` или `POST /auth/register` |

**CORS** касается только браузера. В **Flutter** (Dart `http`, Dio) запросы идут напрямую к серверу — достаточно корректного HTTPS и доверенного сертификата в продакшене.

## Живая песочница

- Страница в проекте: **`frontend/api-sandbox.html`** — разделы: база и токен, регистрация/вход, справочники, объекты и поиск, календарь (`availability`, `calculate-price`), 360° (`/properties/{id}/360-images`), бронирования, избранное. Отдельного API под «мини-видео» нет — видео можно брать из полей карточки объекта, когда бэкенд их отдаёт.
- Полный список методов: **`/api-docs.php`** (в корне сайта).

## Авторизация

**`POST /auth/login`**

```json
{ "email": "user@example.com", "password": "secret" }
```

Успешный ответ (упрощённо): `data.token`, `data.user`.

Сохраняйте `token` в `SharedPreferences` / secure storage и подставляйте во все защищённые методы.

## Избранное

| Метод | Путь | Тело / query |
|-------|------|----------------|
| GET | `/favorites` | Query: `type` (опционально), `page` |
| POST | `/favorites/toggle` | `{"favorable_type":"property","favorable_id":123}` |
| POST | `/favorites/check` | те же поля — проверка «в избранном ли» |

Типично `favorable_type`: строка вроде `property`; `favorable_id` — числовой id объекта из каталога.

## Пример (Dart, `package:http`)

```dart
import 'dart:convert';
import 'package:http/http.dart' as http;

final base = Uri.parse('https://YOUR_HOST/api/v1');

Future<String?> login(String email, String password) async {
  final r = await http.post(
    base.resolve('auth/login'),
    headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
    body: jsonEncode({'email': email, 'password': password}),
  );
  if (r.statusCode != 200) return null;
  final map = jsonDecode(r.body) as Map<String, dynamic>;
  return (map['data'] as Map?)?['token'] as String?;
}

Future<http.Response> getFavorites(String token) {
  return http.get(
    base.resolve('favorites'),
    headers: {'Accept': 'application/json', 'Authorization': 'Bearer $token'},
  );
}
```

## Проверка доступности API

**`GET /health`** — без авторизации, лёгкий ответ: `status`, `timestamp`, `version`.

## Ошибки

При `401` клиент должен сбросить токен и отправить пользователя на экран входа. Формат ошибок в теле JSON смотрите в ответах реального сервера (`success: false`, `message`).

---

Полный перечень маршрутов см. **`FRONTEND-BACKEND.md`** и интерактивно — **`api-docs.php`**.
