# 📖 MarkClean API — Документация по использованию API-ключа > **Версия API:** `v1` > **Базовый URL:** `https://markclean.online/api` > **Формат данных:** JSON > **Аутентификация:** API-ключ (`X-API-Key` / `Authorization: Bearer`) для `/convert` и `/converter/history`; HTTP-сессия (cookie `session_id`) для учётной, API-key и биллинга --- ## 📑 Содержание 1. [Обзор](<#🔍 Обзор>) 2. [Получение API-ключа](<#🔑 Получение API-ключа>) 3. [Аутентификация](<#🛡️ Аутентификация>) 4. [Конвертация URL в Markdown](<#🔄 Конвертация URL в Markdown>) 5. [История конвертаций](<#📚 История конвертаций>) 6. [Опции конвертации](<#🎛️ Опции конвертации>) 7. [API-ключи](<#🔑 API-ключи>) 8. [Биллинг: пакеты и подписки](<#💳 Биллинг: пакеты и подписки>) 9. [Проверка баланса](<#📊 Проверка баланса>) 10. [Обработка ошибок](<#⚠️ Обработка ошибок>) 11. [Квоты, лимиты и кэширование](<#📊 Квоты, лимиты и кэширование>) 12. [Примеры кода](<#💻 Примеры кода>) 13. [FAQ](<#❓ FAQ>) --- ## 🔍 Обзор MarkClean API позволяет программно конвертировать веб-страницы в чистый Markdown, оптимизированный для LLM, RAG-систем и документации. **Ключевые возможности:** - 🌐 Конвертация любой HTML-страницы в Markdown - 🧹 Автоматическая очистка от мусора (реклама, навигация, футеры) через Readability - ⚡ Двухуровневое кэширование — повторные запросы не списывают кредиты - 🎛️ Гибкие опции вывода (заголовок, frontmatter, оглавление, удаление картинок/ссылок) - 📊 История всех конвертаций с дедупликацией - 💳 Система кредитов + ежедневный лимит + подписка - 🗝️ Управление API-ключами (создание, чтение, удаление) - 📦 Пакеты кредитов и подписки --- ## 🔑 Получение API-ключа API-ключ можно создать **только** через веб-интерфейс (требует авторизации по сессии). ### Шаги: 1. Войдите в аккаунт на [markclean.online](https://markclean.online) 2. Перейдите на страницу **API Keys** (или откройте `/api-keys`) 3. Нажмите **"Сгенерировать ключ"** 4. **⚠️ ВАЖНО:** Сохраните ключ немедленно — он будет показан **только один раз!** ### Формат ключа ``` mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ │ └── 32 символа base64url (криптографически случайные, 24 байта) └── префикс (8 символов) ``` **Общая длина:** 40 символов > 🔒 **Безопасность:** В базе данных хранится только SHA-256 хеш ключа. Если вы потеряете ключ — восстановить его невозможно, только создать новый. --- ## 🛡️ Аутентификация API поддерживает два независимых способа авторизации: ### 1. API-ключ (для API-клиентов) Все запросы к защищённым эндпоинтам должны содержать API-ключ в одном из заголовков: #### Способ 1: `X-API-Key` (рекомендуется) ```http X-API-Key: mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ ``` #### Способ 2: `Authorization: Bearer` ```http Authorization: Bearer mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ ``` Оба способа равнозначны. При отсутствии или невалидности ключа сервер вернёт `401 Unauthorized` с ошибкой `invalid_api_key`. ### 2. Сессия (для веб-интерфейса и защищённых эндпоинтов) Для веб-интерфейса и защищённых эндпоинтов (API-ключи, биллинг, история, профиль) используется HTTP-сессия: - Cookie: `session_id` (UUID) - Устанавливается при регистрации/логине - Живёт 30 дней - Передаётся автоматически браузером --- ## 🔄 Конвертация URL в Markdown Основной эндпоинт API. Доступен **анонимам** и **пользователям с API-ключом**. ### Запрос ```http POST /api/convert Content-Type: application/json X-API-Key: mk_live_YOUR_KEY_HERE ``` > **Примечание:** Для анонимов API-ключ не нужен, но в этом случае применяется IP-лимит (3 запроса/24 часа). ### Тело запроса ```json { "url": "https://example.com/article", "include_title": true, "include_frontmatter": false, "include_toc": false, "strip_images": false, "strip_links": false } ``` | Поле | Тип | Обязательное | По умолчанию | Описание | |------|-----|:------------:|:------------:|----------| | `url` | string | ✅ | — | URL веб-страницы для конвертации (http/https) | | `include_title` | bool | ❌ | `false` | Добавить заголовок страницы как H1 (`# Title`) | | `include_frontmatter` | bool | ❌ | `false` | Добавить YAML-шапку с метаданными | | `include_toc` | bool | ❌ | `false` | Добавить оглавление (Table of Contents) | | `strip_images` | bool | ❌ | `false` | Удалить все `` теги | | `strip_links` | bool | ❌ | `false` | Удалить `` теги (текст сохраняется) | ### Успешный ответ (200 OK) ```json { "title": "Example Article", "content": "# Example Article\n\nThis is the converted content...", "metadata": { "words": 4521, "tokens": 1234, "bytes": 4521 }, "quota_incremented": true } ``` | Поле | Тип | Описание | |------|-----|----------| | `title` | string | Заголовок страницы (может отсутствовать) | | `content` | string | Результат конвертации в Markdown | | `metadata.words` | int | Количество слов в контенте | | `metadata.tokens` | int | Количество токенов (для LLM, cl100k) | | `metadata.bytes` | int | Размер в байтах | | `quota_incremented` | bool | `true` — списан кредит; `false` — cache hit или лимит | ### Пример cURL ```bash curl -X POST https://markclean.online/api/convert \ -H "Content-Type: application/json" \ -H "X-API-Key: mk_live_YOUR_KEY_HERE" \ -d '{ "url": "https://metanit.com/web/html5/2.6.php", "include_title": true, "include_frontmatter": true, "strip_images": true, "strip_links": false }' ``` --- ## 📚 История конвертаций Получить список всех конвертаций текущего пользователя с пагинацией. **Требует авторизации** (API-ключ или сессия). ### Запрос ```http GET /api/converter/history?page=1&page_size=20 X-API-Key: mk_live_YOUR_KEY_HERE ``` | Параметр | Тип | По умолчанию | Описание | |----------|-----|:------------:|----------| | `page` | int | `1` | Номер страницы (≥ 1) | | `page_size` | int | `20` | Количество записей (1–100) | ### Успешный ответ (200 OK) ```json { "items": [ { "id": "27c1e865-a77e-4d99-87d0-1179aef2a3e6", "title": "Example Article", "target_url": "https://example.com/article", "updated_at": "2026-09-27T14:30:00Z" } ], "total_count": 42, "page": 1, "page_size": 20, "has_more": true } ``` | Поле | Тип | Описание | |------|-----|----------| | `items` | array | Список конвертаций (отсортированы по `updated_at` DESC) | | `items[].id` | string | UUID записи | | `items[].title` | string | Заголовок страницы (или пустая строка) | | `items[].target_url` | string | Исходный URL | | `items[].updated_at` | string | Последняя конвертация (ISO 8601) | | `total_count` | int | Общее количество записей (только успешные, status_code = 200) | | `page` | int | Текущая страница | | `page_size` | int | Размер страницы | | `has_more` | bool | Есть ли следующие страницы | > 💡 **Дедупликация:** Один URL = одна запись. Повторные конвертации обновляют `updated_at` и перемещают запись наверх. --- ## 🎛️ Опции конвертации ### `include_title: true` Добавляет заголовок страницы как H1 в начало документа: ```markdown # Example Article This is the content... ``` ### `include_frontmatter: true` Добавляет YAML-шапку с метаданными (удобно для статических генераторов типа Hugo/Jekyll): ```markdown --- title: "Example Article" source: "https://example.com/article" language: "English" words: 4521 tokens: 1234 bytes: 4521 reading_time: "23 min" extracted_at: "2026-09-27" --- # Example Article This is the content... ``` Поле `language` определяется автоматически через `whatlanggo` (только если включён `include_toc` или `include_frontmatter`). ### `include_toc: true` Добавляет оглавление (Table of Contents) из заголовков H1–H3: ```markdown ## Table of Contents - [Main Section](<#Main Section>) - [Subsection](<#Subsection>) # Main Section Content... ``` > ⚠️ Оглавление добавляется только если заголовков H1–H3 **не менее 2**. Иначе оглавление не включается. ### `strip_images: true` Полностью удаляет все `` теги. Полезно для: - 🤖 Подготовки текста для LLM (экономия токенов) - 📄 Создания текстовых версий статей - 💾 Уменьшения размера результата ### `strip_links: true` Удаляет `` теги, но **сохраняет текст ссылок**: **Было:** ```html Visit our site for more info. ``` **Стало:** ```markdown Visit our site for more info. ``` ### 🎯 Рекомендуемые комбинации | Сценарий | Опции | |----------|-------| | **Для LLM / RAG** | `strip_images: true`, `strip_links: true` | | **Для документации** | `include_title: true`, `include_frontmatter: true` | | **Для архивирования** | `include_title: true`, `include_frontmatter: true`, `strip_images: false` | | **Минимальный размер** | `strip_images: true`, `strip_links: true`, `include_title: false` | --- ## 🔑 API-ключи Управление API-ключами — **только через сессию** (веб-интерфейс). API-ключами нельзя управлять через API. ### Создание ключа **POST** `/api/keys` **Запрос:** ```json { "name": "My Key" } ``` `name` необязательно (макс. 50 символов). Если не указан, используется `"Default Key"`. **Ответ (201 Created):** ```json { "id": "27c1e865-a77e-4d99-87d0-1179aef2a3e6", "name": "My Key", "api_key": "mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ", "warning": "Save this key now. It will not be shown again." } ``` > ⭐ Сырой ключ возвращается **только в этом ответе**. Больше его никто не покажет. **Ошибки:** - `409 Conflict` + `key_already_exists` — ключ уже существует (у пользователя только 1 ключ) - `500 Internal Server Error` + `internal_error` ### Получение метаданных ключа **GET** `/api/keys` **Ответ (200 OK):** ```json { "id": "27c1e865-a77e-4d99-87d0-1179aef2a3e6", "name": "My Key", "key_prefix": "mk_live_a1b2c3d4", "last_used_at": "2026-09-27 10:30:00", "is_active": true, "created_at": "2026-09-27 09:00:00" } ``` **Ошибки:** - `404 Not Found` + `key_not_found` — у пользователя нет ключа ### Удаление ключа **DELETE** `/api/keys/:key_id` **Ответ (200 OK):** ```json { "message": "API key deleted successfully" } ``` **Ошибки:** - `400 Bad Request` + `invalid_key_id` — некорректный UUID - `404 Not Found` + `key_not_found` — ключ не найден - `500 Internal Server Error` + `internal_error` --- ## 💳 Биллинг: пакеты и подписки ### Получение доступных пакетов **GET** `/api/billing/packages` — **публичный** эндпоинт (авторизация не требуется). **Ответ (200 OK):** ```json { "free": { "name": "Free", "daily_limit": 10 }, "credits": [ { "code": "micro", "name": "Micro", "credits": 300, "price": 49, "price_per_100_requests": 17, "discount": 0, "savings_rub": 0, "is_popular": false }, { "code": "start", "name": "Start", "credits": 1000, "price": 99, "price_per_100_requests": 9, "discount": 32, "savings_rub": 17, "is_popular": false }, { "code": "optimal", "name": "Optimal", "credits": 3000, "price": 199, "price_per_100_requests": 7, "discount": 42, "savings_rub": 27, "is_popular": true }, { "code": "pro", "name": "Pro", "credits": 8000, "price": 399, "price_per_100_requests": 5, "discount": 51, "savings_rub": 57, "is_popular": false }, { "code": "ultra", "name": "Ultra", "credits": 20000, "price": 799, "price_per_100_requests": 4, "discount": 63, "savings_rub": 124, "is_popular": false } ], "subscriptions": [ { "months": 1, "discount_percent": 0, "total_price": 499, "monthly_price": 499, "savings_rub": 0 }, { "months": 3, "discount_percent": 5, "total_price": 1419, "monthly_price": 473, "savings_rub": 78 }, { "months": 6, "discount_percent": 10, "total_price": 2690, "monthly_price": 448, "savings_rub": 304 }, { "months": 12, "discount_percent": 19, "total_price": 4790, "monthly_price": 399, "savings_rub": 1198 } ] } ``` | Поле | Описание | |------|----------| | `free` | Бесплатный план: дневной лимит | | `credits[]` | Пакеты кредитов (от дешёвого к дорогому) | | `credits[].code` | Код пакета (`micro`, `start`, `optimal`, `pro`, `ultra`) | | `credits[].credits` | Количество кредитов в пакете | | `credits[].price` | Цена в рублях | | `credits[].price_per_100_requests` | Стоимость 100 запросов | | `credits[].discount` | Процент выгоды (от базового тарифа Micro) | | `credits[].savings_rub` | Экономия в рублях | | `credits[].is_popular` | Флаг «Хит» | | `subscriptions[]` | Пакеты подписки (от короткого к длинному) | ### Покупка пакета **POST** `/api/billing/purchase` — **требует авторизации** (API-ключ или сессия). > ⚠️ **Временный endpoint!** В продакшене будет заменён на реальную интеграцию с платёжной системой. **Запрос:** ```json { "package_code": "optimal" } ``` **Ответ (200 OK):** ```json { "message": "Credits added successfully", "package": "optimal", "credits_added": 3000, "new_balance": 3000 } ``` **Ошибки:** - `400 Bad Request` + `invalid_package` — некорректный код пакета - `401 Unauthorized` + `unauthorized` — нет аутентификации - `500 Internal Server Error` + `purchase_failed` — ошибка начисления кредитов --- ## 📊 Проверка баланса ### Получение баланса кредитов и подписки **GET** `/api/billing/usage` — **требует авторизации** (API-ключ или сессия). **Ответ (200 OK):** ```json { "credits": { "credits": 2500, "daily_used": 3, "daily_limit": 10, "daily_remaining": 7, "has_credits": true, "daily_reset_at": "2026-09-27T14:00:00Z" }, "subscription": { "is_active": true, "ends_at": "2026-10-01T10:00:00Z", "days_remaining": 4, "is_unlimited": true }, "is_subscriber": true } ``` | Поле | Тип | Описание | |------|-----|----------| | `credits` | object | Баланс кредитов | | `credits.credits` | int | Платные кредиты (не сгорают) | | `credits.daily_used` | int | Использовано запросов сегодня | | `credits.daily_limit` | int | Дневной лимит (10) | | `credits.daily_remaining` | int | Осталось запросов сегодня | | `credits.has_credits` | bool | Есть ли платные кредиты | | `credits.daily_reset_at` | time | Время следующего сброса дневного лимита | | `subscription` | object/null | Информация об активной подписке | | `subscription.is_active` | bool | Подписка активна | | `subscription.ends_at` | time | Дата окончания подписки | | `subscription.days_remaining` | int | Осталось дней | | `subscription.is_unlimited` | bool | Безлимит (всегда true) | | `is_subscriber` | bool | Есть ли активная подписка | --- ## ⚠️ Обработка ошибок Все ошибки возвращаются в едином формате: ```json { "error": "machine_readable_code", "message": "Human-readable description" } ``` ### HTTP-статусы | Статус | Код ошибки | Описание | Действие | |:------:|------------|----------|----------| | `400` | `invalid_payload` | Битый JSON или отсутствует `url` | Проверьте тело запроса | | `400` | `invalid_url` | Некорректный формат URL | Должен начинаться с `http://` или `https://` | | `400` | `invalid_request` | Некорректный запрос (биллинг) | Проверьте параметры | | `400` | `invalid_package` | Неверный код пакета | Используйте валидный код | | `401` | `invalid_api_key` | Ключ отсутствует, невалиден или удалён | Проверьте заголовок `X-API-Key` | | `401` | `unauthorized` | Нет аутентификации | Передайте API-ключ или cookie | | `402` | `quota_exceeded` | Кредиты закончились + дневной лимит | Купите кредиты в веб-интерфейсе | | `404` | `key_not_found` | Ключ не найден | Создайте ключ заново | | `404` | `key_not_found` | Ключ пользователя не найден (GET /api/keys) | Создайте ключ | | `404` | `user_not_found` | Пользователь не найден | Проверьте данные | | `409` | `key_already_exists` | У пользователя уже есть API-ключ | Удалите существующий ключ | | `409` | `user_exists` | Пользователь с этим email уже зарегистрирован | Используйте другой email | | `409` | `account_already_deleted` | Аккаунт уже удалён | — | | `410` | `email_previously_deleted` | Email был использован для удалённого аккаунта | Используйте другой email | | `415` | `unsupported_content` | URL не возвращает HTML | API работает только с веб-страницами | | `422` | `conversion_failed` | Не удалось распознать контент | Попробуйте другой URL | | `429` | `rate_limit_exceeded` | Превышен лимит запросов | Подождите или войдите в аккаунт | | `429` | `registration_ip_limit` | Превышен лимит регистраций с IP | Подождите | | `429` | `resend_cooldown` | Слишком часто запрашивается OTP | Подождите | | `429` | `otp_blocked` | Слишком много неудачных попыток OTP | Запросите новый код | | `429` | `registration_ip_limit` | Превышен лимит регистраций с IP | Подождите | | `429` | `registration_ip_limit` | Превышен лимит регистраций с IP | Подождите | | `500` | `internal_server_error` | Внутренняя ошибка сервера | Повторите позже | | `502` | `fetch_failed` | Не удалось загрузить страницу | Сайт недоступен или заблокировал запрос | ### Пример обработки ошибки ```bash $ curl -X POST https://markclean.online/api/convert \ -H "X-API-Key: mk_live_INVALID" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com"}' HTTP/1.1 401 Unauthorized { "error": "invalid_api_key", "message": "Invalid or expired API key" } ``` --- ## 📊 Квоты, лимиты и кэширование ### Система кредитов - 💳 **Платные кредиты** — не сгорают, не имеют срока действия - 📅 **Дневной лимит** (10 запросов/сутки) — сбрасывается каждые 24 часа - ⚡ **Приоритет списания:** сначала платные кредиты, затем дневной лимит - 📊 **Максимальный баланс:** 500 000 кредитов ### Кэширование и экономия API использует **двухуровневое кэширование**: | Уровень | Что кэшируется | TTL | Экономия | |---------|----------------|-----|----------| | **L1** | Сырой HTML | 30 мин | Нет HTTP-запроса к внешнему сайту | | **L2** | Финальный Markdown | 15 мин | Мгновенный ответ | **Важно:** Кредиты списываются **только** при реальном HTTP-запросе к внешнему сайту. Повторные конвертации того же URL (даже с другими опциями) **бесплатны**, если HTML ещё в кэше. ### Таблица сценариев | Сценарий | Fetch | Кредиты | Скорость | |----------|:-----:|:-------:|----------| | Первый запрос URL | ✅ | −1 | ~2-5 сек | | Повтор с теми же опциями | ❌ | 0 | ~50 мс | | Повтор с другими опциями | ❌ | 0 | ~200 мс | | Спустя 30+ минут | ✅ | −1 | ~2-5 сек | ### IP-лимиты (для анонимов) - **Конвертация:** 3 запроса / 24 часа с одного IP - **Регистрация:** 3 регистрации / 1 час с одного IP (глобально), 3 регистрации / 2 минуты с одного IP - **Логин:** 10 попыток / 30 минут с одного IP, 5 попыток / 15 минут на email - **Recovery OTP:** 10 попыток / 1 час с одного IP - **OTP Verify:** 20 попыток / 5 минут с одного IP - **Смена пароля:** 5 попыток / 1 час с одного IP, 3 попытки / 1 час на пользователя ### Проверка баланса Для проверки баланса кредитов используйте: - Веб-интерфейс (страница **Billing**) - API: **GET** `/api/billing/usage` (требует авторизации) --- ## 💻 Примеры кода ### Python (requests) ```python import requests API_KEY = "mk_live_YOUR_KEY_HERE" API_BASE = "https://markclean.online/api" headers = { "Content-Type": "application/json", "X-API-Key": API_KEY, } # Конвертация response = requests.post( f"{API_BASE}/convert", headers=headers, json={ "url": "https://example.com/article", "include_title": True, "include_frontmatter": True, "strip_images": True, "strip_links": False, }, ) if response.ok: data = response.json() print(f"Title: {data.get('title')}") print(f"Words: {data['metadata']['words']}") print(f"Tokens: {data['metadata']['tokens']}") print(f"Credits used: {data['quota_incremented']}") print(data["content"][:500]) else: error = response.json() print(f"Error {response.status_code}: {error['error']} - {error['message']}") ``` ### Python (пакетная конвертация) ```python import requests import time API_KEY = "mk_live_YOUR_KEY_HERE" urls = [ "https://example.com/page1", "https://example.com/page2", "https://example.com/page3", ] headers = { "Content-Type": "application/json", "X-API-Key": API_KEY, } results = [] for url in urls: resp = requests.post( "https://markclean.online/api/convert", headers=headers, json={"url": url, "include_title": True}, ) if resp.ok: results.append(resp.json()) print(f"✅ {url}") else: print(f"❌ {url}: {resp.json()['error']}") time.sleep(0.5) # Вежливый delay # Сохраняем в один файл with open("output.md", "w", encoding="utf-8") as f: for r in results: f.write(r["content"] + "\n\n---\n\n") ``` ### JavaScript / Node.js (fetch) ```javascript const API_KEY = 'mk_live_YOUR_KEY_HERE'; const API_BASE = 'https://markclean.online/api'; async function convertUrl(url, options = {}) { const response = await fetch(`${API_BASE}/convert`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, }, body: JSON.stringify({ url, include_title: options.title ?? false, include_frontmatter: options.frontmatter ?? false, include_toc: options.toc ?? false, strip_images: options.stripImages ?? false, strip_links: options.stripLinks ?? false, }), }); if (!response.ok) { const error = await response.json(); throw new Error(`${error.error}: ${error.message}`); } return response.json(); } // Использование try { const result = await convertUrl('https://example.com/article', { title: true, frontmatter: true, stripImages: true, }); console.log(result.content); } catch (err) { console.error('Conversion failed:', err.message); } ``` ### Go ```go package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" ) const ( apiKey = "mk_live_YOUR_KEY_HERE" apiBase = "https://markclean.online/api" ) type ConvertRequest struct { URL string `json:"url"` IncludeTitle bool `json:"include_title"` IncludeFrontmatter bool `json:"include_frontmatter"` IncludeTOC bool `json:"include_toc"` StripImages bool `json:"strip_images"` StripLinks bool `json:"strip_links"` } type Metadata struct { Words int `json:"words"` Tokens int `json:"tokens"` Bytes int `json:"bytes"` } type ConvertResponse struct { Title string `json:"title"` Content string `json:"content"` Metadata Metadata `json:"metadata"` QuotaIncremented bool `json:"quota_incremented"` } func main() { reqBody, _ := json.Marshal(ConvertRequest{ URL: "https://example.com/article", IncludeTitle: true, IncludeFrontmatter: true, StripImages: true, StripLinks: false, }) req, _ := http.NewRequest("POST", apiBase+"/convert", bytes.NewReader(reqBody)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-API-Key", apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { fmt.Printf("Error %d: %s\n", resp.StatusCode, body) return } var result ConvertResponse json.Unmarshal(body, &result) fmt.Println(result.Content) } ``` ### cURL (все сценарии) ```bash # Базовая конвертация curl -X POST https://markclean.online/api/convert \ -H "X-API-Key: mk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com"}' # Для LLM (чистый текст) curl -X POST https://markclean.online/api/convert \ -H "X-API-Key: mk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "strip_images": true, "strip_links": true }' # Для документации (с метаданными) curl -X POST https://markclean.online/api/convert \ -H "X-API-Key: mk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "include_title": true, "include_frontmatter": true }' # Для документации (с оглавлением) curl -X POST https://markclean.online/api/convert \ -H "X-API-Key: mk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "include_title": true, "include_frontmatter": true, "include_toc": true }' # История конвертаций curl -X GET "https://markclean.online/api/converter/history?page=1&page_size=10" \ -H "X-API-Key: mk_live_YOUR_KEY" # Проверка баланса curl -X GET "https://markclean.online/api/billing/usage" \ -H "X-API-Key: mk_live_YOUR_KEY" # Получить доступные пакеты curl -X GET "https://markclean.online/api/billing/packages" ``` --- ## ❓ FAQ ### 🔐 Безопасно ли передавать API-ключ? **Да**, если соблюдаете правила: - ✅ Используйте HTTPS (всегда) - ✅ Храните ключ в переменных окружения, не в коде - ✅ Не логируйте ключ - ❌ Не коммитьте ключ в Git - ❌ Не передавайте ключ в URL-параметрах ### 🔄 Что делать, если ключ скомпрометирован? 1. Откройте `/api-keys` в веб-интерфейсе 2. Нажмите **"Пересоздать"** (старый ключ будет удалён, создан новый) 3. Обновите ключ во всех ваших приложениях > ⚠️ Старый ключ перестаёт работать мгновенно после пересоздания. ### 💳 Можно ли использовать API без кредитов? Да, у каждого пользователя есть **бесплатный дневной лимит** (10 запросов/сутки), который сбрасывается каждые 24 часа. После исчерпания и дневного лимита, и кредитов — API вернёт `402 quota_exceeded`. ### ⚡ Почему `quota_incremented: false`? Это нормально в трёх случаях: 1. **Cache hit** — этот URL уже конвертировался недавно (бесплатно) 2. **Анонимный запрос** — без API-ключа (лимит по IP) 3. **Подписка** — у пользователя активна подписка, кредиты не списываются ### 🌍 Работает ли API с любым сайтом? API работает с любыми **публично доступными HTML-страницами**. Ограничения: - ❌ Сайты, требующие авторизации - ❌ Сайты с жёсткой анти-бот защитой (Cloudflare challenge и т.п.) - ❌ PDF, изображения и другие не-HTML форматы - ❌ Сайты, блокирующие по User-Agent ### 📦 Есть ли лимит на размер страницы? - Максимум: **5 MB** контента после конвертации - Типичные статьи: 10-100 KB - Длинные технические документы: 200-500 KB Страницы больше 5 MB не кэшируются, но всё равно конвертируются. ### 🔗 Как использовать API-ключ с другими инструментами? **Postman:** - Вкладка **Authorization** → Type: **API Key** - Key: `X-API-Key`, Value: ваш ключ, Add to: **Header** **Insomnia:** - Request → **Auth** → **API Key** - Header name: `X-API-Key` **VS Code (REST Client):** ```http @apiKey = mk_live_YOUR_KEY POST https://markclean.online/api/convert Content-Type: application/json X-API-Key: {{apiKey}} { "url": "https://example.com" } ``` ### 🗑️ Что будет с API-ключом после удаления аккаунта? **Ключ продолжит работать**, пока не закончатся кредиты. После удаления аккаунта: - ✅ API-ключ остаётся активным - ✅ Существующие кредиты можно использовать - ❌ Пополнить кредиты нельзя (нет доступа к UI) - ❌ Создать новый ключ нельзя Это сделано для честности: вы оплатили кредиты — вы их используете. ### 🛠️ Есть ли SDK? Официальных SDK пока нет, но API достаточно простой для использования через стандартные HTTP-клиенты в любом языке. Примеры выше покрывают самые популярные языки. --- ## 📞 Поддержка - 📧 **Email:** support@markclean.online - 📧 **Email:** gnutenko.s.r@gmail.com - 💬 **Telegram:** [@Slayder12](https://t.me/Slayder12) - 📝 **Changelog:** [markclean-doc/changelog.md](https://gitea.gsrhost.ru/gsr/markclean-doc/src/branch/main/changelog.md)