Public API - public-api - подробное руководство для разработчиков

19 февраля 2026
Автор

Олег Марков

Введение

Public API (public-api) — это внешний интерфейс приложения или сервиса, предназначенный для использования другими системами и разработчиками. Через него клиентские приложения, партнерские сервисы, мобильные клиенты, микросервисы и интеграции получают доступ к функциям и данным вашей системы.

Смотрите, я покажу вам, как на Public API смотрят с точки зрения архитектуры, как им пользоваться и как его правильно проектировать. В статье мы будем опираться в первую очередь на HTTP‑API (REST и близкие к нему стили), потому что именно их чаще всего имеют в виду под public-api в веб-разработке.

Давайте по шагам разберем:

  • какие бывают типы Public API;
  • как выглядит типичная структура запросов и ответов;
  • как организовать аутентификацию и авторизацию;
  • как проектировать ресурсы и методы;
  • как документировать и версионировать public-api;
  • какие практики помогут сделать интерфейс стабильным и безопасным.

Что такое Public API и чем он отличается от других API

Определение Public API

Public API — это программный интерфейс, который:

  • документирован и предназначен для внешних пользователей;
  • имеет стабильный договор (контракт) по структуре входящих и исходящих данных;
  • защищен (как минимум по аутентификации и авторизации);
  • поддерживается и развивается по понятным правилам (версионирование, депрекейшн).

Важно разделять понятия:

  • внутренний (private, internal) API — используется только внутри одной команды или внутри периметра компании;
  • партнерский API — формально может быть внешним, но доступ выдается ограниченному числу партнеров;
  • публичный API (public-api) — доступен широкой аудитории разработчиков по понятной процедуре (например, регистрация и получение ключа).

Public API почти всегда строят с учетом:

  • обратной совместимости — вы не можете «сломать» интеграции сотен клиентов простым изменением поля;
  • четкой политики версионирования;
  • повышенных требований к безопасности и отказоустойчивости.

Типы Public API по протоколу

Чаще всего под public-api понимают:

  • RESTful HTTP API — классический вариант с ресурсами и методами HTTP;
  • JSON-over-HTTP — близкий к REST стиль, но без строго следования всем REST‑принципам;
  • GraphQL API — единая схема и один HTTP‑эндпоинт с декларативными запросами;
  • gRPC API — больше подходит для межсервисного взаимодействия, но иногда используется и как публичный.

В этой статье мы сосредоточимся на HTTP Public API с JSON, потому что это самый распространенный и понятный формат для разработчиков.

Базовая структура Public API

Базовый URL и версия

Обычно Public API доступен по базовому URL:

Версия часто указывается:

Для большинства сценариев проще начинать с версии в URL. Смотрите, как это может выглядеть в целом:

Формат данных

Наиболее распространенный формат — JSON. Клиент сообщает, что ожидает JSON, через заголовок Accept, а сервер возвращает Content-Type.

Пример простого ответа Public API:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 123,
  "email": "user@example.com",
  "name": "Ivan Petrov",
  "created_at": "2025-01-01T12:00:00Z"
}

Здесь я использую поля в snake_case, но вы можете выбрать и camelCase. Главное — быть последовательными в рамках всего public-api.

Стандартные методы HTTP

Чаще всего используются:

  • GET — получение данных;
  • POST — создание ресурса или выполнение действия;
  • PUT — полное обновление ресурса;
  • PATCH — частичное обновление;
  • DELETE — удаление.

Например:

GET /v1/users           # Получить список пользователей
GET /v1/users/123       # Получить одного пользователя
POST /v1/users          # Создать пользователя
PATCH /v1/users/123     # Частично обновить пользователя
DELETE /v1/users/123    # Удалить пользователя

Комментарии здесь показывают тип операций, которые вы выполняете на одном и том же ресурсе users.

Аутентификация и авторизация в Public API

Токены и ключи API

Почти любой public-api требует аутентификацию. Самые распространенные варианты:

  • API Key — простой ключ, который передается в заголовках или в параметрах;
  • OAuth 2.0 — более сложная, но гибкая схема с access token;
  • JWT (JSON Web Token) — токен формата JWT, подписанный сервером.

Пример с API Key в заголовке:

GET /v1/users/me HTTP/1.1
Host: api.example.com
X-API-Key: your_api_key_here
Accept: application/json

В этом примере:

  • X-API-Key — пользовательский заголовок, который ваш сервер проверяет;
  • yourapikey_here — секретное значение, которое клиент получает при регистрации.

Пример с Bearer токеном (OAuth 2.0 или JWT):

GET /v1/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Accept: application/json

