Открыть меню
Переключить меню настроек
Открыть персональное меню
Вы не представились системе
Ваш IP-адрес будет виден всем, если вы внесёте какие-либо изменения.

CDN:API Docs

Материал из DenchikP Docs
Версия от 00:45, 17 июня 2026; Denchikp (обсуждение | вклад)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

CDN API (Reference)

Общие требования

Base URL: https://cdnapi.denchikp.com
CDN URL файлов: https://cdn.denchikp.com/<domain>/<folder>/<filename>

Большинство CDN‑эндпоинтов требуют:

  • заголовок X-Api-Key: <API_KEY>

или

  • активную cookie‑сессию, где ключ может резолвиться автоматически (OAuth/CDN login + bind).

Во многих операциях, где передаётся url, принимаются только ссылки вида:

https://cdn.denchikp.com/<domain>/<folder>/<filename>

Ключ / домены / квоты

GET /api-key/me

Информация о текущем API‑ключе (по X-Api-Key или по сессии).

Headers:

  • X-Api-Key: <API_KEY> (рекомендуется)

Response 200:

{
  "status":"ok",
  "key":{
    "name":"ClientName",
    "is_admin":false,
    "domains":["public"],
    "ips":["1.2.3.4"]
  }
}

Ошибки:

  • 401 — нет ключа и нет валидной авторизации

GET /domains

Список доменов, доступных для ключа.

Response 200 (примеры):

  • admin‑ключ:
{"status":"ok","mode":"any","domains":[]}
  • allowlist‑ключ:
{"status":"ok","mode":"allowlist","domains":["public","example.com"]}
  • ключ без доменов (разрешён как минимум public):
{"status":"ok","mode":"any","domains":["public"]}

GET /usage

Текущее использование (без лимитов квоты).

Response 200 (пример):

{
  "status":"ok",
  "usage":{
    "total_files":120,
    "total_bytes":123456789,
    "month":"2026-06",
    "monthly_uploaded_files":10,
    "monthly_uploaded_bytes":12300000
  }
}

GET /quota

Квоты + использование + остатки (remaining).

Response 200 (пример):

{
  "status":"ok",
  "api_key_name":"ClientName",
  "is_admin":false,
  "quota":{
    "max_file_bytes":524288000,
    "max_total_bytes":10737418240,
    "max_total_files":50000,
    "max_monthly_upload_bytes":5368709120,
    "max_monthly_upload_files":null
  },
  "usage":{
    "total_files":120,
    "total_bytes":123456789,
    "month":"2026-06",
    "monthly_uploaded_files":10,
    "monthly_uploaded_bytes":12300000
  },
  "remaining":{
    "total_bytes":10000000000,
    "total_files":49880,
    "monthly_upload_bytes":5300000000,
    "monthly_upload_files":null
  }
}

Ошибки:

  • 403 — если ключ невалиден/запрещён по IP и т.п.

Файлы (БД / meta / exists)

GET /files

Список «моих» файлов из БД.

Headers:

  • X-Api-Key: <API_KEY>

Query parameters (все опциональны):

Name Type Default Description
q string поиск по filename/original_filename (LIKE)
domain string фильтр домена (нормализуется)
folder string фильтр папки
ext string фильтр расширения
exists_on_disk bool true фильтр по наличию на диске
limit int 50 максимум 200
offset int 0 смещение

Response 200 (пример):

{
  "status":"ok",
  "total":123,
  "limit":50,
  "offset":0,
  "files":[
    {
      "id":1,
      "domain":"public",
      "folder":"img",
      "filename":"a.png",
      "original_filename":"cat.png",
      "url":"https://cdn.denchikp.com/public/img/a.png",
      "size":12345,
      "created_at":"2026-06-16T10:00:00",
      "exists_on_disk":true
    }
  ]
}

POST /file/meta

Метаданные файла: сведения из БД + проверка на диске.

Body (JSON):

{"url":"https://cdn.denchikp.com/public/img/a.png"}

Response 200 (схема):

{
  "status":"ok",
  "url":"...",
  "domain":"public",
  "folder":"img",
  "filename":"a.png",
  "db":{
    "id":1,
    "owner":"ClientName",
    "api_key_id":10,
    "size":12345,
    "exists_on_disk":true,
    "created_at":"..."
  },
  "disk":{
    "exists":true,
    "size":12345,
    "mime_type":"image/png"
  }
}

Ошибки:

  • 400 — URL не CDN формата/не парсится
  • 403 — нет доступа к домену
  • 401 — нет ключа

POST /file/exists

Проверка существования файла (в БД и на диске).

Body (JSON):

