diff --git a/markclean-api-en.md b/markclean-api-en.md
new file mode 100644
index 0000000..9a2213f
--- /dev/null
+++ b/markclean-api-en.md
@@ -0,0 +1,1012 @@
+# ๐ MarkClean API โ API Key Usage Documentation
+
+> **API Version:** `v1`
+> **Base URL:** `https://markclean.online/api`
+> **Data Format:** JSON
+> **Authentication:** API key (`X-API-Key` / `Authorization: Bearer`) for `/convert` and `/converter/history`; HTTP session (cookie `session_id`) for account, API-key and billing endpoints
+
+---
+
+## ๐ Table of Contents
+
+1. [Overview](<#๐ Overview>)
+2. [Getting an API Key](<#๐ Getting an API Key>)
+3. [Authentication](<#๐ก๏ธ Authentication>)
+4. [Converting URL to Markdown](<#๐ Converting URL to Markdown>)
+5. [Conversion History](<#๐ Conversion History>)
+6. [Conversion Options](<#๐๏ธ Conversion Options>)
+7. [API Keys](<#๐ API Keys>)
+8. [Billing: Credit Packs & Subscriptions](<#๐ณ Billing: Credit Packs & Subscriptions>)
+9. [Balance Check](<#๐ Balance Check>)
+10. [Error Handling](<#โ ๏ธ Error Handling>)
+11. [Quotas, Limits & Caching](<#๐ Quotas, Limits & Caching>)
+12. [Code Examples](<#๐ป Code Examples>)
+13. [FAQ](<#โ FAQ>)
+
+---
+
+## ๐ Overview
+
+The MarkClean API allows you to programmatically convert web pages into clean Markdown, optimized for LLMs, RAG systems, and documentation.
+
+**Key Features:**
+- ๐ Convert any HTML page to Markdown
+- ๐งน Automatic junk removal (ads, navigation, footers) via Readability
+- โก Two-tier caching โ repeated requests do not consume credits
+- ๐๏ธ Flexible output options (title, frontmatter, table of contents, image/link stripping)
+- ๐ Full history of all conversions with de-duplication
+- ๐ณ Credit system + daily limit + subscriptions
+- ๐๏ธ API key management (create, read, delete)
+- ๐ฆ Credit packs and subscriptions
+
+---
+
+## ๐ Getting an API Key
+
+An API key can be created **only** through the web interface (session authentication required).
+
+### Steps:
+
+1. Log in to your account on [markclean.online](https://markclean.online)
+2. Navigate to the **API Keys** page (or open `/api-keys`)
+3. Click **"Generate Key"**
+4. **โ ๏ธ IMPORTANT:** Save the key immediately โ it will be shown **only once!**
+
+### Key Format
+
+```
+mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ
+โ โโโ 32 base64url characters (cryptographically random, 24 bytes)
+โโโ prefix (8 characters)
+```
+
+**Total length:** 40 characters
+
+> ๐ **Security:** Only the SHA-256 hash of the key is stored in the database. If you lose the key โ it cannot be recovered, only a new one can be created.
+
+---
+
+## ๐ก๏ธ Authentication
+
+The API supports two independent authentication methods:
+
+### 1. API Key (for API clients)
+
+All requests to protected endpoints must include an API key in one of the following headers:
+
+#### Method 1: `X-API-Key` (recommended)
+
+```http
+X-API-Key: mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ
+```
+
+#### Method 2: `Authorization: Bearer`
+
+```http
+Authorization: Bearer mk_live_Xk9mP2vQ7rT4wL8nZ3sY6tB1cD5fG0hJ
+```
+
+Both methods are equivalent. If the key is missing or invalid, the server returns `401 Unauthorized` with error `invalid_api_key`.
+
+### 2. Session (for the web interface and protected endpoints)
+
+For the web interface and protected endpoints (API keys, billing, history, profile), an HTTP session is used:
+
+- Cookie: `session_id` (UUID)
+- Set on registration/login
+- Expires after 30 days
+- Sent automatically by the browser
+
+---
+
+## ๐ Converting URL to Markdown
+
+The main API endpoint. Available to **anonymous users** and **users with an API key**.
+
+### Request
+
+```http
+POST /api/convert
+Content-Type: application/json
+X-API-Key: mk_live_YOUR_KEY_HERE
+```
+
+> **Note:** Anonymous users do not need an API key, but in that case the IP limit applies (3 requests / 24 hours).
+
+### Request Body
+
+```json
+{
+ "url": "https://example.com/article",
+ "include_title": true,
+ "include_frontmatter": false,
+ "include_toc": false,
+ "strip_images": false,
+ "strip_links": false
+}
+```
+
+| Field | Type | Required | Default | Description |
+|-------|------|:---------:|:-------:|-------------|
+| `url` | string | โ
| โ | URL of the web page to convert (http/https) |
+| `include_title` | bool | โ | `false` | Add the page title as an H1 (`# Title`) |
+| `include_frontmatter` | bool | โ | `false` | Add a YAML frontmatter with metadata |
+| `include_toc` | bool | โ | `false` | Add a table of contents (TOC) |
+| `strip_images` | bool | โ | `false` | Remove all `
` tags |
+| `strip_links` | bool | โ | `false` | Remove `` tags (text is preserved) |
+
+### Successful Response (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
+}
+```
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `title` | string | Page title (may be absent) |
+| `content` | string | Markdown conversion result |
+| `metadata.words` | int | Number of words in the content |
+| `metadata.tokens` | int | Number of tokens (for LLM, cl100k) |
+| `metadata.bytes` | int | Size in bytes |
+| `quota_incremented` | bool | `true` โ a credit was consumed; `false` โ cache hit or limit reached |
+
+### cURL Example
+
+```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
+ }'
+```
+
+---
+
+## ๐ Conversion History
+
+Retrieve the list of all conversions for the current user with pagination.
+
+**Authentication required** (API key or session).
+
+### Request
+
+```http
+GET /api/converter/history?page=1&page_size=20
+X-API-Key: mk_live_YOUR_KEY_HERE
+```
+
+| Parameter | Type | Default | Description |
+|-----------|------|:--------:|-------------|
+| `page` | int | `1` | Page number (โฅ 1) |
+| `page_size` | int | `20` | Number of records (1โ100) |
+
+### Successful Response (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
+}
+```
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `items` | array | List of conversions (sorted by `updated_at` DESC) |
+| `items[].id` | string | Record UUID |
+| `items[].title` | string | Page title (or empty string) |
+| `items[].target_url` | string | Source URL |
+| `items[].updated_at` | string | Last conversion (ISO 8601) |
+| `total_count` | int | Total number of records (successful only, status_code = 200) |
+| `page` | int | Current page |
+| `page_size` | int | Page size |
+| `has_more` | bool | Whether there are more pages |
+
+> ๐ก **Deduplication:** One URL = one record. Repeated conversions update `updated_at` and move the record to the top.
+
+---
+
+## ๐๏ธ Conversion Options
+
+### `include_title: true`
+
+Adds the page title as an H1 at the beginning of the document:
+
+```markdown
+# Example Article
+
+This is the content...
+```
+
+### `include_frontmatter: true`
+
+Adds a YAML frontmatter with metadata (convenient for static site generators such as 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...
+```
+
+The `language` field is detected automatically via `whatlanggo` (only when `include_toc` or `include_frontmatter` is enabled).
+
+### `include_toc: true`
+
+Adds a table of contents (TOC) from H1โH3 headings:
+
+```markdown
+## Table of Contents
+
+- [Main Section](<#Main Section>)
+- [Subsection](<#Subsection>)
+
+# Main Section
+
+Content...
+```
+
+> โ ๏ธ The TOC is added only if there are **at least 2** H1โH3 headings. Otherwise, the TOC is not included.
+
+### `strip_images: true`
+
+Completely removes all `
` tags. Useful for:
+- ๐ค Preparing text for LLMs (token savings)
+- ๐ Creating text-only versions of articles
+- ๐พ Reducing the result size
+
+### `strip_links: true`
+
+Removes `` tags, but **preserves the link text**:
+
+**Before:**
+```html
+Visit our site for more info.
+```
+
+**After:**
+```markdown
+Visit our site for more info.
+```
+
+### ๐ฏ Recommended Combinations
+
+| Scenario | Options |
+|----------|---------|
+| **For LLM / RAG** | `strip_images: true`, `strip_links: true` |
+| **For documentation** | `include_title: true`, `include_frontmatter: true` |
+| **For archiving** | `include_title: true`, `include_frontmatter: true`, `strip_images: false` |
+| **Minimal size** | `strip_images: true`, `strip_links: true`, `include_title: false` |
+
+---
+
+## ๐ API Keys
+
+Managing API keys is **session-only** (web interface). API keys cannot be managed via the API.
+
+### Creating a Key
+
+**POST** `/api/keys`
+
+**Request:**
+```json
+{
+ "name": "My Key"
+}
+```
+`name` is optional (max. 50 characters). If not specified, `"Default Key"` is used.
+
+**Response (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."
+}
+```
+
+> โญ The raw key is returned **only in this response**. No one will ever show it again.
+
+**Errors:**
+- `409 Conflict` + `key_already_exists` โ a key already exists (a user has only 1 key)
+- `500 Internal Server Error` + `internal_error`
+
+### Getting Key Metadata
+
+**GET** `/api/keys`
+
+**Response (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"
+}
+```
+
+**Errors:**
+- `404 Not Found` + `key_not_found` โ the user has no key
+
+### Deleting a Key
+
+**DELETE** `/api/keys/:key_id`
+
+**Response (200 OK):**
+```json
+{
+ "message": "API key deleted successfully"
+}
+```
+
+**Errors:**
+- `400 Bad Request` + `invalid_key_id` โ invalid UUID
+- `404 Not Found` + `key_not_found` โ key not found
+- `500 Internal Server Error` + `internal_error`
+
+---
+
+## ๐ณ Billing: Credit Packs & Subscriptions
+
+### Getting Available Packages
+
+**GET** `/api/billing/packages` โ **public** endpoint (no authentication required).
+
+**Response (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
+ }
+ ]
+}
+```
+
+| Field | Description |
+|-------|-------------|
+| `free` | Free plan: daily limit |
+| `credits[]` | Credit packs (from cheapest to most expensive) |
+| `credits[].code` | Package code (`micro`, `start`, `optimal`, `pro`, `ultra`) |
+| `credits[].credits` | Number of credits in the pack |
+| `credits[].price` | Price in rubles |
+| `credits[].price_per_100_requests` | Cost of 100 requests |
+| `credits[].discount` | Discount percentage (compared to the base Micro rate) |
+| `credits[].savings_rub` | Savings in rubles |
+| `credits[].is_popular` | "Hit" flag |
+| `subscriptions[]` | Subscription packs (from shortest to longest) |
+
+### Purchasing a Pack
+
+**POST** `/api/billing/purchase` โ **authentication required** (API key or session).
+
+> โ ๏ธ **Temporary endpoint!** In production it will be replaced with a real payment system integration.
+
+**Request:**
+```json
+{
+ "package_code": "optimal"
+}
+```
+
+**Response (200 OK):**
+```json
+{
+ "message": "Credits added successfully",
+ "package": "optimal",
+ "credits_added": 3000,
+ "new_balance": 3000
+}
+```
+
+**Errors:**
+- `400 Bad Request` + `invalid_package` โ invalid package code
+- `401 Unauthorized` + `unauthorized` โ no authentication
+- `500 Internal Server Error` + `purchase_failed` โ error while crediting
+
+---
+
+## ๐ Balance Check
+
+### Getting Credits & Subscription Balance
+
+**GET** `/api/billing/usage` โ **authentication required** (API key or session).
+
+**Response (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
+}
+```
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `credits` | object | Credit balance |
+| `credits.credits` | int | Paid credits (do not expire) |
+| `credits.daily_used` | int | Requests used today |
+| `credits.daily_limit` | int | Daily limit (10) |
+| `credits.daily_remaining` | int | Requests remaining today |
+| `credits.has_credits` | bool | Whether paid credits are available |
+| `credits.daily_reset_at` | time | Next daily limit reset time |
+| `subscription` | object/null | Active subscription info |
+| `subscription.is_active` | bool | Subscription is active |
+| `subscription.ends_at` | time | Subscription end date |
+| `subscription.days_remaining` | int | Days remaining |
+| `subscription.is_unlimited` | bool | Unlimited (always true) |
+| `is_subscriber` | bool | Whether there is an active subscription |
+
+---
+
+## โ ๏ธ Error Handling
+
+All errors are returned in a unified format:
+
+```json
+{
+ "error": "machine_readable_code",
+ "message": "Human-readable description"
+}
+```
+
+### HTTP Statuses
+
+| Status | Error Code | Description | Action |
+|:------:|------------|-------------|--------|
+| `400` | `invalid_payload` | Invalid JSON or missing `url` | Check the request body |
+| `400` | `invalid_url` | Invalid URL format | Must start with `http://` or `https://` |
+| `400` | `invalid_request` | Invalid request (billing) | Check the parameters |
+| `400` | `invalid_package` | Invalid package code | Use a valid code |
+| `401` | `invalid_api_key` | Key missing, invalid, or deleted | Check the `X-API-Key` header |
+| `401` | `unauthorized` | No authentication | Pass an API key or cookie |
+| `402` | `quota_exceeded` | Credits exhausted + daily limit reached | Purchase credits in the web interface |
+| `404` | `key_not_found` | Key not found | Create a new key |
+| `404` | `key_not_found` | User's key not found (GET /api/keys) | Create a key |
+| `404` | `user_not_found` | User not found | Check your credentials |
+| `409` | `key_already_exists` | User already has an API key | Delete the existing key |
+| `409` | `user_exists` | A user with this email is already registered | Use a different email |
+| `409` | `account_already_deleted` | Account already deleted | โ |
+| `410` | `email_previously_deleted` | Email was used for a deleted account | Use a different email |
+| `415` | `unsupported_content` | URL does not return HTML | The API works only with web pages |
+| `422` | `conversion_failed` | Could not recognize the content | Try another URL |
+| `429` | `rate_limit_exceeded` | Request limit exceeded | Wait or log in to your account |
+| `429` | `registration_ip_limit` | Registration limit from the IP exceeded | Wait |
+| `429` | `resend_cooldown` | OTP requested too frequently | Wait |
+| `429` | `otp_blocked` | Too many failed OTP attempts | Request a new code |
+| `429` | `registration_ip_limit` | Registration limit from the IP exceeded | Wait |
+| `429` | `registration_ip_limit` | Registration limit from the IP exceeded | Wait |
+| `500` | `internal_server_error` | Server internal error | Retry later |
+| `502` | `fetch_failed` | Could not load the page | Site unavailable or blocked the request |
+
+### Error Handling Example
+
+```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"
+}
+```
+
+---
+
+## ๐ Quotas, Limits & Caching
+
+### Credit System
+
+- ๐ณ **Paid credits** โ do not expire, no expiration date
+- ๐
**Daily limit** (10 requests / day) โ resets every 24 hours
+- โก **Debit priority:** paid credits first, then the daily limit
+- ๐ **Maximum balance:** 500,000 credits
+
+### Caching & Savings
+
+The API uses **two-tier caching**:
+
+| Level | What is cached | TTL | Savings |
+|-------|----------------|-----|---------|
+| **L1** | Raw HTML | 30 min | No HTTP request to the external site |
+| **L2** | Final Markdown | 15 min | Instant response |
+
+**Important:** Credits are debited **only** when an actual HTTP request is made to the external site. Repeated conversions of the same URL (even with different options) are **free** if the HTML is still in the cache.
+
+### Scenario Table
+
+| Scenario | Fetch | Credits | Speed |
+|----------|:-----:|:-------:|-------|
+| First request for a URL | โ
| โ1 | ~2-5 sec |
+| Repeat with the same options | โ | 0 | ~50 ms |
+| Repeat with different options | โ | 0 | ~200 ms |
+| After 30+ minutes | โ
| โ1 | ~2-5 sec |
+
+### IP Limits (for anonymous users)
+
+- **Conversion:** 3 requests / 24 hours per IP
+- **Registration:** 3 registrations / 1 hour per IP (globally), 3 registrations / 2 minutes per IP
+- **Login:** 10 attempts / 30 minutes per IP, 5 attempts / 15 minutes per email
+- **Recovery OTP:** 10 attempts / 1 hour per IP
+- **OTP Verify:** 20 attempts / 5 minutes per IP
+- **Password change:** 5 attempts / 1 hour per IP, 3 attempts / 1 hour per user
+
+### Balance Check
+
+To check your credit balance, use:
+- Web interface (page **Billing**)
+- API: **GET** `/api/billing/usage` (authentication required)
+
+---
+
+## ๐ป Code Examples
+
+### 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,
+}
+
+# Conversion
+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 (batch conversion)
+
+```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) # Polite delay
+
+# Save to a single file
+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();
+}
+
+// Usage
+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 (all scenarios)
+
+```bash
+# Basic conversion
+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"}'
+
+# For LLM (plain text)
+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
+ }'
+
+# For documentation (with metadata)
+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
+ }'
+
+# For documentation (with TOC)
+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
+ }'
+
+# Conversion history
+curl -X GET "https://markclean.online/api/converter/history?page=1&page_size=10" \
+ -H "X-API-Key: mk_live_YOUR_KEY"
+
+# Balance check
+curl -X GET "https://markclean.online/api/billing/usage" \
+ -H "X-API-Key: mk_live_YOUR_KEY"
+
+# Get available packages
+curl -X GET "https://markclean.online/api/billing/packages"
+```
+
+---
+
+## โ FAQ
+
+### ๐ Is it safe to pass an API key?
+
+**Yes**, as long as you follow these rules:
+- โ
Use HTTPS (always)
+- โ
Store the key in environment variables, not in code
+- โ
Do not log the key
+- โ Do not commit the key to Git
+- โ Do not pass the key in URL parameters
+
+### ๐ What to do if the key is compromised?
+
+1. Open `/api-keys` in the web interface
+2. Click **"Regenerate"** (the old key is deleted and a new one is created)
+3. Update the key in all of your applications
+
+> โ ๏ธ The old key stops working immediately after regeneration.
+
+### ๐ณ Can I use the API without credits?
+
+Yes, every user has a **free daily limit** (10 requests / day) that resets every 24 hours. After both the daily limit and credits are exhausted, the API will return `402 quota_exceeded`.
+
+### โก Why is `quota_incremented: false`?
+
+This is normal in three cases:
+1. **Cache hit** โ this URL was recently converted (free)
+2. **Anonymous request** โ without an API key (IP limit)
+3. **Subscription** โ the user has an active subscription, no credits are consumed
+
+### ๐ Does the API work with any site?
+
+The API works with any **publicly accessible HTML page**. Limitations:
+- โ Sites requiring authentication
+- โ Sites with strict anti-bot protection (Cloudflare challenges, etc.)
+- โ PDF, images, and other non-HTML formats
+- โ Sites that block by User-Agent
+
+### ๐ฆ Is there a page size limit?
+
+- Maximum: **5 MB** of content after conversion
+- Typical articles: 10-100 KB
+- Long technical documents: 200-500 KB
+
+Pages larger than 5 MB are not cached but are still converted.
+
+### ๐ How to use an API key with other tools?
+
+**Postman:**
+- **Authorization** tab โ Type: **API Key**
+- Key: `X-API-Key`, Value: your key, 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"
+}
+```
+
+### ๐๏ธ What happens to the API key after account deletion?
+
+**The key continues to work** until the credits run out. After account deletion:
+- โ
The API key remains active
+- โ
Existing credits can still be used
+- โ You cannot top up credits (no UI access)
+- โ You cannot create a new key
+
+This is for fairness: you paid for the credits โ you use them.
+
+### ๐ ๏ธ Is there an SDK?
+
+No official SDK yet, but the API is simple enough to use via standard HTTP clients in any language. The examples above cover the most popular languages.
+
+---
+
+## ๐ Support
+
+- ๐ง **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)
diff --git a/api.md b/markclean-api-ru.md
similarity index 100%
rename from api.md
rename to markclean-api-ru.md