Пользовательский 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 через реальные контроллеры и сервисы.
Модель работы
- Получите короткую access session через POST /auth/login либо создайте PAT с минимальными scopes.
- Создайте draft через POST /posts; сохраняйте ETag и идентификатор.
- Проверьте constraints, затем выполните schedule или создайте команду publish.
- Команды публикации отвечают 202; состояние и результаты целей читайте через /jobs.
- Для событий используйте подписанные 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 и доступен по неизменяемому тегу. Отметка о разработке означает, что репозиторий уже зарезервирован, но ещё не имеет стабильного релиза.
npm install github:VediSMM/sdk-typescript#v0.1.1
Python
sdk-python
Опубликовано
Релиз v0.1.1
python -m pip install "vedismm @ git+https://github.com/VediSMM/sdk-python.git@v0.1.1"
PHP
sdk-php
Опубликовано
Релиз v0.1.1
composer config repositories.vedismm-sdk vcs https://github.com/VediSMM/sdk-php && composer require vedismm/sdk:0.1.1
Go
sdk-go
Опубликовано
Релиз v0.1.1
go get github.com/VediSMM/sdk-go@v0.1.1
Java
sdk-java
Опубликовано
Релиз v0.1.1
git clone --branch v0.1.1 --depth 1 https://github.com/VediSMM/sdk-java.git && cd sdk-java && ./mvnw -q -DskipTests install
.NET / C#
sdk-dotnet
Опубликовано
Релиз v0.1.1
git clone --branch v0.1.1 --depth 1 https://github.com/VediSMM/sdk-dotnet.git && cd sdk-dotnet && dotnet pack src/VediSMM/VediSMM.csproj --configuration Release --output ./artifacts
Ruby
sdk-ruby
В разработке
Swift
sdk-swift
В разработке
Исполняемые примеры
Нужны 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