{"url":"https://cdn.denchikp.com/public/img/a.png"}

Response 200:

{"status":"ok","exists_in_db":true,"exists_on_disk":true}

GET /file/redirect

Публичный редирект на CDN URL.

Query:

Ошибки:

Загрузка

POST /upload

Загрузка файла (multipart/form-data).

Headers:

  • X-Api-Key: <API_KEY>

Content-Type: multipart/form-data Max size: 500 MB

Form fields:

Field Type Required Default Description
file file да загружаемый файл
domain string нет "" "" → public
name_mode string нет uuid original | custom
custom_filename string нет "" используется при name_mode=custom
conflict_action string нет "" overwrite → перезаписать (с проверками прав)

Response 200 (пример):

{
  "status":"ok",
  "url":"https://cdn.denchikp.com/public/img/...",
  "size":12345,
  "owner":"ClientName",
  "ip":"1.2.3.4",
  "domain":"public",
  "folder":"img",
  "filename":"..."
}

Response 409 (конфликт имени, если не overwrite):

{
  "status":"conflict",
  "message":"FILE_EXISTS",
  "requested_filename":"a.png",
  "existing_url":"https://cdn.denchikp.com/public/img/a.png",
  "domain":"public",
  "folder":"img"
}

Ошибки (частые):

  • 403 — квота превышена / домен не разрешён / IP не разрешён / overwrite запрещён
  • 413 — файл слишком большой
  • 500 — внутренняя ошибка

Webhook event: UPLOAD (payload.source="api")

Пример (curl):

curl -X POST "https://cdnapi.denchikp.com/upload" \
  -H "X-Api-Key: <API_KEY>" \
  -F "domain=public" \
  -F "name_mode=uuid" \
  -F "file=@./cat.png"

POST /upload/by-url

Сервер скачивает файл по ссылке и сохраняет в CDN.

Body (JSON):

{
  "source_url":"https://example.com/file.png",
  "domain":"public",
  "folder":"img"
}

Ограничения:

  • только http/https
  • SSRF‑защита: запрещено скачивать с private/loopback/link-local/reserved/multicast адресов
  • max 500 MB

Response 200 (пример):

{"status":"ok","url":"https://cdn.denchikp.com/public/img/...","size":12345,"domain":"public","folder":"img"}

Webhook event: UPLOAD (payload.source="by_url")

Multipart Upload

POST /upload/session/start

Создать multipart‑сессию.

Body (JSON):

{
  "domain":"public",
  "ext":"mp4",
  "mime_type":"video/mp4",
  "folder":"files"
}

Response 200:

{"status":"ok","upload_id":"<uuid>","chunk_size":8388608}

PUT /upload/session/{upload_id}/part/{part_no}

Загрузить часть файла (chunk).

Content-Type: multipart/form-data Form-data:

  • file — chunk

Response 200:

{"status":"ok","part_no":1,"bytes":8388608}

Ошибки:

  • 404 — upload_id не найден
  • 403 — upload_id принадлежит другому ключу (для не‑админов)

POST /upload/session/{upload_id}/complete

Склеить части и создать итоговый файл.

Ошибки:

  • 400 — нет частей
  • 413 — итоговый файл > 500MB
  • 403 — квота/права

Webhook event: UPLOAD (payload.source="multipart")


POST /upload/session/{upload_id}/abort

Отменить multipart‑сессию и удалить временные части.

Response 200:

{"status":"ok"}

Операции с файлами

POST /files/bulk-delete

Удалить много файлов за один запрос (до 500 URL).

Body (JSON):

{"urls":["https://cdn.denchikp.com/public/files/a.txt","..."]}

Response 200 (пример):

{
  "status":"ok",
  "results":[
    {"url":"...","status":"ok"},
    {"url":"...","status":"error","error":"..."}
  ]
}

Webhook event: BULK_DELETE


POST /file/move

Перемещение/переименование файла (можно менять домен/папку/имя).

Body (JSON):

{
  "url":"https://cdn.denchikp.com/public/img/a.png",
  "new_domain":"public",
  "new_folder":"files",
  "new_filename":"b.png"
}

Правила:

  • new_folder должен быть одним из: img/css/js/fonts/files
  • new_filename не должен содержать .., /, \

Response 200:

{"status":"ok","from":"https://.../a.png","to":"https://.../b.png"}

Webhook event: FILE_MOVE


POST /file/rename

Переименовать файл (alias для /file/move, но меняется только имя).

Body (JSON):

{
  "url":"https://cdn.denchikp.com/public/img/a.png",
  "new_filename":"b.png"
}

Ошибки:

  • 400 — new_filename обязателен

