Files
markclean-doc/api.md
T
2026-09-27 15:56:56 +03:00

22 KiB
Raw Blame History

📖 MarkClean API — Документация по использованию API-ключа

Версия API: v1
Базовый URL: https://markclean.online/api
Формат данных: JSON
Аутентификация: API-ключ (Bearer token / X-API-Key)


📑 Содержание

  1. Обзор
  2. Получение API-ключа
  3. Аутентификация
  4. Конвертация URL в Markdown
  5. История конвертаций
  6. Опции конвертации
  7. Обработка ошибок
  8. Квоты и лимиты
  9. Примеры кода
  10. FAQ

🔍 Обзор

MarkClean API позволяет программно конвертировать веб-страницы в чистый Markdown, оптимизированный для LLM, RAG-систем и документации.

Ключевые возможности:

  • 🌐 Конвертация любой HTML-страницы в Markdown
  • 🧹 Автоматическая очистка от мусора (реклама, навигация, футеры) через Readability
  • ⚡ Двухуровневое кэширование — повторные запросы не списывают кредиты
  • 🎛️ Гибкие опции вывода (заголовок, frontmatter, удаление картинок/ссылок)
  • 📊 История всех конвертаций с дедупликацией

🔑 Получение API-ключа

API-ключ можно создать только через веб-интерфейс (требует авторизации по сессии).

Шаги:

  1. Войдите в аккаунт на markclean.online
  2. Перейдите на страницу API Keys (или откройте /api-keys)
  3. Нажмите "Сгенерировать ключ"
  4. ⚠️ ВАЖНО: Сохраните ключ немедленно — он будет показан только один раз!

Формат ключа

mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ
│       └── 32 символа base64url (криптографически случайные)
└── префикс (8 символов)

Общая длина: 40 символов

🔒 Безопасность: В базе данных хранится только SHA-256 хеш ключа. Если вы потеряете ключ — восстановить его невозможно, только создать новый.


🛡️ Аутентификация

Все запросы к защищённым эндпоинтам должны содержать API-ключ в одном из заголовков:

Способ 1: X-API-Key (рекомендуется)

X-API-Key: mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ

Способ 2: Authorization: Bearer

Authorization: Bearer mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ

Оба способа равнозначны. При отсутствии или невалидности ключа сервер вернёт 401 Unauthorized.


🔄 Конвертация URL в Markdown

Основной эндпоинт API.

Запрос

POST /api/convert
Content-Type: application/json
X-API-Key: mk_live_YOUR_KEY_HERE

Тело запроса

{
  "url": "https://example.com/article",
  "include_title": true,
  "include_frontmatter": false,
  "strip_images": false,
  "strip_links": false
}
Поле Тип Обязательное По умолчанию Описание
url string ✅ — URL веб-страницы для конвертации (http/https)
include_title bool ❌ false Добавить заголовок страницы как H1 (# Title)
include_frontmatter bool ❌ false Добавить YAML-шапку с метаданными
strip_images bool ❌ false Удалить все <img> теги
strip_links bool ❌ false Удалить <a> теги (текст сохраняется)

Успешный ответ (200 OK)

{
  "title": "Example Article",
  "content": "# Example Article\n\nThis is the converted content...",
  "quota_incremented": true
}
Поле Тип Описание
title string Заголовок страницы (может отсутствовать)
content string Результат конвертации в Markdown
quota_incremented bool true — списан кредит; false — cache hit или лимит

Пример cURL

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,
    "strip_images": true
  }'

📚 История конвертаций

Получить список всех конвертаций текущего пользователя с пагинацией.

Запрос

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)

