DP‑CDN‑API (v4.4.0)
Обзор
Base URL: https://cdnapi.denchikp.com
CDN URL файлов: https://cdn.denchikp.com/<domain>/<folder>/<filename>
Rate limits: не применяются (не реализованы)
DP‑CDN‑API — API для загрузки/удаления/просмотра файлов в CDN, управления квотами, вебхуками, статистикой и админ‑операциями. Также есть внутренние эндпоинты для Discord‑бота.
Важно: не публикуйте реальные API‑ключи. Если ключ попал в публичный доступ — выполните ротацию ключа (см. Admin → Rotate).
Термины и ограничения
Домен (domain)
- Если
domainпустой или не передан — используетсяpublic. - Разрешён только основной домен: ровно одна точка (пример:
example.com). Поддомены запрещены. - Запрещены символы/паттерны:
..,/,\. - Значение
publicразрешено.
Папка (folder)
Допустимые папки (строго):
img,css,js,fonts,files
Лимиты
- Максимальный размер одного файла: 500 MB
- Предпросмотр/редактор текста: максимум 2 MB
- Bulk delete: максимум 500 URL за запрос
GET /files:limitмаксимум 200
Аутентификация (Security)
API Key (основной способ)
Передавайте ключ в заголовке:
| Header | Тип | Обязательно | Описание |
|---|---|---|---|
| X-Api-Key | string | да* | API‑ключ клиента |
Примечание: часть эндпоинтов может работать через cookie‑сессию (OAuth/CDN login), но для API‑интеграций рекомендуется всегда использовать X-Api-Key.
Ограничения ключа
enabled=false→ 403- IP allowlist: если у ключа задан список IP, запросы разрешены только с этих IP (используется
request.client.host) - Domain allowlist: если задан список доменов — доступ только к ним (и/или
public, если он в списке) is_admin=true: доступ к админ‑эндпоинтам, SSE и админ‑статистике
Cookie‑сессии (для браузера)
Cookie‑сессия создаётся после:
- OAuth:
/auth/google,/auth/discord,/auth/github(+ callback) - CDN login по ключу:
/auth/cdn/login
Если OAuth‑аккаунт привязан к API‑ключу (/auth/bind-api-key), ключ может резолвиться из сессии без заголовка.
Bot token (внутреннее API для Discord‑бота)
Все эндпоинты /bot/* требуют заголовок:
| Header | Обязательно | Описание |
|---|---|---|
| X-Bot-Token | да | должен совпадать с DISCORD_BOT_INTERNAL_TOKEN на сервере |
Права и квоты
Админ‑доступ (is_admin)
Только админам:
/admin/*/events(SSE)- большинство
/stats/*(кроме публичной сводки)
Квоты (для не‑админов)
Квоты применяются к операциям загрузки/копирования:
max_file_bytesmax_total_bytesmax_total_filesmax_monthly_upload_bytesmax_monthly_upload_files
Эндпоинты квот/использования:
GET /quotaGET /usage
Админ меняет квоты:
PUT /admin/api-keys/{api_key_id}/quota
Ошибки (Errors)
| Код | Значение |
|---|---|
| 400 | Некорректные параметры / URL / имя файла / папка |
| 401 | Нет ключа (и нет валидной сессии) |
| 403 | Запрещено: ключ отключен / IP не разрешён / домен не разрешён / не админ / квоты |
| 404 | Не найдено (файл, upload_id, webhook, ключ, запись) |
| 409 | Конфликт имени файла (если не overwrite) |
| 413 | Слишком большой файл |
| 500 | Внутренняя ошибка |
FastAPI обычно возвращает:
{"detail":"..."}
Пример конфликта при загрузке (409):
{
"status": "conflict",
"message": "FILE_EXISTS",
"requested_filename": "a.png",
"existing_url": "https://cdn.denchikp.com/public/img/a.png",
"domain": "public",
"folder": "img"
}
Monitoring
GET /healthz
Проверка «жив».
Response 200
{"status":"ok"}
GET /readyz
Проверка готовности: MySQL + BASE_PATH + свободное место.
Response 200
{
"status": "ready",
"mysql": "ok",
"storage": {
"free_bytes": 123,
"total_bytes": 456
}
}
GET /version
Версия сервиса.
Response 200
{
"service": "DP-CDN-API",
"version": "4.4.0",
"time": "2026-06-16T12:00:00"
}
GET /status
Сводка состояния API+CDN (файлы/размер/домены, версия API).
CDN Core
GET /api-key/me
Информация о текущем ключе.
Auth: X-Api-Key или cookie‑сессия
Response 200
{
"status": "ok",
"key": {
"name": "ClientName",
"is_admin": false,
"domains": ["public"],
"ips": ["1.2.3.4"]
}
}
GET /domains
Доступные домены для ключа.
Auth: требуется
Response 200 (пример)
{"status":"ok","mode":"allowlist","domains":["public","example.com"]}
GET /usage
Текущее использование (без лимитов).
GET /quota
Квота и текущее использование (usage + 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
}
}
Files (DB / Meta)
GET /files
Список «моих» файлов из БД (с пагинацией).
Auth: требуется
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 | max 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
Метаданные файла (диск + БД).
Auth: требуется Body:
{"url":"https://cdn.denchikp.com/public/img/a.png"}
POST /file/exists
Проверка существования файла (в БД и на диске).
Auth: требуется Body:
{"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:
?url=https://cdn.denchikp.com/...
Upload
POST /upload
Загрузка файла (multipart/form-data).
Auth: требуется Max size: 500 MB
Form fields:
| Field | Type | Required | Description |
|---|---|---|---|
| file | file | да | загружаемый файл |
| domain | string | нет | "" → public |
| name_mode | string | нет | original | custom |
| custom_filename | string | нет | используется при name_mode=custom |
| conflict_action | string | нет | overwrite → перезаписать (с проверками прав) |
Пример:
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"
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 (конфликт) — см. раздел Ошибки.
Webhook event: UPLOAD (payload.source="api")
POST /upload/by-url
Сервер скачивает файл по URL и кладёт в CDN.
Auth: требуется Body:
{"source_url":"https://example.com/file.png","domain":"public","folder":"img"}
Ограничения:
- только http/https
- SSRF защита: запрещены private/loopback/link-local/reserved адреса
- max 500 MB
Webhook event: UPLOAD (payload.source="by_url")
Multipart Upload
POST /upload/session/start
Создать multipart‑сессию.
Auth: требуется Body:
{"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}
Загрузка части (multipart/form-data).
Auth: требуется Form-data:
file— chunk
Response 200
{"status":"ok","part_no":1,"bytes":8388608}
POST /upload/session/{upload_id}/complete
Склеивает части, создаёт итоговый файл, проверяет квоты и max size.
Auth: требуется
Webhook event: UPLOAD (payload.source="multipart")
POST /upload/session/{upload_id}/abort
Отмена/очистка multipart‑сессии.
Auth: требуется
File Operations
POST /files/bulk-delete
Удалить множество файлов (до 500 URL).
Auth: требуется Body:
{"urls":["https://cdn.denchikp.com/public/files/a.txt","..."]}
Response 200 (пример)
{
"status": "ok",
"results": [
{"url":"https://cdn.denchikp.com/public/files/a.txt","status":"ok"},
{"url":"https://cdn.denchikp.com/public/files/b.txt","status":"error","error":"..."}
]
}
Webhook event: BULK_DELETE
POST /file/move
Перемещение файла.
Auth: требуется Body:
{
"url":"https://cdn.denchikp.com/public/img/a.png",
"new_domain":"public",
"new_folder":"files",
"new_filename":"b.png"
}
Webhook event: FILE_MOVE
POST /file/rename
Alias для /file/move. Требует new_filename, домен/папка не меняются.
POST /file/copy
Копирование файла (учитывает квоты как новая загрузка).
Auth: требуется Body:
{
"url":"https://cdn.denchikp.com/public/img/a.png",
"target_domain":"public",
"target_folder":"img",
"target_filename":"copy.png"
}
Webhook event: FILE_COPY
POST /file/hash
Хэш файла.
Auth: требуется Body:
{"url":"https://cdn.denchikp.com/public/files/a.bin","algo":"sha256"}
Поддерживаемые алгоритмы: sha256, sha1, md5.
Browse / Folders / Download
POST /browse
Просмотр файлов на диске в домене.
Auth: требуется Body:
{"domain":"public","folder":"img","filter":"cat"}
GET /folders/{domain}
Возвращает папки домена и агрегаты (кол-во файлов/размер).
Auth: требуется
GET /download/{domain}/{folder}/{filename:path}
Скачать файл (FileResponse).
Auth: требуется Response: бинарный поток (application/octet-stream)
Validate URL
POST /validate
Проверяет URL (http/https), выполняет HEAD/GET и возвращает заголовки.
Если URL — CDN URL, дополнительно парсит domain/folder/filename и проверяет наличие на диске.
Auth: требуется Body:
{"url":"https://example.com/file.png"}
Delete file
DELETE /delete
Удалить файл по CDN URL.
Auth: требуется Body:
{"url":"https://cdn.denchikp.com/public/files/a.txt"}
Webhook event: DELETE (payload.source="api")
Text Editor (Read/Save)
Условия:
- только «текстовые» расширения/MIME
- размер файла ≤ 2 MB
POST /file/content
Получить содержимое текстового файла.
Auth: требуется Body:
{"url":"https://cdn.denchikp.com/public/files/app.js"}
POST /file/save
Сохранить содержимое текстового файла.
Auth: требуется Body:
{"url":"https://cdn.denchikp.com/public/files/app.js","content":"console.log('hi');\n"}
Webhook event: FILE_SAVE
Webhooks
POST /webhooks
Создать webhook.
Auth: требуется Body:
{
"url":"https://example.com/hook",
"events":["UPLOAD","DELETE"],
"secret":"optional",
"enabled":true
}
GET /webhooks
Список webhooks текущего ключа.
Auth: требуется
DELETE /webhooks/{webhook_id}
Удалить webhook.
Auth: требуется
Формат доставки
На URL вебхука отправляется POST JSON:
{"event":"UPLOAD","payload":{...}}
События
- UPLOAD
- DELETE
- BULK_DELETE
- FILE_MOVE
- FILE_COPY
- FILE_SAVE
- SCAN_FINISHED
Подпись (HMAC)
Если на сервере включена подпись и у webhook задан secret, рекомендуется отправлять:
Headers:
X-Webhook-Event: UPLOADX-Webhook-Timestamp: 1718550000X-Webhook-Signature: sha256=<hex>
Алгоритм:
- base =
{timestamp}.{raw_body} - signature =
HMAC_SHA256(secret, base)(hex) - получатель проверяет подпись и свежесть timestamp (например ±300 секунд)
Admin
Все admin endpoints требуют is_admin=true.
Scan
POST /admin/scan/startGET /admin/scan/status
Webhook event: SCAN_FINISHED
API Keys
GET /admin/api-keys
Query: show_full=false (если true — вернёт ключи полностью)
POST /admin/api-keys
Body:
{
"name":"Client",
"enabled":true,
"is_admin":false,
"ips":["1.2.3.4"],
"domains":["public"]
}
PATCH /admin/api-keys/{api_key_id}
Body:
{"name":"NewName","enabled":true,"is_admin":false}
POST /admin/api-keys/{api_key_id}/rotate
Ротация ключа.
DELETE /admin/api-keys/{api_key_id}
Удаление ключа.
IP allowlist
POST /admin/api-keys/{api_key_id}/ipsbody:{"value":"1.2.3.4"}DELETE /admin/api-keys/{api_key_id}/ips/{ip}
Domain allowlist
POST /admin/api-keys/{api_key_id}/domainsbody:{"value":"example.com"}DELETE /admin/api-keys/{api_key_id}/domains/{domain}
Quota
PUT /admin/api-keys/{api_key_id}/quota
{
"max_file_bytes": 524288000,
"max_total_bytes": 10737418240,
"max_total_files": 50000,
"max_monthly_upload_bytes": 5368709120,
"max_monthly_upload_files": null
}
Admin: Files / Logs / SSE
GET /admin/files
Query: domain, owner, exists_on_disk
GET /admin/logs
Query: action, level, domain, owner, limit (≤1000)
GET /events (SSE)
Поток text/event-stream, event: cdn_log.
Query: last_id=0.
Stats
Public
GET /stats/public-summary
Публичная краткая статистика (без ключа).
Admin (is_admin)
POST /stats— детальная статистика + графики (base64 PNG data URL)GET /stats/summaryGET /stats/domainsGET /stats/realtimeGET /stats/top-files?period=month&by=downloads|size&limit=50GET /stats/export?period=month&kind=uploads|downloads|deletions|browses&format=csv
Примечание по реализации: часть статистики берётся из MySQL таблиц (uploads/downloads/deletions/browses + files), часть — из SQLite файла cdn_stats.db.
Auth (sessions)
OAuth redirects/callbacks
GET /auth/google,/auth/google/callbackGET /auth/discord,/auth/discord/callbackGET /auth/github,/auth/github/callback
CDN session login
POST /auth/cdn/login
Body:
{"api_key":"<API_KEY>"}
POST /auth/cdn/logout
Выход из CDN‑сессии.
Current session
GET /auth/me
Возвращает текущую активную сессию (cdn/google/discord/github) и информацию о привязанном ключе (если есть).
Bind/unbind API key
POST /auth/bind-api-key
Body:
{"api_key":"<API_KEY>"}
Требуется активная OAuth‑сессия (google/discord/github).
POST /auth/unbind-api-key
Отвязка ключа от текущей OAuth‑сессии.
Logout
GET /auth/logout
Выход из любой активной сессии (очистка cookie‑состояния).
Bot (internal)
Все /bot/* требуют X-Bot-Token.
Discord binding
POST /bot/discord/bindPOST /bot/discord/unbindGET /bot/discord/me/{discord_id}
Bot files
POST /bot/upload(multipart/form-data: discord_id, file, domain, name_mode, custom_filename, conflict_action)DELETE /bot/deletebody:{"discord_id":"...","url":"https://cdn.denchikp.com/..."}GET /bot/download/{discord_id}/{domain}/{folder}/{filename:path}POST /bot/browseGET /bot/folders/{discord_id}/{domain}POST /bot/validatePOST /bot/file/content(только текст, ≤2MB)
Bot stats
GET /bot/public-summaryGET /bot/stats/{discord_id}(только если привязанный ключ is_admin=true)