POST /file/copy

Копирование файла (квоты учитываются как новая загрузка).

Body (JSON):

{
  "url":"https://cdn.denchikp.com/public/img/a.png",
  "target_domain":"public",
  "target_folder":"img",
  "target_filename":"copy.png"
}

Response 200:

{"status":"ok","url":"https://cdn.denchikp.com/public/img/copy.png","size":12345}

Webhook event: FILE_COPY


POST /file/hash

Посчитать хэш файла.

Body (JSON):

{"url":"https://cdn.denchikp.com/public/files/a.bin","algo":"sha256"}

algo: sha256 | sha1 | md5

Response 200:

{"status":"ok","algo":"sha256","hash":"<hex>","url":"https://..."}

Browse / Folders / Download

POST /browse

Просмотр файлов на диске в домене.

Body (JSON):

{"domain":"public","folder":"img","filter":"cat"}

Поля:

  • domain — обязательно
  • folder — опционально
  • filter — подстрока в имени файла (опционально)

Response 200 (схема):

{"status":"ok","domain":"public","total_files":2,"files":[{"name":"a.png","path":"img/a.png","size":12345,"url":"..."}]}

GET /folders/{domain}

Список папок домена + агрегаты.

Response 200 (пример):

{
  "status":"ok",
  "domain":"public",
  "total_files":10,
  "total_size":1234567,
  "folders":[{"name":"img","file_count":5,"size":1000000,"path":"public/img"}]
}

GET /download/{domain}/{folder}/{filename:path}

Скачивание файла.

Важно: хотя параметр {filename:path} технически может содержать “/”, сервер отклонит имя, если в нём есть / или \ или ...

Response 200:

  • бинарный поток (FileResponse), application/octet-stream

Ошибки:

  • 404 — файл не найден
  • 403 — нет доступа к домену

Validate URL

POST /validate

Валидация ссылки (http/https). Делает HEAD, при необходимости GET range 0-0, возвращает HTTP статус и заголовки. Если URL — CDN URL, дополнительно парсит domain/folder/filename и проверяет наличие на диске.

Body (JSON):

{"url":"https://example.com/file.png"}

Response 200 (схема, укорочено):

{
  "status":"ok",
  "checked_by":"ClientName",
  "url":"https://example.com/file.png",
  "is_valid_url":true,
  "is_cdn_url":false,
  "available":true,
  "http_status":200,
  "headers":{"content_type":"image/png","content_length":"12345","last_modified":null,"etag":null,"server":"nginx"},
  "file_info":{"filename":"file.png","extension":"png","host":"example.com","path":"/file.png"},
  "cdn_info":null,
  "error":null
}

Delete file

DELETE /delete

Удаление файла по CDN URL.

Body (JSON):

{"url":"https://cdn.denchikp.com/public/files/a.txt"}

Response 200 (пример):

{
  "status":"ok",
  "message":"Файл успешно удален",
  "domain":"public",
  "folder":"files",
  "filename":"a.txt",
  "url":"https://cdn.denchikp.com/public/files/a.txt",
  "owner":"ClientName",
  "ip":"1.2.3.4"
}

Webhook event: DELETE (payload.source="api")

Text Editor (Read/Save)

Только для “текстовых” файлов и ограничение по размеру: ≤ 2 MB.


POST /file/content

Получить содержимое текстового файла.

Body (JSON):

{"url":"https://cdn.denchikp.com/public/files/app.js"}

Response 200 (пример):

{
  "status":"ok",
  "url":"https://cdn.denchikp.com/public/files/app.js",
  "domain":"public",
  "folder":"files",
  "filename":"app.js",
  "mime_type":"application/javascript",
  "size":1200,
  "content":"console.log('hi');\n"
}

Ошибки:

  • 400 — файл не текстовый / >2MB / неподдерживаемая кодировка
  • 404 — файл не найден

POST /file/save

Сохранить содержимое текстового файла.

Body (JSON):

{"url":"https://cdn.denchikp.com/public/files/app.js","content":"console.log('hi');\n"}

Response 200:

{"status":"ok","message":"Файл успешно сохранен","url":"...","domain":"public","folder":"files","filename":"app.js","size":1200}

Webhook event: FILE_SAVE


Webhooks (Reference)

Webhooks позволяют получать события от сервиса (UPLOAD/DELETE и т.д.) на ваш URL.

Создание / управление

POST /webhooks

Создать webhook.

Headers:

  • X-Api-Key: <API_KEY>

Body (JSON):

{
  "url":"https://example.com/hook",
  "events":["UPLOAD","DELETE"],
  "secret":"optional",
  "enabled":true
}

