Если вы уже читали про проектирование API в целом, то знаете базу: ресурсы, глаголы HTTP, коды ответов. Но когда дело доходит до мобильных приложений — картина усложняется. Смартфон — не браузер на гигабитном интернете. Это устройство с нестабильным соединением, ограниченным аккумулятором и пользователями, которые могут месяцами не обновлять приложение. Всё это меняет подход к проектированию API.

Сеть — враг мобильного разработчика

Самое очевидное отличие — качество соединения. Пользователь едет в метро, заходит в лифт, переключается между Wi-Fi и мобильным интернетом. Запрос, который на ноутбуке занимает 50 мс, на телефоне может висеть секунды или вообще не завершиться.

Что это означает для API:

Таймауты должны быть разумными. Мобильный клиент обычно выставляет таймаут 10–30 секунд. Если ваш бэкенд отвечает за 25 секунд — это провал. Целевое время ответа для мобильного API — до 300 мс на 95-м перцентиле. Всё, что дольше — пользователь думает, что приложение зависло.

Повторные запросы неизбежны. Клиент не получил ответ — и пробует снова. Если ваши эндпоинты не идемпотентны, это приводит к дублированию данных: двойным заказам, двойным платежам. POST-запросы должны либо быть идемпотентными через Idempotency-Key в заголовке, либо возвращать уже созданный ресурс при повторном вызове.

Размер ответа имеет значение. На мобильном интернете каждый лишний килобайт — это время и деньги пользователя. Отдавайте только те поля, которые нужны конкретному экрану. Этот подход называется sparse fieldsets — клиент указывает нужные поля через параметр fields=id,name,price. GraphQL решает эту проблему элегантнее, но добавляет сложность на бэкенде.

Версионирование — больная тема

Веб-сайт обновляется мгновенно для всех. Мобильное приложение — нет. Часть пользователей всегда сидит на старой версии: кто-то не обновился, кто-то не пустил обновление в App Store или Google Play.

Это значит, что вы не можете просто изменить структуру ответа или убрать поле — старые клиенты сломаются. Типичная картина для нагруженного продукта: одновременно в продакшне живут три версии API — v1, v2, v3. И все надо поддерживать.

Несколько правил, которые спасают:

  • Не удаляйте поля — только добавляйте. Если старый клиент получит незнакомое поле, он его просто проигнорирует. Если не получит ожидаемое — упадёт.
  • Версионируйте через URL: /api/v2/orders. Это прозрачнее, чем версия в заголовке, и проще для отладки.
  • Фиксируйте дату deprecation. Если убираете версию — предупредите за 3–6 месяцев, добавьте заголовок Deprecation: true в ответы и пишите в changelog.
  • Закладывайте sunset policy с самого начала. Договоритесь: поддерживаем версию API 12 месяцев после выхода следующей. Иначе будете поддерживать v1 вечно.

Офлайн — не исключение, а норма

Пользователь открывает приложение в самолёте. Или в зоне плохого покрытия. Или просто хочет посмотреть список товаров без интернета. Мобильное приложение должно работать офлайн — хотя бы в режиме чтения.

Для API это означает проектирование с учётом синхронизации:

Инкрементальная синхронизация. Клиент не должен каждый раз тянуть все данные. Достаточно того, что изменилось с последнего запроса. Стандартный подход — параметр updated_since с временной меткой: /api/products?updated_since=2026-04-10T12:00:00Z. Сервер возвращает только изменённые записи.

Мягкое удаление (soft delete). Если вы физически удаляете запись из базы, клиент при следующей синхронизации не узнает, что её надо удалить из локального кэша. Добавьте поле deleted_at — клиент увидит удалённые записи и обработает их правильно.

Конфликты при синхронизации. Пользователь редактировал данные офлайн, а за это время кто-то другой изменил ту же запись. Нужна стратегия разрешения конфликтов — Last Write Wins, версионирование через ETag или ручное решение на стороне пользователя.

Пагинация: cursor vs offset

Курсорная пагинация выигрывает у offset-based для мобайла. Объясняю почему.

Представьте ленту новостей. Пользователь прокрутил до 50-го поста — и тут появились три новых поста наверху. При offset-пагинации следующий запрос ?offset=50 вернёт сдвинутые данные — часть постов пропустится, часть повторится.

Курсорная пагинация работает через непрозрачный токен, который указывает на конкретную позицию в наборе данных:

GET /api/feed?after=eyJpZCI6MTIzfQ==

{
  "items": [...],
  "next_cursor": "eyJpZCI6MTczfQ==",
  "has_more": true
}

Курсор — это обычно base64-encoded JSON с id последнего элемента или временной меткой. Сервер знает, с какого места продолжить, независимо от новых вставок.

Дополнительный плюс: курсорная пагинация эффективнее для базы данных — не нужен OFFSET, который на больших таблицах убивает производительность.

Аутентификация на мобильных устройствах

JWT-токены — стандарт для мобильного API, но с нюансами.

Access token + refresh token. Access token живёт 15–60 минут, refresh token — 30–90 дней. Когда access истёк, клиент тихо обновляет его через refresh без участия пользователя. Храните токены в безопасном хранилище: iOS Keychain, Android Keystore. Не в SharedPreferences и не в локальных файлах.

