Документация / Разработчикам

Пользовательский REST API v1

Базовый URL: https://vedismm.ru/api/v1. Проверяемый контракт: OpenAPI 3.1 JSON.

API покрывает пользовательские сценарии целиком: аутентификацию, профиль и аудит, подключения соцсетей, группы, приватные медиа, черновики, расписание, durable jobs, аналитику и webhooks. Административных endpoints в контракте нет.

Группы операций, scopes, схемы и команды ниже строятся непосредственно из docs/openapi.json. Тесты сравнивают контракт с router и исполняют основной success workflow через реальные контроллеры и сервисы.

Модель работы

  1. Получите короткую access session через POST /auth/login либо создайте PAT с минимальными scopes.
  2. Создайте draft через POST /posts; сохраняйте ETag и идентификатор.
  3. Проверьте constraints, затем выполните schedule или создайте команду publish.
  4. Команды публикации отвечают 202; состояние и результаты целей читайте через /jobs.
  5. Для событий используйте подписанные at-least-once webhooks и дедупликацию по event ID.

Авторизация и безопасные повторы

Основной способ — Authorization: Bearer <token>. Если reverse proxy удаляет этот заголовок и API отвечает missing_token, передайте то же значение в X-API-Token. При одновременной передаче приоритет имеет Authorization. Cookie кабинета, query-параметры и X-API-Key не авторизуют API. Access token живёт 15 минут; refresh token ротируется при каждом использовании. Raw PAT и webhook secret показываются только при создании или ротации.

Idempotency-Key обязателен для отмеченных команд. Он привязан к credential, method, route, query, Content-Type, If-Match и каноническому body. Точный повтор возвращает сохранённый ответ с Idempotency-Replayed; изменение запроса с тем же ключом получает 409.

Версионированные ресурсы изменяются с сильным If-Match. Ответ 412 требует перечитать ресурс. Списки используют непрозрачный tenant/filter-bound cursor из meta.next_cursor.

Scope tracking:read читает ссылки, а tracking:write создаёт, отключает и архивирует их. Создание требует Idempotency-Key, lifecycle-команды — сильный If-Match. Destination неизменяем: disable даёт 410, archive скрывает ссылку из списка и освобождает квоту, сохраняя redirect.

Настройки поста options.tracking содержат оба boolean-поля shorten_links и add_source. Точное правило UTM, поведение interstitial/bot/HEAD и готовые cURL-команды приведены в руководстве по трекинговым ссылкам.

Аналитика переходов

Шесть read-only операций со scope tracking:read дают summary, UTC timeseries, разрезы links/posts/sources и coarse GeoJSON. Везде обязательны включительные from/to максимум на 366 дней; optional filters — link_id, post_id и network. Cursor и limit доступны только для links, posts и sources.

Ответы не раскрывают raw IP, полный User-Agent или точные координаты. География определяется локально, а города показываются с трёх human-переходов за выбранный период. Полный маршрутный и privacy-контракт находится в статье справки.

Официальные SDK и GitHub

Контракт, инструменты соответствия и нативные клиенты развиваются открыто в организации VediSMM на GitHub. Статус «Опубликовано» означает, что релиз прошёл CI и доступен по неизменяемому тегу. Отметка о разработке означает, что репозиторий уже зарезервирован, но ещё не имеет стабильного релиза.

Исполняемые примеры

Нужны curl 7.76+ и jq 1.6+. Перед запуском задайте USER_EMAIL, USER_PASSWORD, числовой ACCOUNT_ID и будущий RFC 3339 SCHEDULED_AT. Получите ACCOUNT_ID из GET /accounts: API принимает только принадлежащий текущему пользователю подключённый аккаунт. Команды безопасно строят JSON через jq --arg/--argjson, показывают response headers/body и экспортируют captures для следующих шагов.

Schedule и immediate publish — независимые ветки: первый draft использует SCHEDULE_POST_ID/SCHEDULE_POST_ETAG, второй — PUBLISH_POST_ID/PUBLISH_POST_VERSION. Publish экспортирует JOB_ID; запланированный draft не снимается с расписания.

Создать короткую API-сессию

login · ожидаемый HTTP 200

vedismm_example_login() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  local CAPTURE_ACCESS_TOKEN
  EXAMPLE_STATUS=0
  REQUEST_BODY="$(jq -cn --arg 'USER_EMAIL' "${USER_EMAIL}" --arg 'USER_PASSWORD' "${USER_PASSWORD}" '{"email":$USER_EMAIL,"password":$USER_PASSWORD,"client_name":"Сервер публикаций"}')"
  BODY_STATUS="$?"
  if [ "$BODY_STATUS" -ne 0 ]; then
    printf 'Request JSON serialization failed.
' >&2
    unset -f vedismm_example_login 2>/dev/null || true
    return "$BODY_STATUS"
  fi
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_login 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_login 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_login 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_login 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'POST' \
    'https://vedismm.ru/api/v1/auth/login' \
    --header 'Content-Type: application/json' \
    --data "$REQUEST_BODY")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '200' ]; then
    printf 'Unexpected HTTP status: expected 200, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  # Failure precedence: body jq, curl, exact HTTP status, then first capture failure.
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_ACCESS_TOKEN="$(jq -er '.data.access_token' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_ACCESS_TOKEN" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    # Captured atomically for the following examples: ACCESS_TOKEN
    export ACCESS_TOKEN="$CAPTURE_ACCESS_TOKEN"
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_login 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_login

Проверить текущего пользователя

getMe · ожидаемый HTTP 200

vedismm_example_read_profile() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  EXAMPLE_STATUS=0
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_read_profile 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_read_profile 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_read_profile 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_read_profile 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'GET' \
    'https://vedismm.ru/api/v1/me' \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '200' ]; then
    printf 'Unexpected HTTP status: expected 200, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_read_profile 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_read_profile

Создать отдельный черновик для расписания

createPostDraft · ожидаемый HTTP 201