Здесь:

  • Authorization — стандартный заголовок;
  • Bearer — схема, которая говорит, что вы передаете токен доступа.

Ответы при ошибках авторизации

Хорошая практика — всегда возвращать понятные коды ошибок и сообщения.

Пример отсутствия токена:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": "unauthorized",
  "error_description": "Missing or invalid authentication token"
}

Пример недостаточных прав (нет доступа к ресурсу):

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "forbidden",
  "error_description": "You do not have access to this resource"
}

Обратите внимание, как текст в error_description объясняет, что именно произошло. Это сильно облегчает отладку для тех, кто интегрируется с вашим public-api.

Проектирование ресурсов и эндпоинтов

Ресурсный подход

В ресурсном public-api вы работаете с сущностями:

  • users
  • orders
  • products
  • invoices
  • etc.

Именно эти сущности становятся основой URL. Давайте разберем пример для ресурса orders.

Возможная структура:

  • GET /v1/orders — список заказов;
  • POST /v1/orders — создание заказа;
  • GET /v1/orders/{id} — один заказ;
  • PATCH /v1/orders/{id} — частичное обновление;
  • DELETE /v1/orders/{id} — удаление заказа.

Теперь вы увидите, как это выглядит в примере JSON.

Создание заказа:

POST /v1/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Content-Type: application/json
Accept: application/json

{
  "customer_id": 42,           // Идентификатор клиента
  "items": [                   // Список позиций заказа
    {
      "product_id": 10,        // Идентификатор товара
      "quantity": 2            // Количество
    },
    {
      "product_id": 11,
      "quantity": 1
    }
  ],
  "comment": "Please deliver after 18:00"  // Комментарий
}

Пример ответа:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/orders/1001

{
  "id": 1001,                     // Созданный идентификатор заказа
  "status": "pending",            // Статус заказа
  "customer_id": 42,
  "items": [
    {
      "product_id": 10,
      "quantity": 2
    },
    {
      "product_id": 11,
      "quantity": 1
    }
  ],
  "comment": "Please deliver after 18:00",
  "created_at": "2025-01-10T12:30:00Z"
}

Как видите, сервер возвращает не только id, но и все важные поля, которые могут понадобиться клиенту сразу после создания.

Фильтрация, сортировка, пагинация

Public API обычно должен уметь эффективно возвращать большие списки. Для этого используются:

  • query‑параметры для фильтрации и сортировки;
  • параметры пагинации (page, limit, cursor и т. д.).

Пример запроса списка с фильтрами:

GET /v1/orders?status=pending&customer_id=42&sort=-created_at&page=1&limit=20 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Accept: application/json

Здесь:

  • status=pending — фильтруем по статусу;
  • customer_id=42 — фильтр по клиенту;
  • sort=-created_at — сортировка по дате создания по убыванию (минус обозначает убывание);
  • page=1, limit=20 — первая страница по 20 записей.

Пример ответа с пагинацией:

{
  "data": [
    {
      "id": 1001,
      "status": "pending",
      "customer_id": 42,
      "created_at": "2025-01-10T12:30:00Z"
    }
    // Здесь могут быть другие заказы
  ],
  "meta": {
    "page": 1,            // Текущая страница
    "limit": 20,          // Размер страницы
    "total": 135,         // Общее количество элементов
    "total_pages": 7      // Общее количество страниц
  }
}

Комментарии подсказывают, как интерпретировать метаинформацию, чтобы вы могли правильно построить клиентский код.

Действия над ресурсами

Иногда вам нужно представить не только CRUD‑операции, но и действия, которые меняют состояние ресурса. Например:

  • подтверждение заказа;
  • отмена заказа;
  • смена пароля;

Есть два распространенных подхода:

  1. Отдельный эндпоинт:

    • POST /v1/orders/{id}/confirm
    • POST /v1/orders/{id}/cancel
  2. Поле состояния и PATCH:

    • PATCH /v1/orders/{id} с телом {"status": "confirmed"}

Чаще для публичных API используют первый вариант для явных действий, чтобы не оставлять место для неправильных комбинаций состояний.

Пример:

POST /v1/orders/1001/confirm HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Accept: application/json

Ответ:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 1001,
  "status": "confirmed",            // Статус изменился
  "confirmed_at": "2025-01-10T13:00:00Z"
}

Обратите внимание, как отдельное поле confirmed_at явно показывает момент подтверждения.

Единый формат ошибок в Public API

Зачем нужен единый формат

Если вы хотите, чтобы сторонние разработчики могли быстро отладить интеграцию, ошибки должны быть:

  • предсказуемыми по структуре;
  • понятными по коду и сообщению;
  • детальными там, где это безопасно.