Response 200:

{"status":"ok","id":123}

Ошибки:

  • 401/403 — нет доступа

GET /webhooks

Список webhooks текущего ключа.

Response 200 (пример):

{
  "status":"ok",
  "webhooks":[
    {
      "id":123,
      "url":"https://example.com/hook",
      "events":["UPLOAD","DELETE"],
      "enabled":true,
      "created_at":"2026-06-16T12:00:00"
    }
  ]
}

DELETE /webhooks/{webhook_id}

Удалить webhook.

Path params:

  • webhook_id — integer

Response 200:

{"status":"ok"}

Ошибки:

  • 404 — webhook не найден (или не принадлежит ключу)

Delivery format

Сервер отправляет на URL webhook’а запрос:

  • Method: POST
  • Content-Type: application/json
  • Body:
{"event":"UPLOAD","payload":{...}}

События

События, которые реально используются в текущем коде:

  • UPLOAD
  • DELETE
  • BULK_DELETE
  • FILE_MOVE
  • FILE_COPY
  • FILE_SAVE
  • SCAN_FINISHED

Подпись (HMAC)

Если у webhook задан secret и на сервере включена подпись, рекомендуется добавлять:

Headers:

  • X-Webhook-Event: <EVENT>
  • X-Webhook-Timestamp: <unix_seconds>
  • X-Webhook-Signature: sha256=<hex>

Алгоритм:

  • base = {timestamp}.{raw_body}
  • signature = HMAC_SHA256(secret, base) (hex)
  • получатель проверяет подпись и свежесть timestamp (например ±300 секунд)

Примечание: в текущем коде поле secret хранится, но подпись нужно добавить отдельным патчем (если ещё не добавлено).

Bot (internal)