vedismm_example_create_scheduled_draft() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  local CAPTURE_SCHEDULE_POST_ID CAPTURE_SCHEDULE_POST_VERSION CAPTURE_SCHEDULE_POST_ETAG
  EXAMPLE_STATUS=0
  REQUEST_BODY="$(jq -cn --argjson 'ACCOUNT_ID' "${ACCOUNT_ID}" 'if (($ACCOUNT_ID | type) == "number" and ($ACCOUNT_ID | isinfinite | not) and ($ACCOUNT_ID | isnan | not) and $ACCOUNT_ID > 0 and ($ACCOUNT_ID | floor) == $ACCOUNT_ID) then {"title":"Запланированный запуск","content":"Текст запланированной публикации","account_ids":[$ACCOUNT_ID]} else error("ACCOUNT_ID must be a finite positive integer") end')"
  BODY_STATUS="$?"
  if [ "$BODY_STATUS" -ne 0 ]; then
    printf 'Request JSON serialization failed.
' >&2
    unset -f vedismm_example_create_scheduled_draft 2>/dev/null || true
    return "$BODY_STATUS"
  fi
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_create_scheduled_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_create_scheduled_draft 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_create_scheduled_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_create_scheduled_draft 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'POST' \
    'https://vedismm.ru/api/v1/posts' \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}" \
    --header 'Idempotency-Key: schedule-draft-20260718-0001' \
    --header 'Content-Type: application/json' \
    --data "$REQUEST_BODY")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '201' ]; then
    printf 'Unexpected HTTP status: expected 201, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  # Failure precedence: body jq, curl, exact HTTP status, then first capture failure.
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_SCHEDULE_POST_ID="$(jq -er '.data.id' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_SCHEDULE_POST_ID" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_SCHEDULE_POST_VERSION="$(jq -er '.data.version' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_SCHEDULE_POST_VERSION" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_SCHEDULE_POST_ETAG="$(awk -v name='ETag' 'tolower(substr($0, 1, length(name) + 1)) == tolower(name ":") { sub(/\r$/, ""); value = substr($0, length(name) + 2); sub(/^[[:space:]]*/, "", value); found = (value != "") } END { if (!found) exit 66; print value }' "$RESPONSE_HEADERS")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_SCHEDULE_POST_ETAG" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    # Captured atomically for the following examples: SCHEDULE_POST_ID, SCHEDULE_POST_VERSION, SCHEDULE_POST_ETAG
    export SCHEDULE_POST_ID="$CAPTURE_SCHEDULE_POST_ID"
    export SCHEDULE_POST_VERSION="$CAPTURE_SCHEDULE_POST_VERSION"
    export SCHEDULE_POST_ETAG="$CAPTURE_SCHEDULE_POST_ETAG"
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_create_scheduled_draft 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_create_scheduled_draft

Запланировать актуальную версию

schedulePost · ожидаемый HTTP 200

vedismm_example_schedule_draft() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  local CAPTURE_SCHEDULE_POST_VERSION CAPTURE_SCHEDULE_POST_ETAG
  EXAMPLE_STATUS=0
  REQUEST_BODY="$(jq -cn --arg 'SCHEDULED_AT' "${SCHEDULED_AT}" '{"scheduled_at":$SCHEDULED_AT}')"
  BODY_STATUS="$?"
  if [ "$BODY_STATUS" -ne 0 ]; then
    printf 'Request JSON serialization failed.
' >&2
    unset -f vedismm_example_schedule_draft 2>/dev/null || true
    return "$BODY_STATUS"
  fi
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_schedule_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_schedule_draft 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_schedule_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_schedule_draft 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'POST' \
    'https://vedismm.ru/api/v1/posts/'"${SCHEDULE_POST_ID}"'/schedule' \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}" \
    --header 'If-Match: '"${SCHEDULE_POST_ETAG}" \
    --header 'Content-Type: application/json' \
    --data "$REQUEST_BODY")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '200' ]; then
    printf 'Unexpected HTTP status: expected 200, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  # Failure precedence: body jq, curl, exact HTTP status, then first capture failure.
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_SCHEDULE_POST_VERSION="$(jq -er '.data.version' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_SCHEDULE_POST_VERSION" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_SCHEDULE_POST_ETAG="$(awk -v name='ETag' 'tolower(substr($0, 1, length(name) + 1)) == tolower(name ":") { sub(/\r$/, ""); value = substr($0, length(name) + 2); sub(/^[[:space:]]*/, "", value); found = (value != "") } END { if (!found) exit 66; print value }' "$RESPONSE_HEADERS")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_SCHEDULE_POST_ETAG" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    # Captured atomically for the following examples: SCHEDULE_POST_VERSION, SCHEDULE_POST_ETAG
    export SCHEDULE_POST_VERSION="$CAPTURE_SCHEDULE_POST_VERSION"
    export SCHEDULE_POST_ETAG="$CAPTURE_SCHEDULE_POST_ETAG"
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_schedule_draft 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_schedule_draft

Создать отдельный черновик для немедленной публикации

createPostDraft · ожидаемый HTTP 201

vedismm_example_create_publish_draft() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  local CAPTURE_PUBLISH_POST_ID CAPTURE_PUBLISH_POST_VERSION CAPTURE_PUBLISH_POST_ETAG
  EXAMPLE_STATUS=0
  REQUEST_BODY="$(jq -cn --argjson 'ACCOUNT_ID' "${ACCOUNT_ID}" 'if (($ACCOUNT_ID | type) == "number" and ($ACCOUNT_ID | isinfinite | not) and ($ACCOUNT_ID | isnan | not) and $ACCOUNT_ID > 0 and ($ACCOUNT_ID | floor) == $ACCOUNT_ID) then {"title":"Немедленный запуск","content":"Текст немедленной публикации","account_ids":[$ACCOUNT_ID]} else error("ACCOUNT_ID must be a finite positive integer") end')"
  BODY_STATUS="$?"
  if [ "$BODY_STATUS" -ne 0 ]; then
    printf 'Request JSON serialization failed.