Давайте разберем типичный формат ошибок:

{
  "error": "validation_error",          // Код общего типа ошибки
  "error_description": "Validation failed",  // Краткое описание
  "details": [                         // Детали по конкретным полям
    {
      "field": "email",
      "message": "Email is invalid"
    },
    {
      "field": "password",
      "message": "Password must be at least 8 characters"
    }
  ],
  "request_id": "b12f90c1-4bde-4b2c-9c38-9aa9d2b9a001"  // Идентификатор запроса для логов
}

Теперь давайте посмотрим, как это выглядит в HTTP‑ответе:

HTTP/1.1 400 Bad Request
Content-Type: application/json
X-Request-Id: b12f90c1-4bde-4b2c-9c38-9aa9d2b9a001

{
  "error": "validation_error",
  "error_description": "Validation failed",
  "details": [
    {
      "field": "email",
      "message": "Email is invalid"
    }
  ],
  "request_id": "b12f90c1-4bde-4b2c-9c38-9aa9d2b9a001"
}

Комментарии в теле ответа уже были выше, поэтому здесь вы легко можете сопоставить их с заголовком X-Request-Id.

Основные коды ошибок

Рекомендуется использовать стандартные HTTP‑коды:

  • 400 — неверный запрос (validationerror, invalidrequest);
  • 401 — неавторизован (unauthorized);
  • 403 — нет прав (forbidden);
  • 404 — не найдено (not_found);
  • 409 — конфликт (conflict, например, дубликат уникального поля);
  • 429 — слишком много запросов (ratelimitexceeded);
  • 500 — ошибка сервера (internal_error);
  • 503 — сервис недоступен (service_unavailable).

Покажу вам простой пример ошибки лимита запросов:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60

{
  "error": "rate_limit_exceeded",
  "error_description": "Rate limit exceeded. Try again later",
  "retry_after": 60                // Через сколько секунд можно повторить запрос
}

Такой ответ сразу дает клиенту понятную инструкцию, как корректно обработать ситуацию.

Версионирование Public API

Зачем нужно версионирование

Когда вы публикуете внешний API, вы заключаете договор с пользователями. Любое изменение, которое может «сломать» существующий код клиентов, считается несовместимым (breaking change). Чтобы все было управляемо, используют версии.

Варианты:

  • v1, v2 в URL;
  • semver, отраженный в URL или заголовках;
  • тип «вечный v1», где вы добавляете только обратно совместимые изменения.

Что считать breaking change

Несколько примеров несовместимых изменений:

  • удаление поля из ответа;
  • изменение типа поля (было число, стало строка);
  • изменение семантики поля (значения меняют смысл);
  • изменение структуры тела запроса;
  • изменение кода ответа (например, 200 вместо 201).

То, что вы добавляете новое поле в ответ — обычно не является breaking change, если клиенты умеют его игнорировать. Но здесь важно зафиксировать это в документации как правило.

Пример двух версий

Представим:

  • v1 — возвращает пользователя с полем full_name;
  • v2 — вместо fullname возвращает firstname и last_name.

Возможная реализация:

GET /v1/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Accept: application/json

Ответ v1:

{
  "id": 123,
  "email": "user@example.com",
  "full_name": "Ivan Petrov"
}

Теперь новая версия:

GET /v2/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Accept: application/json

Ответ v2:

{
  "id": 123,
  "email": "user@example.com",
  "first_name": "Ivan",      // Имя
  "last_name": "Petrov"      // Фамилия
}

Здесь v1 и v2 существуют параллельно, и клиенты сами выбирают, когда мигрировать.

Документация Public API

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

Для public-api документация — не просто дополнение, а основной инструмент, по которому судят о качестве всего сервиса. Если документация:

  • полная;
  • актуальная;
  • содержит примеры запросов и ответов;
  • описывает коды ошибок;

то разработчикам будет гораздо проще интегрироваться.

Чаще всего используют:

  • OpenAPI (Swagger) — машинно‑читаемое описание + визуальный UI;
  • встроенную документацию в виде API портала;
  • примеры SDK и кода на популярных языках.

Пример фрагмента OpenAPI

Ниже я показываю упрощенный пример для эндпоинта GET /v1/users/{id}:

paths:
  /v1/users/{id}:
    get:
      summary: Get user by id          # Краткое описание
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: User found
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                  email:
                    type: string
                  name:
                    type: string
        '404':
          description: User not found

Комментарии здесь помогают вам понять, какую роль играет каждый блок.

