Mobile API

API для мобильного приложения

Backend contract уже описан в OpenAPI. Этой страницы достаточно, чтобы другой разработчик понял текущие base URL, авторизацию, основные ресурсы и где скачать спецификацию.

Готово для интеграции
REST API
https://meets.cc/api/backend
Direct API ports
Закрыты наружу, используйте HTTPS endpoint
Web/OAuth origin
https://meets.cc
CORS allowlist
https://meets.cc
Auth state
Bearer access token + rotating refresh token

Base URLs

API_BASE_URL=https://meets.cc/api/backend WEB_ORIGIN=https://meets.cc API_CORS_ALLOWED_ORIGIN=https://meets.cc OPENAPI=https://meets.cc/api/openapi.yaml

Мобильный клиент должен использовать HTTPS endpoint. Nginx снимает префикс /api/backend и проксирует запросы в backend root, поэтому https://meets.cc/api/backend/events соответствует backend path /events.

Авторизация

Native-клиент отправляет Authorization: Bearer ACCESS_TOKEN. Browser/WebView flow остаётся совместимым с HttpOnly cookie vstrechi_refresh_token.

curl -i https://meets.cc/api/backend/healthz

curl -i "https://meets.cc/auth/yandex/start?return_to=/profile"

Mobile auth flow

Native-контракт: короткоживущий Bearer access token и ротируемый refresh token.

  1. Открыть login flow Для Yandex входа откройте web/OAuth origin в WebView или системном браузере.
  2. Получить пару токенов Native/mobile получает короткоживущий access_token и refresh_token; web flow продолжает хранить refresh только в HttpOnly cookie.
  3. Обновлять сессию Native client вызывает POST /auth/refresh с JSON body {"refresh_token":"..."} и атомарно заменяет оба токена.
  4. Ходить в protected API Protected endpoints принимают Authorization: Bearer <access_token>; refresh храните в secure storage и не отправляйте на обычные API calls.
POST https://meets.cc/api/backend/auth/refresh
Content-Type: application/json

{"refresh_token":"REFRESH_TOKEN_FROM_LOGIN"}
GET https://meets.cc/api/backend/me
Authorization: Bearer ACCESS_TOKEN

First integration checklist

Минимальный порядок работ для мобильного разработчика без погружения в backend-код.

  1. Собрать anonymous discovery: events list, event detail, feed, public profiles.
  2. Подключить phone auth или dev-auth в разрешённом окружении, сохранить refresh_token в secure storage и access_token в памяти; Yandex WebView остаётся cookie-backed web flow.
  3. Реализовать refresh через JSON body и Authorization: Bearer для protected calls.
  4. Добавить join event, calendar entries, notifications и обработку 401 после logout/expired session.
  5. Перед handoff прогнать mobile smoke с API_CORS_ALLOWED_ORIGIN=https://meets.cc.

First mobile screens

Практический порядок сборки: сначала anonymous discovery, потом login, join, calendar и notifications.

Events list

Без логина
GET /events?limit=20

Главный экран поиска событий: title, image_url, starts_at, location, participant_count и badges.

Event detail

Без логина
GET /events/{event_id}

Детальная карточка события, место, организатор, текущий статус участия и social proof.

Feed

Без логина, богаче после входа
GET /feed?limit=20

Лента событий и activity-сигналов. Для signed-in пользователя появляются персональные причины.

My profile

Нужна сессия
GET /me

Проверка текущего пользователя после login/refresh и источник данных для профиля.

Join event

Нужна сессия
POST /events/{event_id}/join

Главное mutating действие: после join обновлять detail, attendees и calendar affordance.

Calendar

Нужна сессия
GET /calendar/entries

Личный календарь пользователя, включая event-linked entries после участия.

Mobile integration examples

Request, expected result and client-side action for the first mobile flows.

Скачать runnable collection для REST Client: mobile-api-examples.http.

Anonymous discovery

GET /events?limit=20
GET /feed?limit=20
Expected
200, items[], next_cursor; feed also returns scope.
Client action
Render event cards, cache cursors, keep login optional until a mutating action.

Dev auth and secure storage

POST /auth/dev/start
POST /auth/dev/verify
Expected
200, current_user, session_id, access/refresh tokens and expiries.
Client action
Persist refresh_token securely and send access_token as Bearer.

Refresh then load profile

POST /auth/refresh
GET /me
Expected
Refresh returns the rotated access/refresh pair; /me returns current user.
Client action
Atomically replace both tokens, then fetch /me with the new Bearer token.

Join event

POST /events/{event_id}/join
{"participation_visibility":"public"}
Expected
200 with participant status going, or 401 when the user is anonymous.
Client action
Update event detail, participant_count, viewer_going and calendar affordance after success.

Calendar and notifications

GET /calendar/entries
GET /notifications?limit=20
Expected
200 for signed-in user; 401 for anonymous user.
Client action
Show login gate on 401; render empty states when items[] is empty.

Logout cleanup