' >&2
    unset -f vedismm_example_create_publish_draft 2>/dev/null || true
    return "$BODY_STATUS"
  fi
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_create_publish_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_create_publish_draft 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_create_publish_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_create_publish_draft 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'POST' \
    'https://vedismm.ru/api/v1/posts' \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}" \
    --header 'Idempotency-Key: publish-draft-20260718-0001' \
    --header 'Content-Type: application/json' \
    --data "$REQUEST_BODY")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '201' ]; then
    printf 'Unexpected HTTP status: expected 201, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  # Failure precedence: body jq, curl, exact HTTP status, then first capture failure.
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_PUBLISH_POST_ID="$(jq -er '.data.id' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_PUBLISH_POST_ID" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_PUBLISH_POST_VERSION="$(jq -er '.data.version' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_PUBLISH_POST_VERSION" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_PUBLISH_POST_ETAG="$(awk -v name='ETag' 'tolower(substr($0, 1, length(name) + 1)) == tolower(name ":") { sub(/\r$/, ""); value = substr($0, length(name) + 2); sub(/^[[:space:]]*/, "", value); found = (value != "") } END { if (!found) exit 66; print value }' "$RESPONSE_HEADERS")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_PUBLISH_POST_ETAG" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    # Captured atomically for the following examples: PUBLISH_POST_ID, PUBLISH_POST_VERSION, PUBLISH_POST_ETAG
    export PUBLISH_POST_ID="$CAPTURE_PUBLISH_POST_ID"
    export PUBLISH_POST_VERSION="$CAPTURE_PUBLISH_POST_VERSION"
    export PUBLISH_POST_ETAG="$CAPTURE_PUBLISH_POST_ETAG"
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_create_publish_draft 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_create_publish_draft

Поставить публикацию в durable queue

publishPost · ожидаемый HTTP 202

vedismm_example_publish_draft() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  local CAPTURE_JOB_ID
  EXAMPLE_STATUS=0
  REQUEST_BODY="$(jq -cn --argjson 'PUBLISH_POST_VERSION' "${PUBLISH_POST_VERSION}" 'if (($PUBLISH_POST_VERSION | type) == "number" and ($PUBLISH_POST_VERSION | isinfinite | not) and ($PUBLISH_POST_VERSION | isnan | not) and $PUBLISH_POST_VERSION > 0 and ($PUBLISH_POST_VERSION | floor) == $PUBLISH_POST_VERSION) then {"version":$PUBLISH_POST_VERSION} else error("PUBLISH_POST_VERSION must be a finite positive integer") end')"
  BODY_STATUS="$?"
  if [ "$BODY_STATUS" -ne 0 ]; then
    printf 'Request JSON serialization failed.
' >&2
    unset -f vedismm_example_publish_draft 2>/dev/null || true
    return "$BODY_STATUS"
  fi
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_publish_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_publish_draft 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_publish_draft 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_publish_draft 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'POST' \
    'https://vedismm.ru/api/v1/posts/'"${PUBLISH_POST_ID}"'/publish' \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}" \
    --header 'Idempotency-Key: publish-20260718-0001' \
    --header 'Content-Type: application/json' \
    --data "$REQUEST_BODY")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '202' ]; then
    printf 'Unexpected HTTP status: expected 202, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  # Failure precedence: body jq, curl, exact HTTP status, then first capture failure.
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_JOB_ID="$(jq -er '.data.id' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_JOB_ID" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    # Captured atomically for the following examples: JOB_ID
    export JOB_ID="$CAPTURE_JOB_ID"
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_publish_draft 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_publish_draft

Получить состояние задания

getPublicationJob · ожидаемый HTTP 200

vedismm_example_poll_publication_job() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  EXAMPLE_STATUS=0
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_poll_publication_job 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_poll_publication_job 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_poll_publication_job 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_poll_publication_job 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'GET' \
    'https://vedismm.ru/api/v1/jobs/'"${JOB_ID}" \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '200' ]; then
    printf 'Unexpected HTTP status: expected 200, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_poll_publication_job 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_poll_publication_job

Получить аналитику за произвольный период

getAnalyticsSummary · ожидаемый HTTP 200

vedismm_example_filtered_analytics() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  EXAMPLE_STATUS=0
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_filtered_analytics 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_filtered_analytics 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_filtered_analytics 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_filtered_analytics 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'GET' \
    'https://vedismm.ru/api/v1/analytics/summary?from=2026-06-01&to=2026-07-17&timezone=Europe%2FMoscow&network=vk' \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '200' ]; then
    printf 'Unexpected HTTP status: expected 200, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_filtered_analytics 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_filtered_analytics

Создать webhook endpoint

createWebhook · ожидаемый HTTP 201