Примеры для клиентов

Очень полезно в документации показывать «готовые» запросы:

  • curl;
  • фрагменты на JavaScript, Python, Go, Java и т. д.

Давайте разберем пример с curl:

curl -X GET "https://api.example.com/v1/users/123" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Accept: application/json"
# Здесь мы отправляем GET-запрос на получение пользователя 123,
# передаем токен в заголовке Authorization и ожидаем JSON-ответ

И пример на JavaScript с fetch:

async function getUser(userId, token) {
  const response = await fetch(`https://api.example.com/v1/users/${userId}`, {
    method: "GET",                              // Метод запроса
    headers: {
      "Authorization": `Bearer ${token}`,       // Токен доступа
      "Accept": "application/json"             // Ожидаемый тип ответа
    }
  });

  if (!response.ok) {
    // Здесь мы обрабатываем ошибку, если статус ответа не 2xx
    const errorBody = await response.json();
    throw new Error(errorBody.error_description || "Request failed");
  }

  // Если все хорошо - парсим тело ответа как JSON
  return response.json();
}

Такой пример можно практически сразу использовать в реальном проекте.

Безопасность Public API

HTTPS как обязательное требование

Все запросы к public-api должны идти только по HTTPS. Это защищает:

  • токены и ключи;
  • пользовательские данные;
  • конфиденциальную бизнес-информацию.

Обычно:

  • HTTP перенаправляют на HTTPS с кодом 301 или 308;
  • или полностью блокируют HTTP с кодом 400/426.

Ограничение частоты запросов (Rate limiting)

Публичный API подвержен риску:

  • чрезмерного потребления ресурсов;
  • простых DoS‑атак;
  • неаккуратных клиентов, которые делают слишком много запросов.

Поэтому на уровне API часто вводят лимиты, например:

  • не более 1000 запросов в минуту на токен;
  • дополнительные ограничения на «тяжелые» операции.

Сервер может возвращать специальные заголовки:

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000        # Максимальное количество запросов за период
X-RateLimit-Remaining: 750     # Остаток запросов в текущем периоде
X-RateLimit-Reset: 1700000000  # Время UNIX, когда лимит будет сброшен

Покажу вам, как клиент может использовать эти заголовки:

// Здесь мы предполагаем, что у нас есть объект response из fetch
const limit = response.headers.get("X-RateLimit-Limit");
const remaining = response.headers.get("X-RateLimit-Remaining");
const resetAt = response.headers.get("X-RateLimit-Reset");

// По этим значениям клиент может решить, нужно ли замедлить запросы

Защита от утечек данных

Важно не только шифровать трафик, но и:

  • фильтровать поля в ответах (не светить внутренние идентификаторы и служебные данные);
  • маскировать чувствительные данные (например, часть номера карты);
  • не возвращать подробные стеки ошибок и SQL‑сообщения во внешних ответах.

Например, вместо:

{
  "error": "internal_error",
  "error_description": "SQL error at line 1: syntax error near FROM"
}

лучше вернуть:

{
  "error": "internal_error",
  "error_description": "Internal server error"
}

А детали уже логировать только во внутреннюю систему мониторинга.

Клиентские SDK и публичный API

Зачем нужны SDK

Public API часто сопровождают:

  • официальными SDK (JavaScript, Python, Java, Go и т. д.);
  • примерами кода для популярных фреймворков.

SDK позволяют:

  • скрыть детали аутентификации;
  • инкапсулировать базовый URL, версию API;
  • предоставить удобные методы вместо ручной работы с HTTP.

Простой пример класса-клиента на JavaScript:

class PublicApiClient {
  constructor({ baseUrl, token }) {
    this.baseUrl = baseUrl;   // Базовый URL API
    this.token = token;       // Токен аутентификации
  }

  async request(path, options = {}) {
    const url = `${this.baseUrl}${path}`;  // Собираем полный URL
    const headers = {
      "Authorization": `Bearer ${this.token}`,
      "Accept": "application/json",
      ...options.headers
    };

    const response = await fetch(url, { ...options, headers });

    if (!response.ok) {
      // Читаем тело ошибки и выбрасываем исключение
      const errorBody = await response.json().catch(() => ({}));
      throw new Error(errorBody.error_description || "Request failed");
    }

    // Возвращаем распарсенное тело ответа
    return response.json();
  }

  // Метод для получения текущего пользователя
  getMe() {
    return this.request("/v1/users/me", { method: "GET" });
  }

  // Метод для создания заказа
  createOrder(orderData) {
    return this.request("/v1/orders", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(orderData)  // Сериализуем тело запроса в JSON
    });
  }
}