{
  "items": [
    {
      "id": "27c1e865-a77e-4d99-87d0-1179aef2a3e6",
      "title": "Example Article",
      "target_url": "https://example.com/article",
      "count_usage": 3,
      "created_at": "2026-09-27T10:00:00Z",
      "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[].count_usage int Сколько раз этот URL конвертировался
items[].created_at string Первая конвертация (ISO 8601)
items[].updated_at string Последняя конвертация (ISO 8601)
total_count int Общее количество записей
page int Текущая страница
page_size int Размер страницы
has_more bool Есть ли следующие страницы

💡 Дедупликация: Один URL = одна запись. Повторные конвертации увеличивают count_usage и обновляют updated_at, перемещая запись наверх.


🎛️ Опции конвертации

include_title: true

Добавляет заголовок страницы как H1 в начало документа:

# Example Article

This is the content...

include_frontmatter: true

Добавляет YAML-шапку с метаданными (удобно для статических генераторов типа Hugo/Jekyll):

---
title: "Example Article"
source: "https://example.com/article"
bytes: 4521
tokens: 1234
---

# Example Article

This is the content...

strip_images: true

Полностью удаляет все <img> теги. Полезно для:

  • 🤖 Подготовки текста для LLM (экономия токенов)
  • 📄 Создания текстовых версий статей
  • 💾 Уменьшения размера результата

Удаляет <a> теги, но сохраняет текст ссылок:

Было:

Visit <a href="https://example.com">our site</a> for more info.

Стало:

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

⚠️ Обработка ошибок

Все ошибки возвращаются в едином формате:

{
  "error": "machine_readable_code",
  "message": "Human-readable description"
}

HTTP-статусы

Статус Код ошибки Описание Действие
400 invalid_payload Битый JSON или отсутствует url Проверьте тело запроса
400 invalid_url Некорректный формат URL Должен начинаться с http:// или https://
401 invalid_api_key Ключ отсутствует, невалиден или удалён Проверьте заголовок X-API-Key
401 unauthorized Нет аутентификации Передайте API-ключ
402 quota_exceeded Кредиты закончились + дневной лимит Купите кредиты в веб-интерфейсе
404 key_not_found Ключ не найден (для истории) Создайте ключ заново
415 unsupported_content URL не возвращает HTML API работает только с веб-страницами
422 conversion_failed Не удалось распознать контент Попробуйте другой URL
429 rate_limit_exceeded Превышен лимит запросов Подождите или войдите в аккаунт
502 fetch_failed Не удалось загрузить страницу Сайт недоступен или заблокировал запрос
500 internal_server_error Внутренняя ошибка сервера Повторите позже

Пример обработки ошибки

$ 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"
}

📊 Квоты и лимиты

Система кредитов

  • 💳 Платные кредиты — не сгорают, не имеют срока действия
  • 📅 Дневной лимит (5 запросов/сутки) — сбрасывается каждые 24 часа
  • ⚡ Приоритет списания: сначала платные кредиты, затем дневной лимит

Кэширование и экономия

API использует двухуровневое кэширование:

Уровень Что кэшируется TTL Экономия
L1 Сырой HTML 30 мин Нет HTTP-запроса к внешнему сайту
L2 Финальный Markdown 5 мин Мгновенный ответ

Важно: Кредиты списываются только при реальном HTTP-запросе к внешнему сайту. Повторные конвертации того же URL (даже с другими опциями) бесплатны, если HTML ещё в кэше.

Таблица сценариев

Сценарий Fetch Кредиты Скорость
Первый запрос URL ✅ −1 ~2-5 сек
Повтор с теми же опциями ❌ 0 ~50 мс
Повтор с другими опциями ❌ 0 ~200 мс
Спустя 30+ минут ✅ −1 ~2-5 сек

Проверка баланса

Для проверки баланса кредитов используйте веб-интерфейс (эндпоинт GET /api/billing/credits доступен только через сессию, не через API-ключ).


💻 Примеры кода

Python (requests)

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,
        "strip_images": True,
    },
)

if response.ok:
    data = response.json()
    print(f"Title: {data.get('title')}")
    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 (пакетная конвертация)

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)

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,
      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,
    stripImages: true,
  });
  console.log(result.content);
} catch (err) {
  console.error('Conversion failed:', err.message);
}

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"`
    StripImages        bool   `json:"strip_images"`
    StripLinks         bool   `json:"strip_links"`
}

type ConvertResponse struct {
    Title            string `json:"title"`
    Content          string `json:"content"`
    QuotaIncremented bool   `json:"quota_incremented"`
}

func main() {
    reqBody, _ := json.Marshal(ConvertRequest{
        URL:          "https://example.com/article",
        IncludeTitle: true,
        StripImages:  true,
    })

    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 (все сценарии)

# Базовая конвертация
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 GET "https://markclean.online/api/converter/history?page=1&page_size=10" \
  -H "X-API-Key: mk_live_YOUR_KEY"

❓ FAQ

🔐 Безопасно ли передавать API-ключ?

Да, если соблюдаете правила:

  • ✅ Используйте HTTPS (всегда)
  • ✅ Храните ключ в переменных окружения, не в коде
  • ✅ Не логируйте ключ
  • ❌ Не коммитьте ключ в Git
  • ❌ Не передавайте ключ в URL-параметрах

🔄 Что делать, если ключ скомпрометирован?

  1. Откройте /api-keys в веб-интерфейсе
  2. Нажмите "Пересоздать" (старый ключ будет удалён, создан новый)
  3. Обновите ключ во всех ваших приложениях

⚠️ Старый ключ перестаёт работать мгновенно после пересоздания.

💳 Можно ли использовать API без кредитов?

Да, у каждого пользователя есть бесплатный дневной лимит (5 запросов/сутки), который сбрасывается каждые 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):

@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-клиенты в любом языке. Примеры выше покрывают самые популярные языки.


📞 Поддержка


Последнее обновление: 27 сентября 2026
Версия API: 0.7.5-beta