vedismm_example_create_webhook() {
  local RESPONSE_HEADERS RESPONSE_BODY REQUEST_BODY HTTP_STATUS
  local BODY_STATUS CURL_STATUS CAPTURE_STATUS EXAMPLE_STATUS
  local CAPTURE_WEBHOOK_ID CAPTURE_WEBHOOK_SECRET
  EXAMPLE_STATUS=0
  REQUEST_BODY="$(jq -cn '{"url":"https://hooks.example.com/vedismm","events":["post.created","publication.completed"]}')"
  BODY_STATUS="$?"
  if [ "$BODY_STATUS" -ne 0 ]; then
    printf 'Request JSON serialization failed.
' >&2
    unset -f vedismm_example_create_webhook 2>/dev/null || true
    return "$BODY_STATUS"
  fi
  RESPONSE_HEADERS="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    unset -f vedismm_example_create_webhook 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_HEADERS" ]; then
    unset -f vedismm_example_create_webhook 2>/dev/null || true
    return 70
  fi
  RESPONSE_BODY="$(mktemp)"
  EXAMPLE_STATUS="$?"
  if [ "$EXAMPLE_STATUS" -ne 0 ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_create_webhook 2>/dev/null || true
    return "$EXAMPLE_STATUS"
  fi
  if [ -z "$RESPONSE_BODY" ]; then
    rm -f "$RESPONSE_HEADERS"
    unset -f vedismm_example_create_webhook 2>/dev/null || true
    return 70
  fi
  HTTP_STATUS="$(curl --silent --show-error --fail-with-body \
    --dump-header "$RESPONSE_HEADERS" \
    --output "$RESPONSE_BODY" \
    --write-out '%{http_code}' \
    --request 'POST' \
    'https://vedismm.ru/api/v1/webhooks' \
    --header 'Authorization: Bearer '"${ACCESS_TOKEN}" \
    --header 'Idempotency-Key: webhook-20260718-0001' \
    --header 'Content-Type: application/json' \
    --data "$REQUEST_BODY")"
  CURL_STATUS="$?"
  cat "$RESPONSE_HEADERS"
  if jq -e . "$RESPONSE_BODY" >/dev/null 2>&1; then jq . "$RESPONSE_BODY"; else cat "$RESPONSE_BODY"; fi
  if [ "$CURL_STATUS" -ne 0 ]; then
    EXAMPLE_STATUS="$CURL_STATUS"
  elif [ "$HTTP_STATUS" != '201' ]; then
    printf 'Unexpected HTTP status: expected 201, received %s.
' "$HTTP_STATUS" >&2
    EXAMPLE_STATUS=65
  fi
  # Failure precedence: body jq, curl, exact HTTP status, then first capture failure.
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_WEBHOOK_ID="$(jq -er '.data.id' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_WEBHOOK_ID" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    CAPTURE_WEBHOOK_SECRET="$(jq -er '.data.secret' "$RESPONSE_BODY")"
    CAPTURE_STATUS="$?"
    if [ "$CAPTURE_STATUS" -ne 0 ] || [ -z "$CAPTURE_WEBHOOK_SECRET" ]; then
      if [ "$CAPTURE_STATUS" -eq 0 ]; then CAPTURE_STATUS=66; fi
      EXAMPLE_STATUS="$CAPTURE_STATUS"
    fi
  fi
  if [ "$EXAMPLE_STATUS" -eq 0 ]; then
    # Captured atomically for the following examples: WEBHOOK_ID, WEBHOOK_SECRET
    export WEBHOOK_ID="$CAPTURE_WEBHOOK_ID"
    export WEBHOOK_SECRET="$CAPTURE_WEBHOOK_SECRET"
  fi
  rm -f "$RESPONSE_HEADERS" "$RESPONSE_BODY"
  unset -f vedismm_example_create_webhook 2>/dev/null || true
  return "$EXAMPLE_STATUS"
}
vedismm_example_create_webhook

Подпись webhook

Получатель проверяет VediSMM-Signature как v1=hex(HMAC-SHA256(secret, timestamp + "." + raw_body)). Используйте точные raw bytes до разбора JSON и constant-time comparison. Канонические заголовки: VediSMM-Event-Id, VediSMM-Delivery-Id, VediSMM-Timestamp, VediSMM-Signature.

Ошибки и заголовки

Ошибки имеют application/problem+json, стабильный code и request_id. Внутренние исключения редактируются. При throttling учитывайте RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset и Retry-After.

Browser CORS по умолчанию выключен и включается точным allowlist. Access-Control-Expose-Headers открывает Request-Id, Idempotency-Replayed, Location, ETag, RateLimit, Retry-After и lifecycle headers.

Операции из OpenAPI

System

Состояние и машинный контракт API.

МетодПутьScopeОписание
GET /api/v1/ping public Проверить доступность API
GET /api/v1/openapi.json public Получить OpenAPI 3.1 контракт

Auth

Opaque access/refresh sessions и восстановление доступа.

МетодПутьScopeОписание
POST /api/v1/auth/register public Зарегистрировать пользователя
POST /api/v1/auth/login public Создать API-сессию
POST /api/v1/auth/refresh public Ротировать refresh token
POST /api/v1/auth/email/verify public Подтвердить e-mail одноразовым кодом
POST /api/v1/auth/email/resend public Повторно запросить код подтверждения
POST /api/v1/auth/forgot-password public Запросить сброс пароля
POST /api/v1/auth/reset-password public Установить новый пароль
POST /api/v1/auth/logout profile:read Отозвать текущую API-сессию
POST /api/v1/auth/logout-all profile:read Отозвать все API-сессии пользователя

Profile

Профиль и пароль текущего пользователя.

МетодПутьScopeОписание
GET /api/v1/me profile:read Получить текущий профиль
PATCH /api/v1/me profile:write Изменить имя или часовой пояс
DELETE /api/v1/me profile:write Поставить удаление аккаунта в очередь
PUT /api/v1/me/password profile:write Изменить пароль и отозвать другие сессии

Audit

Приватная user-visible история действий с redacted metadata и tenant-bound cursor.

МетодПутьScopeОписание
GET /api/v1/audit-events profile:read Получить историю действий текущего пользователя

Sessions

Активные API-сессии пользователя.

МетодПутьScopeОписание
GET /api/v1/sessions profile:read Получить активные API-сессии
GET /api/v1/sessions/{id} profile:read Получить API-сессию
DELETE /api/v1/sessions/{id} profile:write Отозвать API-сессию

Personal tokens

Scoped-токены для серверной автоматизации.

МетодПутьScopeОписание
GET /api/v1/personal-tokens profile:read Получить персональные токены
POST /api/v1/personal-tokens profile:write Создать scoped персональный токен
GET /api/v1/personal-tokens/{id} profile:read Получить персональный токен без raw value
PATCH /api/v1/personal-tokens/{id} profile:write Изменить имя, scopes или срок токена
DELETE /api/v1/personal-tokens/{id} profile:write Отозвать персональный токен
POST /api/v1/personal-tokens/{id}/rotate profile:write Ротировать raw value персонального токена

Preferences

Подписи и шаблоны текущего пользователя.

МетодПутьScopeОписание
GET /api/v1/me/signatures profile:read Получить подписи пользователя
PUT /api/v1/me/signatures profile:write Полностью заменить подписи пользователя
GET /api/v1/me/templates profile:read Получить страницу шаблонов контента
POST /api/v1/me/templates profile:write Создать шаблон контента
GET /api/v1/me/templates/{id} profile:read Получить шаблон контента
PATCH /api/v1/me/templates/{id} profile:write Изменить шаблон с optimistic concurrency
DELETE /api/v1/me/templates/{id} profile:write Удалить актуальную версию шаблона