Все эндпоинты /bot/* требуют заголовок X-Bot-Token.

Header Обязательно Описание
X-Bot-Token да внутренний токен, должен совпадать с DISCORD_BOT_INTERNAL_TOKEN на сервере

Discord binding

POST /bot/discord/bind

Привязать Discord пользователя к API‑ключу (для бота).

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Body (JSON):

{
  "discord_id": "1234567890",
  "api_key": "<API_KEY>",
  "username": "optional",
  "global_name": "optional",
  "email": "optional",
  "avatar": "optional"
}

Response 200 (пример):

{
  "status": "ok",
  "message": "Discord аккаунт успешно привязан к API ключу",
  "discord_id": "1234567890",
  "key_info": {
    "name": "ClientName",
    "domains": ["public"],
    "enabled": true,
    "is_admin": false,
    "ips": []
  }
}

Ошибки:

  • 403 — недействительный X-Bot-Token
  • 400 — API ключ не существует

POST /bot/discord/unbind

Отвязать Discord пользователя от API‑ключа.

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Body (JSON):

{ "discord_id": "1234567890" }

Response 200:

{ "status": "ok", "message": "Discord аккаунт успешно отвязан от API ключа" }

Ошибки:

  • 403 — недействительный X-Bot-Token
  • 404 — привязка не найдена

GET /bot/discord/me/{discord_id}

Получить информацию о привязке Discord пользователя.

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Path params:

  • discord_id — Discord ID

Response 200 (пример, если привязан):

{
  "authenticated": true,
  "bound": true,
  "discord_id": "1234567890",
  "user": { "name": "Display Name", "email": "[email protected]", "avatar": "https://..." },
  "key_info": { "name":"ClientName","domains":["public"],"enabled":true,"is_admin":false,"ips":[] }
}

Response 404 (если не привязан):

{ "authenticated": false, "bound": false }

Bot files

POST /bot/upload

Загрузка файла через Discord‑бота (multipart/form-data).

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Content-Type: multipart/form-data Max file size: 500 MB

Form fields:

Field Type Required Default Description
discord_id string да Discord ID
file file да файл
domain string нет "" "" → public
name_mode string нет uuid original | custom
custom_filename string нет "" используется при custom
conflict_action string нет "" overwrite → перезаписать (с проверками)

Response 200 (пример):

{
  "status":"ok",
  "url":"https://cdn.denchikp.com/public/img/...",
  "size":12345,
  "owner":"ClientName",
  "domain":"public",
  "folder":"img",
  "filename":"..."
}

Response 409 (если имя занято и overwrite не задан):

{
  "status":"conflict",
  "message":"FILE_EXISTS",
  "requested_filename":"a.png",
  "existing_url":"https://cdn.denchikp.com/public/img/a.png",
  "domain":"public",
  "folder":"img"
}

Ошибки (частые):

  • 401 — Discord аккаунт не привязан к API ключу
  • 403 — нет доступа к домену / квота / overwrite запрещён
  • 413 — файл слишком большой
  • 500 — внутренняя ошибка

Пример (curl):

curl -X POST "https://cdnapi.denchikp.com/bot/upload" \
  -H "X-Bot-Token: <BOT_TOKEN>" \
  -F "discord_id=1234567890" \
  -F "domain=public" \
  -F "name_mode=uuid" \
  -F "file=@./cat.png"

DELETE /bot/delete

Удалить файл через бота.

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Body (JSON):

{ "discord_id":"1234567890", "url":"https://cdn.denchikp.com/public/files/a.txt" }

Response 200 (пример):

{
  "status":"ok",
  "message":"Файл успешно удален",
  "domain":"public",
  "folder":"files",
  "filename":"a.txt",
  "url":"https://cdn.denchikp.com/public/files/a.txt",
  "owner":"ClientName"
}

Ошибки:

  • 400 — некорректный URL / путь не файл
  • 401 — Discord аккаунт не привязан
  • 403 — нет доступа к домену
  • 404 — файл не найден

GET /bot/download/{discord_id}/{domain}/{folder}/{filename:path}

Получить ссылки на скачивание (CDN URL + API download URL).

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Response 200 (пример):

{
  "status":"ok",
  "owner":"ClientName",
  "domain":"public",
  "folder":"img",
  "filename":"a.png",
  "size":12345,
  "cdn_url":"https://cdn.denchikp.com/public/img/a.png",
  "api_download_url":"https://cdnapi.denchikp.com/download/public/img/a.png"
}

Ошибки:

  • 400 — недопустимое имя файла (.. или / или \)
  • 401 — Discord аккаунт не привязан
  • 403 — нет доступа к домену
  • 404 — файл не найден

POST /bot/browse

Просмотр файлов домена через бота.

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Body (JSON):

{ "discord_id":"1234567890", "domain":"public", "folder":"img", "filter":"cat" }

Response 200 (пример, укорочено):

{ "status":"ok", "domain":"public", "total_files":1, "files":[{"name":"a.png","path":"img/a.png","size":12345,"url":"..."}] }

GET /bot/folders/{discord_id}/{domain}

Папки домена через бота + статистика по папкам.

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Response 200 (пример):

{
  "status":"ok",
  "domain":"public",
  "total_files":10,
  "total_size":1234567,
  "folders":[{"name":"img","file_count":5,"size":1000000,"path":"public/img"}]
}

POST /bot/validate

Валидация ссылки через бота (HEAD/GET, статус и заголовки). Для CDN URL также возвращает parsed info и exists_on_disk.

Headers:

  • X-Bot-Token: <BOT_TOKEN>

Body (JSON):

{ "discord_id":"1234567890", "url":"https://example.com/file.png" }

Response 200 (схема, укорочено):

{
  "status":"ok",
  "checked_by":"ClientName",
  "url":"https://example.com/file.png",
  "is_valid_url":true,
  "is_cdn_url":false,
  "available":true,
  "http_status":200,
  "headers":{"content_type":"image/png","content_length":"12345"},
  "file_info":{"filename":"file.png","extension":"png","host":"example.com","path":"/file.png"},
  "cdn_info":null,
  "error":null
}

POST /bot/file/content

Получить содержимое текстового файла (предпросмотр для бота).

Ограничения:

  • только текстовые файлы
  • размер ≤ 2 MB

Body (JSON):

{ "discord_id":"1234567890", "url":"https://cdn.denchikp.com/public/files/app.js" }

Response 200 (пример):

{
  "status":"ok",
  "url":"https://cdn.denchikp.com/public/files/app.js",
  "domain":"public",
  "folder":"files",
  "filename":"app.js",
  "mime_type":"application/javascript",
  "size":1200,
  "content":"console.log('hi');\n"
}

Bot stats

GET /bot/public-summary

Публичная статистика CDN (вызов внутри бота). Фактически возвращает то же, что GET /stats/public-summary, но требует X-Bot-Token.

Response 200 (пример):

{
  "status":"ok",
  "stats":{
    "uploaded_files":1000,
    "current_files":900,
    "total_size_bytes":123456789,
    "total_size_mb":117.74,
    "domains":10,
    "clients":3,
    "registered_api_clients":5
  }
}

GET /bot/stats/{discord_id}

Админ‑статистика для бота: доступно только если привязанный к discord_id ключ имеет is_admin=true. Возвращает ту же структуру, что /bot/public-summary.

Ошибки:

  • 403 — только администраторы могут просматривать статистику
  • 401 — Discord аккаунт не привязан к API ключу
Содержание