Биометрия не заменяет токены. Face ID или Touch ID — это разблокировка доступа к хранилищу токена на устройстве, а не способ аутентификации на сервере. Серверу всё равно нужен валидный токен. Это распространённое заблуждение при проектировании.

Device fingerprint для дополнительной безопасности. Привязывайте сессию к устройству через комбинацию: push-токен, модель устройства, хэш идентификаторов. Если refresh-токен вдруг используется с другого устройства — это повод для дополнительной проверки.

Разлогин при смене устройства. Если пользователь потерял телефон, он должен иметь возможность завершить все активные сессии через веб-интерфейс или другое устройство. Для этого нужен список активных refresh-токенов на сервере с возможностью их инвалидации.

Push-уведомления и API

Push-уведомления — отдельная интеграция, но она тесно связана с API. Типичная схема:

  1. Мобильное приложение получает push-токен от APNs (iOS) или FCM (Android).
  2. Отправляет его на ваш сервер: POST /api/devices { "push_token": "...", "platform": "ios" }.
  3. Сервер сохраняет токен, привязывает к пользователю.
  4. При событии (новый заказ, сообщение) сервер отправляет пуш через APNs/FCM.

Проблемы, которые возникают:

  • Push-токены протухают. После переустановки приложения или сброса устройства токен меняется. Нужно обновлять его при каждом запуске приложения.
  • Один пользователь — несколько устройств. Храните токены в таблице devices, а не в users. У пользователя может быть iPhone и iPad.
  • Silent push для фоновой синхронизации. APNs и FCM поддерживают тихие пуши, которые будят приложение для синхронизации данных без видимого уведомления. Удобно для обновления кэша.

Оптимизация под аккумулятор

Это редко обсуждают при проектировании API, но это реально важно. Частые HTTP-запросы дренируют батарею — не только из-за процессора, но и из-за радиомодуля: каждый запрос «будит» антенну.

Батчинг запросов. Вместо десяти отдельных запросов при старте приложения — один batch-эндпоинт, который возвращает всё нужное для главного экрана:

GET /api/bootstrap

{
  "user": {...},
  "unread_count": 3,
  "featured_products": [...],
  "active_promotions": [...]
}

WebSocket vs polling. Если нужны обновления в реальном времени (чат, статус заказа) — используйте WebSocket или Server-Sent Events вместо периодических HTTP-запросов. Одно постоянное соединение экономит батарею и снижает нагрузку на сервер.

HTTP/2 multiplexing. Если всё же нужно несколько параллельных запросов — HTTP/2 отправляет их по одному соединению. Убедитесь, что ваш бэкенд и CDN поддерживают HTTP/2.

Обработка ошибок: будьте конкретны

Стандартные HTTP-коды — хорошо, но недостаточно. 400 Bad Request не говорит клиенту, что именно не так. Возвращайте структурированные ошибки:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Некорректный формат телефона",
    "field": "phone",
    "docs_url": "https://api.example.com/errors/VALIDATION_ERROR"
  }
}

Мобильный разработчик должен понимать по коду ошибки, что показать пользователю. INSUFFICIENT_FUNDS — показать экран пополнения баланса. SESSION_EXPIRED — перейти на экран логина. SERVER_MAINTENANCE — показать заглушку. Это невозможно сделать, имея только HTTP-статус.

Отдельно про 429 Too Many Requests: всегда возвращайте заголовок Retry-After с количеством секунд до следующей попытки. Клиент должен знать, когда можно повторить запрос, а не долбить сервер в цикле.

Мониторинг мобильного API

Мобильный API нужно мониторить иначе, чем веб. Ключевые метрики:

  • P95 и P99 латентности — не среднее. Средние значения скрывают проблемы.
  • Процент ошибок по версиям клиента. Бывает, что v2.1.0 приложения сломалось, а остальные работают нормально.
  • Размер ответов. Если средний размер вырос с 5 КБ до 50 КБ — где-то появился лишний include.
  • Количество повторных запросов. Высокий процент ретраев — симптом проблем с сетью или таймаутами на сервере.

Мы в REEXY при разработке мобильных бэкендов выстраиваем мониторинг с первого дня — не когда уже горит. Это часть работы, а не опция.

Документация — часть продукта

АPI без документации — это загадка. Swagger/OpenAPI — минимум. Хорошая документация содержит:

  • Примеры запросов и ответов для каждого эндпоинта
  • Описание кодов ошибок с примерами
  • Changelog с датами изменений по версиям
  • Руководство по аутентификации с примерами кода на Swift и Kotlin
  • Sandbox-окружение для тестирования

Документация — это не «потом». Она пишется параллельно с API, иначе мобильные разработчики теряют время на выяснение деталей, а интеграция затягивается.


Мобильный API — это не сложнее веб-API, но требует других приоритетов. Нестабильная сеть, долгоживущие клиенты, батарея, офлайн-режим — всё это нужно учитывать на этапе проектирования, а не переделывать потом. Исправление архитектурных решений в продакшне стоит на порядок дороже, чем правильное проектирование с нуля.

Если вам нужна разработка API или мобильного бэкенда — посмотрите на услуги REEXY на r3xy.ru. Интеграция сервисов и разработка бэкенда под мобильные приложения — в том числе то, с чем помогает студия.