Networks

Возможности доступных интеграций без секретной конфигурации.

МетодПутьScopeОписание
GET /api/v1/networks networks:read Получить публичные возможности включённых соцсетей
GET /api/v1/networks/{key} networks:read Получить возможности одной соцсети

Connections

OAuth/manual state machine для подключения аккаунтов.

МетодПутьScopeОписание
POST /api/v1/connection-sessions accounts:write Начать OAuth или ручное подключение
GET /api/v1/connection-sessions/{id} accounts:read Получить состояние подключения
DELETE /api/v1/connection-sessions/{id} accounts:write Отменить незавершённое подключение
POST /api/v1/connection-sessions/{id}/confirm accounts:write Подтвердить выбранные provider candidates

Accounts

Подключённые пользовательские аккаунты без provider credentials.

МетодПутьScopeОписание
GET /api/v1/accounts accounts:read Получить страницу подключённых аккаунтов
GET /api/v1/accounts/{id} accounts:read Получить подключённый аккаунт
DELETE /api/v1/accounts/{id} accounts:write Отключить аккаунт и revalidate scheduled posts
POST /api/v1/accounts/{id}/verify accounts:write Перепроверить credentials подключённого аккаунта

Groups

Tenant-scoped группы аккаунтов с exact replacement и optimistic concurrency.

МетодПутьScopeОписание
GET /api/v1/groups groups:read Получить страницу групп аккаунтов
POST /api/v1/groups groups:write Создать группу аккаунтов атомарно
GET /api/v1/groups/{id} groups:read Получить группу аккаунтов
PATCH /api/v1/groups/{id} groups:write Изменить группу с optimistic concurrency
DELETE /api/v1/groups/{id} groups:write Удалить актуальную версию группы
PUT /api/v1/groups/{id}/accounts groups:write Полностью заменить состав группы

Projects

Tenant-scoped профили проектов, политики и приватные брендовые assets.

МетодПутьScopeОписание
GET /api/v1/projects projects:read Получить активные профили проектов
POST /api/v1/projects projects:write Создать серверный профиль проекта
GET /api/v1/projects/{project_id} projects:read Получить профиль проекта
PATCH /api/v1/projects/{project_id} projects:write Изменить актуальную версию проекта
DELETE /api/v1/projects/{project_id} projects:write Архивировать актуальную версию проекта
POST /api/v1/projects/{project_id}/assets projects:write, media:write Связать приватное медиа с проектом

Tracking links

Неизменяемые tenant-scoped короткие ссылки с one-way lifecycle.

МетодПутьScopeОписание
GET /api/v1/tracking-links tracking:read Получить активную cursor-страницу коротких ссылок
POST /api/v1/tracking-links tracking:write Создать неизменяемую короткую ссылку
GET /api/v1/tracking-links/{id} tracking:read Получить свою короткую ссылку
POST /api/v1/tracking-links/{id}/disable tracking:write Отключить короткую ссылку
POST /api/v1/tracking-links/{id}/archive tracking:write Архивировать короткую ссылку

Tracking analytics

Privacy-safe aggregate-first аналитика кликов по коротким ссылкам.

МетодПутьScopeОписание
GET /api/v1/tracking-analytics/summary tracking:read Получить итоговые счётчики переходов
GET /api/v1/tracking-analytics/timeseries tracking:read Получить дневной ряд переходов
GET /api/v1/tracking-analytics/links tracking:read Получить аналитику по коротким ссылкам
GET /api/v1/tracking-analytics/posts tracking:read Получить аналитику по post target
GET /api/v1/tracking-analytics/sources tracking:read Получить безопасные источники переходов
GET /api/v1/tracking-analytics/geo tracking:read Получить coarse географию переходов

Media

Приватная медиатека, content-sniffed загрузки и короткие HMAC-ссылки.

МетодПутьScopeОписание
GET /api/v1/media-content public Получить медиа по короткой HMAC-ссылке
GET /api/v1/media media:read Получить страницу приватной медиатеки
POST /api/v1/media media:write Загрузить файл в приватное хранилище
GET /api/v1/media/{id} media:read Получить метаданные своего медиа
DELETE /api/v1/media/{id} media:write Удалить неиспользуемое медиа
GET /api/v1/media/{id}/content media:read Скачать своё медиа по Bearer ownership

Posts

Транзакционные черновики, preflight, расписание и optimistic concurrency.

МетодПутьScopeОписание
GET /api/v1/posts posts:read Получить cursor-страницу своих публикаций
POST /api/v1/posts posts:write Создать транзакционный черновик
POST /api/v1/posts/constraints posts:read Проверить композицию до планирования или публикации
GET /api/v1/posts/{id} posts:read Получить свою публикацию
PATCH /api/v1/posts/{id} posts:write Изменить актуальную версию draft
DELETE /api/v1/posts/{id} posts:write Удалить актуальную версию draft
POST /api/v1/posts/{id}/schedule posts:publish Запланировать актуальный draft
POST /api/v1/posts/{id}/unschedule posts:publish Вернуть scheduled-публикацию в draft

Publication jobs

Асинхронные tenant-scoped команды публикации, повторов и удаления из соцсетей.

МетодПутьScopeОписание
POST /api/v1/posts/{id}/publish posts:publish Поставить публикацию в устойчивую очередь
POST /api/v1/posts/{id}/retry posts:publish Повторить выбранные неуспешные цели
POST /api/v1/posts/{id}/delete-everywhere posts:publish Поставить удаление опубликованных целей в очередь
GET /api/v1/jobs jobs:read Получить cursor-страницу своих заданий
GET /api/v1/jobs/{job_id} jobs:read Получить своё задание и статусы целей

Plugin publications

Draft, immutable preflight и одноразовое подтверждение публикации из agent clients.