Такой SDK избавляет интегратора от необходимости каждый раз собирать HTTP‑запросы вручную.

Управление жизненным циклом Public API

Депрекейшн старых версий

Со временем какие-то версии API перестают соответствовать текущим требованиям. Тогда вы планируете их отключение:

  • сначала помечаете версию как deprecated в документации;
  • добавляете предупреждающие заголовки;
  • спустя оговоренный срок отключаете.

Пример ответа со специальным заголовком:

HTTP/1.1 200 OK
Content-Type: application/json
Deprecation: true
Sunset: Wed, 01 Jan 2026 00:00:00 GMT
Link: <https://developer.example.com/migration-guide-v1-to-v2>; rel="deprecation"

{
  "id": 123,
  "email": "user@example.com",
  "full_name": "Ivan Petrov"
}

Здесь:

  • Deprecation: true — явный сигнал, что версия устаревает;
  • Sunset — дата, когда версия будет отключена;
  • Link с rel="deprecation" — ссылка на гайд по миграции.

Мониторинг и логирование

Чтобы поддерживать качество public-api, важно:

  • логировать все запросы и ответы (без чувствительных данных в чистом виде);
  • иметь корреляционные идентификаторы (например, X-Request-Id);
  • отслеживать метрики (количество запросов, ошибки по типам, задержка).

Пример использования X-Request-Id в запросе клиента:

GET /v1/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
X-Request-Id: 1c9bf2e0-996c-4a87-b708-58abdcaf1234
Accept: application/json

Сервер может:

  • логировать этот идентификатор;
  • возвращать его в ответе.

Это упростит разбор инцидентов, когда интегратор пишет вам и присылает конкретный request_id.


Public API (public-api) — это стабильный и тщательно продуманный контракт между вашим сервисом и внешним миром. Чтобы он был полезным и удобным:

  • проектируйте ресурсы и эндпоинты в ресурсном стиле;
  • используйте понятную аутентификацию и авторизацию;
  • придерживайтесь единого формата ошибок;
  • продуманно подходите к версионированию;
  • документируйте все аспекты API и поддерживайте документацию актуальной;
  • уделяйте внимание безопасности и мониторингу.

Когда вы относитесь к public-api как к самостоятельному продукту, разработчикам гораздо проще его освоить и встроить в свои решения.

Частозадаваемые вопросы

Как правильно передавать большие объемы данных через Public API

Используйте пагинацию или постраничную выборку. Вместо того чтобы возвращать тысячи записей за один запрос, реализуйте параметры limit и page или cursor‑based пагинацию. На стороне клиента делайте последовательные запросы, пока не получите все данные. Для изменений больших массивов лучше создать отдельный асинхронный эндпоинт, который запускает задачу на сервере, а клиент периодически опрашивает статус.

Как обновлять схему JSON без ломания старых клиентов

Добавляйте только новые поля и избегайте изменения типов существующих полей. Старые клиенты обычно игнорируют неизвестные поля. Если нужно изменить тип или семантику поля, добавьте новое поле с другим именем и пометьте старое как deprecated в документации. Полное удаление старого поля делайте только в новой версии API.

Как обрабатывать idempotent запросы в Public API

Для операций, которые клиент может повторять (например, создание заказа при нестабильной сети), используйте идемпотентные ключи. Клиент передает уникальный идентификатор операции в заголовке (например, Idempotency-Key), а сервер хранит результат первой успешной операции и возвращает его при повторных запросах с тем же ключом. Это помогает избежать дублирования объектов.

Как лучше реализовать загрузку файлов через Public API

Практичный вариант — использовать двухшаговую схему. Сначала клиент вызывает API-метод, который возвращает pre-signed URL для загрузки файла в хранилище (например, S3). Затем клиент загружает файл напрямую по этому URL без прохождения через ваш основной API. После загрузки можно вызвать отдельный метод для подтверждения и привязки файла к сущности (например, к профилю пользователя).

Как обеспечить совместную работу версии v1 и v2 в одном домене

Разводите версии по URL, например /v1/... и /v2/.... Общие механизмы аутентификации и лимитов запросов можно использовать для обеих версий. Внутри сервера маршрутизируйте запросы на разные обработчики или микросервисы в зависимости от префикса версии. В документации четко указывайте, какие эндпоинты относятся к какой версии, и дайте отдельные примеры для миграции с v1 на v2.

Стрелочка влевоФункция append в Go GolangСтруктура проекта в Go GolangСтрелочка вправо

Все гайды по Feature-sliced_design

Открыть базу знаний

Отправить комментарий