POST /auth/logout
GET /me
Expected
204 on logout; next /me returns 401.
Client action
Clear secure storage, cookie jar and local user/profile state.

Mobile-critical DTO fields

Поля, на которые можно опираться при сборке первых экранов. Полные схемы остаются в OpenAPI YAML.

EventCardDTO

GET /events, GET /feed
  • event.id
  • event.title
  • event.starts_at
  • event.image_url
  • event.status
  • location.city
  • participant_count
  • feed_context.viewer_going

CurrentUserDTO

GET /me
  • user.id
  • user.email
  • profile.user_id
  • profile.display_name
  • profile.avatar_url
  • profile.city
  • profile.interests

AuthSessionDTO

POST /auth/dev/verify
  • current_user
  • session_id
  • access_token
  • access_token_expires_at
  • token_type
  • refresh_token
  • expires_at

RefreshSessionDTO

POST /auth/refresh
  • session_id
  • access_token
  • access_token_expires_at
  • token_type
  • refresh_token
  • expires_at
  • then GET /me with Bearer

CalendarEntryDTO

GET /calendar/entries
  • id
  • owner_user_id
  • source
  • event_id
  • title
  • starts_at
  • status
  • location_text

NotificationDTO

GET /notifications
  • items[]
  • unread_count
  • items[].kind
  • items[].title
  • items[].body
  • items[].href
  • items[].read_at

InviteDTO

GET /calendar/invites
  • invite.id
  • invite.status
  • entry.id
  • entry.title
  • entry.starts_at
  • entry.location_text

Smoke для mobile API

Скрипт проверяет HTTPS API base URL, публичные reads, mobile DTO shape, OpenAPI, anonymous protected gates, allowlisted CORS preflight, блокировку чужого origin, dev-auth, Secure/HttpOnly/SameSite cookie, refresh через JSON body, /me, calendar, notifications и logout.

API_BASE_URL=https://meets.cc/api/backend WEB_BASE_URL=https://meets.cc API_CORS_ALLOWED_ORIGIN=https://meets.cc ./scripts/check-mobile-api-contract

Browser CORS работает по allowlist из API_CORS_ALLOWED_ORIGINS; на staging разрешен https://meets.cc. Native HTTP client не ограничен browser CORS.

Auth errors and session rules

Правила состояния клиента вокруг refresh, protected endpoints и logout.

  • 401 на protected call означает один раз синхронно обновить токены; если refresh не удался — показать login gate.
  • После POST /auth/refresh нужно атомарно заменить access_token и refresh_token, чтобы параллельные запросы не затёрли новую пару.
  • После logout повторный /me обязан вернуть 401; мобильный клиент очищает secure storage и локальный профиль.
  • CORS нужен только browser/WebView origin; native HTTP clients используют HTTPS API напрямую.

Error status contract

Most API errors return JSON as {"error":"code"}; mobile client behavior should follow the status first, then the code.

400 invalid_request

Malformed JSON, invalid filter, missing required field or invalid enum value.

Keep the user on the current screen, highlight the form field or show a compact validation message.
401 unauthorized

No valid access token/cookie, expired session or logout completed.

Attempt one refresh for an expired access token; otherwise clear local session state and show login gate.
403 forbidden

Signed-in user is authenticated but cannot perform this action.

Do not retry automatically; show a permission message and keep local state unchanged.
404 not_found

Event, profile, calendar entry, invite, notification or post does not exist or is not visible.

Show not-found/removed state and remove stale local references after reload.
409 username_taken

Profile username conflict.

Ask user to choose another username; keep the edit form open.
5xx internal_error

Unexpected server-side failure.

Show retry affordance, keep optimistic state pending or roll it back after timeout.

Основные endpoint-группы

Полный список схем, responses и request bodies находится в OpenAPI YAML.

Auth

  • GET /auth/yandex/start
  • GET /auth/yandex/callback
  • POST /auth/refresh
  • POST /auth/logout

Профиль и люди

  • GET /me
  • PATCH /me/profile
  • GET /users/profiles
  • GET /users/{user_id}/profile

События

  • GET /events
  • GET /events/{event_id}
  • POST /events
  • POST /events/{event_id}/join

Лента и соцграф

  • GET /feed
  • GET /feed/preferences
  • POST /feed/preferences
  • POST /users/{user_id}/follow

Календарь

  • GET /calendar/entries
  • POST /calendar/entries
  • GET /calendar/invites
  • PATCH /calendar/invites/{invite_id}

Уведомления

  • GET /notifications
  • GET /notifications/unread-count
  • POST /notifications/read-all

Что важно для mobile dev

  • Публичные GET /events, GET /feed, GET /users/profiles можно читать без логина.
  • Создание событий, join, calendar, follow, comments и notifications требуют cookie session.
  • Платежи на beta выключены: PAYMENTS_ENABLED=false, paid mutations должны считаться недоступными.
  • Инкогнито-участие не появляется в публичном профиле и social feed, но видно самому пользователю в Куда иду.