МетодПутьScopeОписание
POST /api/v1/plugin-publications/drafts posts:write Создать draft в контексте версии проекта
POST /api/v1/plugin-publications/{id}/preflight posts:read Получить immutable preflight и одноразовое подтверждение
POST /api/v1/plugin-publications/{id}/publish posts:publish Поставить подтверждённый snapshot в durable queue
POST /api/v1/plugin-publications/{id}/schedule posts:publish Запланировать подтверждённый snapshot

Calendar

Tenant-scoped календарь публикаций с ограниченным диапазоном и cursor pagination.

МетодПутьScopeОписание
GET /api/v1/calendar calendar:read Получить календарь своих публикаций

Analytics

Tenant-scoped метрики публикаций и аудитории со строгими общими фильтрами.

МетодПутьScopeОписание
GET /api/v1/analytics/summary analytics:read Получить итоговые метрики
GET /api/v1/analytics/timeseries analytics:read Получить дневной ряд метрик
GET /api/v1/analytics/networks analytics:read Получить метрики по сетям
GET /api/v1/analytics/audience analytics:read Получить дневную динамику аудитории
GET /api/v1/analytics/posts analytics:read Получить лучшие публикации

Webhooks

Tenant-scoped HTTPS endpoints, одноразовые secrets и durable delivery history.

МетодПутьScopeОписание
GET /api/v1/webhooks webhooks:read Получить webhook endpoints
POST /api/v1/webhooks webhooks:write Создать HTTPS webhook endpoint
GET /api/v1/webhooks/{webhook_id} webhooks:read Получить webhook endpoint без secret
PATCH /api/v1/webhooks/{webhook_id} webhooks:write Изменить endpoint, subscriptions или active state
DELETE /api/v1/webhooks/{webhook_id} webhooks:write Удалить endpoint и delivery history
POST /api/v1/webhooks/{webhook_id}/rotate-secret webhooks:write Атомарно ротировать webhook secret
POST /api/v1/webhooks/{webhook_id}/test webhooks:write Поставить durable test delivery в очередь
GET /api/v1/webhooks/{webhook_id}/deliveries webhooks:read Получить redacted delivery history
GET /api/v1/webhooks/{webhook_id}/deliveries/{delivery_id} webhooks:read Получить redacted delivery
POST /api/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/retry webhooks:write Повторно поставить terminal retryable delivery в очередь

Схемы компонентов

Полные свойства, ограничения, enum и форматы находятся в OpenAPI. Здесь показан автоматически сформированный индекс обязательных полей.

ApiProblem object

Обязательные поля: type, title, status, code, detail, request_id

StatusResponse object

Обязательные поля: data

AcceptedResponse object

Обязательные поля: data

RegisterRequest object

Обязательные поля: name, email, password, accepted_terms

LoginRequest object

Обязательные поля: email, password

RefreshRequest object

Обязательные поля: refresh_token

VerificationRequest object

Обязательные поля: token

EmailRequest object

Обязательные поля: email

ResetPasswordRequest object

Обязательные поля: email, token, password

TokenPair object

Обязательные поля: token_type, access_token, expires_in, refresh_token, refresh_expires_in

TokenPairResponse object

Обязательные поля: data

User object

Обязательные поля: id, name, email, avatar, timezone, status, email_verified_at, created_at

UserResponse object

Обязательные поля: data

ProfileUpdateRequest object

Обязательные поля: нет

PasswordChangeRequest object

Обязательные поля: current_password, password

AccountDeletionRequest object

Обязательные поля: current_password

AccountDeletionJob object

Обязательные поля: id, status, created_at, updated_at, remote_published_posts, remote_post_policy

AccountDeletionResponse object

Обязательные поля: data

AuditEvent object

Обязательные поля: id, action, subject_type, metadata, request_id, created_at

AuditEventListResponse object

Обязательные поля: data, meta

ApiSession object

Обязательные поля: id, client_name, ip_address, user_agent, scopes, current, created_at, last_used_at, expires_at

SessionResponse object

Обязательные поля: data

SessionListResponse object

Обязательные поля: data

Scope string

Обязательные поля: нет

PersonalToken object

Обязательные поля: id, name, prefix, scopes, status, created_at, last_used_at, last_ip, expires_at, rotated_at, revoked_at

PersonalTokenWithSecret object

Обязательные поля: id, name, prefix, scopes, status, created_at, last_used_at, last_ip, expires_at, rotated_at, revoked_at, token

PersonalTokenResponse object

Обязательные поля: data

PersonalTokenSecretResponse object

Обязательные поля: data

PersonalTokenListResponse object

Обязательные поля: data

PersonalTokenCreateRequest object

Обязательные поля: name, scopes

PersonalTokenUpdateRequest object

Обязательные поля: нет

NetworkSignatures object

Обязательные поля: нет

Signature object

Обязательные поля: general, per_network

SignatureReplaceRequest object

Обязательные поля: general, per_network

SignatureResponse object

Обязательные поля: data

ContentTemplate object

Обязательные поля: id, name, text, version, created_at, updated_at

ContentTemplateResponse object

Обязательные поля: data

ContentTemplateListResponse object

Обязательные поля: data, meta

ContentTemplateCreateRequest object

Обязательные поля: name, text

ContentTemplateUpdateRequest object

Обязательные поля: нет

NetworkCapabilities object

Обязательные поля: publish, delete, first_comment, refresh

Network object

Обязательные поля: key, name, short, color, icon, note, enabled, configured, connectable, connection_type, targets, capabilities, limits

NetworkResponse object

Обязательные поля: data

NetworkListResponse object

Обязательные поля: data, meta

ConnectionStartRequest object

Обязательные поля: network, mode

TelegramChannelCredentials object

Обязательные поля: bot_token, channel

VkCommunityCredentials object

Обязательные поля: access_token

MastodonCredentials object

Обязательные поля: instance, access_token

BlueskyCredentials object

Обязательные поля: identifier, app_password

DiscordCredentials object

Обязательные поля: webhook_url

MaxCredentials object

Обязательные поля: bot_token, chat

RutubeCredentials object

Обязательные поля: access_token

ConnectionConfirmRequest object

Обязательные поля: candidate_ids

ConnectionCandidate object

Обязательные поля: id, target_type, external_id, title, username, avatar, already_connected

ConnectionSession object

Обязательные поля: id, network, mode, status, version, failure_code, account_ids, expires_at, completed_at, created_at, updated_at

ConnectionResponse object

Обязательные поля: data

ConnectionStartSession object

Обязательные поля: id, network, mode, status, version, failure_code, account_ids, expires_at, completed_at, created_at, updated_at

ConnectionStartResponse object

Обязательные поля: data

Account object

Обязательные поля: id, network, connection_mode, target_type, external_id, title, username, avatar, status, token_expires_at, last_health_check_at, attention_required, attention_reason, recommended_action, has_error, created_at, updated_at

AccountResponse object

Обязательные поля: data

AccountListResponse object

Обязательные поля: data, meta

AccountVerificationResponse object

Обязательные поля: data, meta

AccountDisconnectResponse object

Обязательные поля: data, meta

GroupCreateRequest object

Обязательные поля: name

GroupUpdateRequest object

Обязательные поля: нет

GroupAccountsReplaceRequest object

Обязательные поля: account_ids

Group object

Обязательные поля: id, name, color, account_ids, member_count, version, created_at, updated_at

GroupResponse object

Обязательные поля: data

GroupListResponse object

Обязательные поля: data, meta

ProjectPolicy object

Закрытая редакционная политика. Raw credentials и credential-shaped значения отклоняются и не возвращаются.

Обязательные поля: нет

ProjectImagePolicy object

Закрытая image policy без credentials и private provider metadata.

Обязательные поля: нет

ProjectPlatformPreference object

Обязательные поля: нет

ProjectPlatformPreferences object

Карта только включённых network keys к закрытым preference objects; credential fields запрещены.

Обязательные поля: нет

ProjectAccountGroup object

Обязательные поля: id, name, color

ProjectTransformationKey string

Lowercase transformation-specific metadata key без credential, attribution author/creator/photographer и private EXIF/GPS names.

Обязательные поля: нет

ProjectTransformationValue composed

Bounded recursive JSON metadata value; runtime additionally rejects high-confidence credential values.

Обязательные поля: нет

ProjectTransformationDescriptor object

Extensible transformation descriptor with bounded safe dynamic fields.

Обязательные поля: нет

ProjectProvenance object

Без provider credentials, author и private EXIF/GPS; source_hash — content digest, не credential.

Обязательные поля: source_kind, source_hash, policy_version, transformations, created_at

ProjectProvenanceRequest object

Immutable EXIF-free provenance input без author или provider credentials.

Обязательные поля: source_kind, source_hash, policy_version, transformations

ProjectAsset object

Приватная tenant-owned media relation без storage path, provider credentials и private EXIF.

Обязательные поля: media_id, role, position, provenance, created_at

Project object

Без internal ID, user_id, storage paths и credentials. Политики server-side являются source of truth.

Обязательные поля: public_id, slug, name, timezone, language, account_group, editorial_policy, image_policy, platform_preferences, assets, version, created_at, updated_at

ProjectCreateRequest object

Обязательные поля: slug, name, timezone, language

ProjectUpdateRequest object

Обязательные поля: нет

ProjectAssetAttachRequest object

Обязательные поля: media_id, role, provenance

ProjectResponse object

Обязательные поля: data

ProjectListResponse object

Обязательные поля: data, meta

PluginDraftRequest object

Обязательные поля: project_id

PluginPreflightRequest object

Обязательные поля: action

PluginPublishRequest object

Обязательные поля: approval_token

PluginScheduleRequest object

Обязательные поля: scheduled_at, approval_token

ApprovalToken object

Одноразовый opaque token возвращается только успешным preflight; digest и snapshot hash никогда не раскрываются.

Обязательные поля: token, expires_at

PublicationPreflightMedia object

Обязательные поля: media_id, sha256

PublicationPreflightDestination object

Обязательные поля: account_id, network, title, username, status

PublicationDeliveryOptions object

Exact delivery-affecting options included in the approval hash; publishable plugin approvals require disabled signature and tracking rewrites.

Обязательные поля: append_signature, tracking

PublicationPreflight object

Sanitized immutable approval snapshot без provider credentials, storage paths и internal approval hashes.

Обязательные поля: post_id, project_id, project_version, post_version, action, scheduled_at, content, link, first_comment, options, content_overrides, media, destinations, constraints, approval

PublicationPreflightResponse object

Обязательные поля: data

TrackingLinkCreateRequest object

Обязательные поля: destination_url

TrackingLink object

Обязательные поля: id, code, short_url, destination_url, version, disabled_at, archived_at, created_at, updated_at

TrackingLinkResponse object

Обязательные поля: data

TrackingLinkListResponse object

Обязательные поля: data, meta

TrackingAnalyticsFilter object

Обязательные поля: from, to, link_id, post_id, network

TrackingAnalyticsSummary object

Обязательные поля: human_clicks, unique_visitors, unknown_bot_clicks, known_bot_clicks

TrackingAnalyticsTimeseriesRow object

Обязательные поля: date, human_clicks, unique_visitors, unknown_bot_clicks, known_bot_clicks

TrackingAnalyticsLinkRow object

Обязательные поля: link_id, code, post_id, post_target_id, network, human_clicks, unique_visitors, unknown_bot_clicks, known_bot_clicks

TrackingAnalyticsPostRow object

Обязательные поля: post_id, post_target_id, title, network, human_clicks, unique_visitors, unknown_bot_clicks, known_bot_clicks

TrackingAnalyticsSourceRow object

Обязательные поля: source_type, value, human_clicks, unique_visitors, unknown_bot_clicks, known_bot_clicks

TrackingAnalyticsResourceMeta object

Обязательные поля: filter

TrackingAnalyticsPageMeta object

Обязательные поля: filter, next_cursor, has_more, limit

TrackingAnalyticsSummaryResponse object

Обязательные поля: data, meta

TrackingAnalyticsTimeseriesResponse object

Обязательные поля: data, meta

TrackingAnalyticsLinkListResponse object

Обязательные поля: data, meta

TrackingAnalyticsPostListResponse object

Обязательные поля: data, meta

TrackingAnalyticsSourceListResponse object

Обязательные поля: data, meta

TrackingGeoCountry object

Обязательные поля: country_code, human_clicks

TrackingGeoFeatureProperties object

Обязательные поля: country_code, region_name, city_name, human_clicks

TrackingGeoPoint object

Обязательные поля: type, coordinates

TrackingGeoFeature object

City feature появляется только при 3 или более human clicks выбранного периода.

Обязательные поля: type, geometry, properties

TrackingGeoFeatureCollection object

Обязательные поля: type, features

TrackingAnalyticsGeo object

Обязательные поля: countries, cities

TrackingAnalyticsGeoResponse object

Обязательные поля: data, meta

TrackingSettings object

Обязательные поля: shorten_links, add_source

PostTrackingRequestOptions object

Обязательные поля: tracking

PostCreateRequest object

Обязательные поля: нет

PostUpdateRequest object

Обязательные поля: нет

PostConstraintRequest object

Обязательные поля: нет

PostScheduleRequest object

Обязательные поля: scheduled_at

PublicationPublishRequest object

Обязательные поля: version

PublicationRetryRequest object

Обязательные поля: target_ids

EmptyJsonObject object

Обязательные поля: нет

WebhookEvent string

Обязательные поля: нет

WebhookCreateRequest object

Обязательные поля: url, events

WebhookUpdateRequest object

Обязательные поля: нет

WebhookEndpoint object

Публичное представление никогда не содержит secret или ciphertext.

Обязательные поля: id, url, events, active, version, created_at, updated_at

WebhookEndpointWithSecret object

Secret показывается только create/rotate и точному idempotency replay.

Обязательные поля: id, url, events, active, version, created_at, updated_at, secret

WebhookResponse object

Обязательные поля: data

WebhookSecretResponse object

Обязательные поля: data

WebhookListResponse object

Обязательные поля: data, meta

WebhookDeliveryResponseMetadata object

Обязательные поля: status, content_type, size_bytes, duration_ms

WebhookDelivery object

Redacted resource: no outbox payload, raw response, endpoint secret, internal row IDs or lease fields.

Обязательные поля: id, event_id, event_type, status, attempts, max_attempts, retryable, available_at, response, error, occurred_at, created_at, updated_at, delivered_at

WebhookDeliveryResponse object

Обязательные поля: data

WebhookDeliveryListResponse object

Обязательные поля: data, meta

PublicationJobTarget object

Обязательные поля: id, account, source_status, status, external_id, external_url, error, attempts, max_attempts, next_attempt_at, completed_at

PublicationJob object

Публичное представление намеренно не содержит lease owner/token, request ID и сырые provider payloads.

Обязательные поля: id, post_id, type, status, attempts, max_attempts, error_code, targets, created_at, started_at, updated_at, finished_at

PublicationJobResponse object

Обязательные поля: data

PublicationJobListResponse object

Обязательные поля: data, meta

ConstraintIssue object

Обязательные поля: severity, message, fix

PostConstraintReport object

Обязательные поля: ok, blocking, publishable, by_network, requirements, networks, target_count, media_count

PostConstraintResponse object

Обязательные поля: data

PostTarget object

Обязательные поля: id, account, status, external_url, error, attempts

Post object

Обязательные поля: id, title, content, link, status, version, scheduled_at, published_at, options, account_ids, targets, media, created_at, updated_at

PostResponse object

Обязательные поля: data

PostMedia object

Стабильная immutable-сводка медиа внутри versioned Post. Короткие capability URLs доступны в отдельном Media resource и намеренно не входят в сильный ETag поста.

Обязательные поля: id, type, original_name, mime, extension, size, width, height, duration, resource_url

PostListResponse object

Обязательные поля: data, meta

CalendarEvent object

Обязательные поля: id, title, status, version, event_at, scheduled_at, published_at, networks

CalendarEventListResponse object

Обязательные поля: data, meta

AnalyticsFilter object

Обязательные поля: from, to, timezone, network, account_id, group_id, account_ids

AnalyticsResourceMeta object

Обязательные поля: filter

AnalyticsPageMeta object

Обязательные поля: filter, next_cursor, has_more, limit

AnalyticsMetrics object

Обязательные поля: posts, reach, impressions, likes, comments, shares, clicks, engagement

AnalyticsMetricAvailability array

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

Обязательные поля: нет

AnalyticsAudienceAvailability array

Детерминированный список доступных показателей аудитории.

Обязательные поля: нет

AnalyticsSummary object

Обязательные поля: metrics, available_metrics, is_demo

AnalyticsSummaryResponse object

Обязательные поля: data, meta

AnalyticsMetricRow object

Обязательные поля: date, reach, impressions, likes, comments, shares, clicks, engagement, available_metrics, is_demo

AnalyticsNetworkRow object

Обязательные поля: network, posts, reach, impressions, likes, comments, shares, clicks, engagement, available_metrics, is_demo

AnalyticsAudienceRow object

Обязательные поля: date, followers, reach, available_metrics, is_demo

AnalyticsPost object

Обязательные поля: id, title, published_at, networks, reach, impressions, likes, comments, shares, clicks, engagement, available_metrics, is_demo

AnalyticsTimeseriesResponse object

Обязательные поля: data, meta

AnalyticsNetworksResponse object

Обязательные поля: data, meta

AnalyticsAudienceResponse object

Обязательные поля: data, meta

AnalyticsPostListResponse object

Обязательные поля: data, meta

MediaUploadRequest object

Обязательные поля: file

Media object

Обязательные поля: id, type, original_name, mime, extension, size, width, height, duration, status, content_url, thumbnail_url, created_at, updated_at

MediaResponse object

Обязательные поля: data

MediaListResponse object

Обязательные поля: data, meta