Verifiziertes AgentReady.md-Zertifikat
Ausgestellt am sig: 97d565df0ca4e493 Verifizieren →

Analysierte URL

https://mdapi.io

Gemessen vor 22 Stunden

Weitere URL analysieren

KI-Ready Score

Mangelhaft

von 100

Token-Einsparung

HTML-Tokens 13.428
Markdown-Tokens 13.397
Einsparung 0%

Score-Aufschlüsselung

Zugänglichkeit 75/100
KI-Auffindbarkeit 46/100
Strukturierte Daten 0/100
Semantisches HTML 35/100
Inhaltseffizienz 60/100

Emerging Protocols

4 von 6 erkannt

Well-known-Endpunkte, nach denen KI-Agenten suchen. Erkannt bedeutet, dass ein Agent Ihren Dienst automatisch finden und verbinden kann.

  • OAuth Protected Resource RFC 9728
    /.well-known/oauth-protected-resource
  • OAuth Discovery RFC 8414
    /.well-known/oauth-authorization-server
  • MCP Server Card SEP-1649 draft
    /.well-known/mcp/server-card.json
    • v1.0.0
    • 1 tool(s)
    • 15 resource(s)
  • A2A Agent Card A2A v1.0
    /.well-known/agent-card.json
    • name: mdapi
    • v1.0.0
    • 3 capabilities
    • 7 skill(s)
  • API Catalog RFC 9727
    /.well-known/api-catalog
    • 1 API(s)
  • Agent Skills index Discovery RFC v0.2.0 draft
    /.well-known/agent-skills/index.json
    • 1 skill(s)
    • mdapi-conversion

Das bleibt nicht so. Monitoring gibt es noch nicht — tragen Sie sich ein, wir melden uns zum Start.

Sie stehen auf der Liste! Wir benachrichtigen Sie, sobald es verfügbar ist.

Was wir gemessen haben 5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot

Ihre robots.txt blockiert einige KI-Bots (GPTBot, ClaudeBot, PerplexityBot). Das verhindert, dass KI-Agenten Ihre Inhalte indexieren.

So implementieren Sie es

Aktualisieren Sie Ihre robots.txt, um KI-Bots zu erlauben. Fügen Sie explizite Allow-Regeln für GPTBot, ClaudeBot und PerplexityBot hinzu.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben Main content starts at 100% of HTML

Der Hauptinhalt erscheint spät im HTML-Dokument. KI-Agenten gewichten Inhalte, die früh erscheinen, möglicherweise höher.

So implementieren Sie es

Strukturieren Sie Ihr HTML so um, dass <main>- oder <article>-Inhalt vor Seitenleisten und ergänzenden Inhalten erscheint.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben No JSON-LD / Schema.org found

Keine Schema.org-strukturierten Daten gefunden. JSON-LD hilft KI-Agenten, faktische, strukturierte Informationen aus Ihren Seiten zu extrahieren.

So implementieren Sie es

Fügen Sie einen <script type="application/ld+json">-Block mit Schema.org-Markup hinzu. Verwenden Sie passende Typen: Article für Blogbeiträge, Product für Produktseiten, Organization für Ihre Unternehmensseite.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben No headings found

Ihre Überschriftenstruktur hat Probleme (übersprungene Ebenen oder mehrere h1-Tags). Eine saubere Hierarchie hilft KI-Agenten, die Inhaltsorganisation zu verstehen.

So implementieren Sie es

Stellen Sie sicher, dass Sie genau ein <h1> pro Seite haben und die Überschriften einer sequentiellen Reihenfolge folgen: h1 > h2 > h3. Überspringen Sie keine Ebenen (z.B. h1 direkt zu h3).

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben 0/3 OG tags present

Fehlende oder unvollständige Open-Graph-Tags. OG-Tags helfen KI-Agenten (und sozialen Plattformen), Titel, Beschreibung und Bild Ihrer Seite zu verstehen.

So implementieren Sie es

Fügen Sie og:title, og:description und og:image Meta-Tags zum <head> Ihrer Seite hinzu.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben No meta description

Keine Meta-Beschreibung gefunden. Dieses Tag bietet eine prägnante Zusammenfassung der Seite für KI-Agenten und Suchmaschinen.

So implementieren Sie es

Fügen Sie ein <meta name="description" content="...">-Tag mit einer 150-160 Zeichen langen Beschreibung des Seiteninhalts hinzu.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben 0% token reduction (HTML→Markdown)

Die HTML-zu-Markdown-Konvertierung zeigt ein niedriges Token-Reduktionsverhältnis. Ihre Seite hat übermäßiges Markup im Verhältnis zum Inhalt.

So implementieren Sie es

Entfernen Sie unnötiges Markup, Inline-Styles und präsentationsbezogene Elemente. Behalten Sie inhaltsfokussiertes HTML bei, das sauber zu Markdown konvertiert.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben Missing <article> and <main> elements

Ihre Seite verwendet keine <article>- oder <main>-Elemente. Diese semantischen Container helfen KI-Agenten, den Hauptinhalt zu identifizieren und Navigation, Seitenleisten und Fußzeilen zu ignorieren.

So implementieren Sie es

Fügen Sie ein <main>-Element um den Hauptinhalt Ihrer Seite hinzu und verwenden Sie <article> für eigenständige Inhaltsblöcke wie Blogbeiträge oder Produktbeschreibungen.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben CDN level (Cloudflare) — Content negotiation

Deine Website unterstützt Markdown für Agenten nur teilweise — unter „was wir gemessen haben" stehen die Mechanismen, die du bereits hast. Der Rest lässt mehr Agenten Markdown anfragen statt HTML zu parsen und spart ~80% Tokens.

So implementieren Sie es

  • Liefere einen Markdown-Zwilling unter einer zweiten URL, z. B. /seite.md neben /seite.
  • Füge <link rel="alternate" type="text/markdown" href="…"> in den HTML-Head ein.
  • Ergänze einen Link-HTTP-Header auf die Markdown-Version: Link: <…>; rel="alternate"; type="text/markdown".
In einen Coding-Agenten einfügen, der die Korrektur vornimmt

Was wir gemessen haben No canonical URL

Keine kanonische URL gefunden. Diese hilft KI-Agenten, die bevorzugte Version einer Seite zu identifizieren und doppelte Inhalte zu vermeiden.

So implementieren Sie es

Fügen Sie ein <link rel="canonical" href="...">-Tag hinzu, das auf die kanonische URL der Seite verweist.

In einen Coding-Agenten einfügen, der die Korrektur vornimmt
Markdown-Tokens: 13.397
\# mdapi.io - Minimal Data API I/O: a content transformation layer primitive for AI systems. Transforms documents, images, and webpages into AI-ready Markdown and structured data, optimized for LLM efficiency and token usage. ## Agent entrypoint - \*\*Start AI discovery\*\* → https://mdapi.io/.well-known/ai-discovery.json - \*\*Use skill\*\* → https://mdapi.io/.well-known/skill.md ## Quick Start Choose your entry point based on your role: | Role | Protocol | Endpoint | When to use | | ----------------------------------------------------- | ---------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | IDE / coding agent (JetBrains, Cursor, VS Code, etc.) | ACP (Agent Client Protocol) | POST /acp | You are an IDE plugin or coding agent. Use initialize + session/new + session/prompt (content arrives via session/update notifications). | | AI agent (Claude Code, Codex, OpenClaw, Hermes, etc.) | A2A (Agent-to-Agent) | POST /a2a | You are an autonomous agent. Use SendMessage with data in text parts. Supports streaming and task tracking. | | AI agent (any framework) | MCP (Model Context Protocol) | GET /mcp + POST /mcp | You need tool discovery. Use tools/call with convert tool. | | OpenAI-compatible client | OpenAI API | POST /v1/chat/completions | You already use OpenAI SDK. Pass URL/file in messages. Supports streaming. | | Direct HTTP / curl / script | REST API | GET / or POST / | Simplest path. GET returns Markdown directly. POST returns JSON with metadata. | ### Universal discovery All protocols and capabilities are described in one file: GET /.well-known/ai-discovery.json ## Features - Stateless, in-memory processing - Edge execution with automatic scaling - Prompt-driven transformation - AI-optimized output for LLMs - Pay-per-use via x402 v1/v2 or manual payment ## Supported Formats | Type | Formats | | --------- | ------------------------------ | | Documents | PDF, DOCX, XLSX, XLS, ODT, ODS | | Images | JPEG, JPG, PNG, WebP, SVG | | Text | HTML, XML, JSON, CSV, TXT | | Webpages | Any publicly accessible URL | ## Source Parameters (all protocols) Every protocol (REST, MCP, ACP, A2A, OpenAI) converges on the \*\*same conversion core\*\*, so content is specified via a single unified \`input\` parameter everywhere. | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | \`input\` | string | Content to convert: a URL (http(s)://...), a data URI (data:mime/type;base64,...), or plain text. Auto-detected: http(s):// → URL, data: → file, otherwise → text. | The \`input\` parameter is auto-detected by the core: URLs (starting with \`http://\` or \`https://\`) are fetched, data URIs (starting with \`data:\`) are decoded as files, and anything else is treated as raw text. Additional parameters (\`prompt\`, \`result\`, \`stream\`, \`token\`, \`memo\`) are orthogonal and may be combined with \`input\`. All five protocols expose the same \`input\` source and apply the same transformations, streaming, and prompt-driven processing - the only difference is the transport (REST query/JSON, MCP \`tools/call\`, ACP \`session/prompt\`, OpenAI \`messages\`, A2A \`message.parts\`). ## Limits | Limit | Value | | ------------------- | -------------------------------------------------------------------------------- | | \*\*Max file size\*\* | 50 MB | | \*\*Max URL content\*\* | 50 MB | | \*\*Rate limit\*\* | 10,000 requests per hour | | \*\*Free tier\*\* | 10 requests per day (no token required), within the service’s overall free quota | | \*\*Paid tier\*\* | min $0.01 per conversion (USDC on Solana) | | \*\*Token validity\*\* | 1 year | ## Authentication \*\*Recommended:\*\* Use \`Authorization: Bearer TOKEN\` | Method | Use Case | | -------------------- | ---------------------------------- | | Bearer (recommended) | \`-H "Authorization: Bearer TOKEN"\` | | Header | \`-H "X-Token-Required: TOKEN"\` | ## Token Activation Before you can use a paid token, you must receive a 402 response first: | Step | Description | | ---- | ------------------------------------------------------- | | 1 | Request without token → Receive 402 with NEW token+memo | | 2 | Send USDC on Solana to wallet with memo from 402 | | 3 | Retry with EXACT token+memo from 402 → Activation | | 4 | After: use token only (no memo needed) | Important: The token+memo issued in the 402 response MUST be used exactly. Using old token or different memo will be rejected. ## API Usage ### GET / (Content conversion) Simple content conversion using query parameters. Returns Markdown directly. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \`input\` | string | \* | Content to convert (URL, text, or data URI - auto-detected) | | \`prompt\` | string | | Custom instructions for LLM processing | | \`result\` | string | | Response format: \`markdown\`, \`prompt\`, or \`both\` | | \`stream\` | boolean | | Enable streaming: true for SSE response | | \`token\` | string | | Access token for paid tier | | \`memo\` | string | | Memo for token activation | \*The \`input\` parameter is required.\* > \*\*⚠️ Browser URL limit:\*\* GET requests with long \`input\` or \`prompt\` values may exceed browser URL limits (~2048 characters). Use POST with JSON body for large payloads. ### POST / (Content conversion via JSON) Supports content conversion via JSON body. The \`input\` parameter accepts URLs, text, or data URIs (auto-detected). Returns a JSON object containing the Markdown content. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \`input\` | string | \* | Content to convert (URL, text, or data URI - auto-detected) | | \`prompt\` | string | | Custom instructions for LLM processing | | \`result\` | string | | Response format: \`markdown\`, \`prompt\`, or \`both\` | | \`stream\` | boolean | | Enable streaming: true for SSE response | | \`token\` | string | | Access token for paid tier | | \`memo\` | string | | Memo for token activation | \*The \`input\` parameter is required.\* ### Result Format Parameter The \`result\` parameter controls the response format for both GET and POST requests. | Value | Description | | -------------------- | --------------------------------------------------------------- | | \`markdown\` (default) | Returns the converted Markdown content | | \`prompt\` | Returns the result of LLM processing with \`prompt\` instructions | | \`both\` | Returns both \`markdown\` and \`prompt\_result\` in the response | When \`result=both\`: - \*\*GET requests\*\* return Markdown combining \`markdown\`, followed by "## Prompt Result" and \`prompt\_result\` (always in Markdown format) - \*\*POST requests\*\* return JSON with \`markdown\` and \`prompt\_result\` fields > \*\*Auto \`result\`:\*\* When \`prompt\` is provided without an explicit \`result\`, the core automatically sets \`result="prompt"\` (LLM output only). Without \`prompt\`, default is \`result="markdown"\`. Only specify \`result\` explicitly when you need both (\`result="both"\`). ### Prompt Parameter The \`prompt\` parameter lets you specify custom instructions for the LLM to follow when generating the result. | Use Case | Example | | ------------------ | ---------------------------------------------------------- | | Summarize | \`?input=https://example.com&prompt=Summarize\` | | Extract key points | \`?input=Hello World&prompt=Extract key points\` | | Convert to JSON | \`?input=https://example.com&prompt=Convert to JSON format\` | | Analyze content | \`?input=Hello World&prompt=Analyze and explain\` | ### Streaming Parameter The \`stream\` parameter enables Server-Sent Events (SSE) streaming for real-time response delivery. \*\*Type:\*\* \`boolean\` \*\*Default:\*\* \`false\` (non-streaming) Example: \`\`\`bash curl "https://mdapi.io/?input=...&stream=true" \`\`\` Response format (OpenAI-compatible SSE, one JSON object per \`data:\` line): \`\`\`json data: {"type":"token\_info","token\_status":"valid","token\_balance":0.99,"token\_expires":1798761600} data: {"choices":\[{"index":0,"delta":{"content":" chunk"},"finish\_reason":null}\]} data: {"choices":\[{"index":0,"delta":{"content":" more"},"finish\_reason":null}\]} data: {"choices":\[{"index":0,"delta":{},"finish\_reason":"stop"}\]} data: \[DONE\] \`\`\` \*\*Native streaming per protocol.\*\* Every protocol delivers a \*real\* content stream when \`stream: true\`, but each emits it in its own native frame format (so existing clients keep working): | Protocol | Streaming frame format | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | REST | OpenAI-compatible \`choices/delta\` frames | | OpenAI | \`chat.completion.chunk\` (\`choices/delta\`) | | MCP | \`notifications/message\` content chunks, then one final \`tools/call\` result frame | | ACP | \`session/update\` notification chunks (one stable \`messageId\` per turn), then a final response carrying only \`stopReason\` | | A2A | \`result.task\` (\`TASK\_STATE\_WORKING\`) start frame, \`result.artifactUpdate\` (\`{artifact, append, lastChunk}\`) content frames, then \`result.statusUpdate\` (\`TASK\_STATE\_COMPLETED\`) - stream closes | > \*\*Note on MCP transport vs. the \`stream\` parameter.\*\* The MCP manifest advertises \`transport.type: "streamable-http"\` - that is the MCP \*transport\* > (how JSON-RPC requests are delivered to \`POST /mcp\`). It is unrelated to the \`stream\` \*parameter\*, which independently enables SSE streaming of the > conversion \*\*content\*\*. You can use MCP without streaming; and when you do pass \`stream: true\`, the content arrives as SSE frames alongside the transport. ### Response Codes | Code | Description | Response Body (GET) | Response Body (POST) | | ---- | ----------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------ | | 200 | Success | Markdown content | JSON with \`success\`, markdown, prompt\_result, metrics, token fields | | 402 | Payment Required | Markdown payment instructions (\`X-Error-Code: payment\_required\`) | JSON with \`success:false\`, \`code:"payment\_required"\`, and payment object | | 400 | Bad Request | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"invalid\_request"}\` | | 401 | Invalid Token | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"unauthorized"}\` | | 404 | Not Found | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"not\_found"}\` | | 413 | Payload Too Large | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"too\_large"}\` | | 429 | Rate Limited | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"rate\_limited"}\` | | 500 | Server Error | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"server\_error"}\` | ### Token Status The X-Token-Status header (and token\_status field in responses) indicates the current state of authentication: | Status | Description | | ------------------ | --------------------------------------------------------------------------------------- | | free | Free tier (no token required, 10 requests/day), within the service's overall free quota | | valid | Paid token active with remaining balance | | invalid | Token not found or not provided | | expired | Token validity period has ended | | exhausted | Token balance has been fully used | | expired\_pending | Activation memo has expired | | activated | Token was just activated with this request | | verification\_error | Payment verification failed | | invalid\_payment | Payment transaction is invalid | | error | Internal error during token processing | | pending | Payment required (token not yet activated) | ### Endpoints | Method | Path | Description | | ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------- | | GET | /about | About service | | GET | / | API docs or conversion (via query parameter: \`input\`, \`prompt\`, \`result\`) | | POST | / | Convert content via JSON body (supports \`input\`, \`prompt\`, \`result\` params) | | POST | /v1/chat/completions | OpenAI-compatible endpoint | | GET | /mcp | MCP server manifest | | POST | /mcp | MCP RPC endpoint (discover, tools, resources, subscriptions) | | POST | /acp | ACP RPC endpoint (IDE agents) | | POST | /a2a | A2A RPC endpoint (agent2agent) | | GET | /health | Health check | | GET | /llms.txt | API documentation | | GET | /llms-full.txt | Full API documentation | | GET | /.well-known/ai-discovery.json or /ai-discovery.json | AI discovery | | GET | /.well-known/agent.json or /agent.json | AI Agent discovery | | GET | /.well-known/agent-card.json or /agent-card.json | A2A Agent card | | GET | /.well-known/acp.json or /acp.json | ACP manifest | | GET | /.well-known/x402.json or /x402.json | x402 payment manifest | | GET | /.well-known/openapi.json or /openapi.json | OpenAPI specification (JSON) | | GET | /.well-known/openapi.yaml or /openapi.yaml | OpenAPI specification (YAML) | | GET | /.well-known/mapi.md or /mapi.md | MAPI specification (case-insensitive path MAPI.md support) | | GET | /.well-known/skill.md or /skill.md | Skill specification (case-insensitive path SKILL.md support) | | GET | /.well-known/skills/index.json | Legacy skills index | | GET | /.well-known/agent-skills/index.json | Agent Skills discovery (v0.2.0) | | GET | /.well-known/api-catalog | API catalog (linkset+json, RFC draft) | | GET | /.well-known/mcp/server-cards.json | MCP server cards index | | GET | /.well-known/plugin/plugin.json or /.well-known/plugin.json | Agent Plugins v1.0.0 manifest | | GET | /.well-known/plugin/mcp.json | Agent Plugins MCP config | | GET | /.well-known/plugin/skills/mdapi-conversion/SKILL.md | Agent Plugins conversion skill | #### Examples \`\`\`bash # URL conversion via GET (free) curl "https://mdapi.io/?input=https://example.com" # URL with prompt and result=both (returns markdown + prompt\_result) curl "https://mdapi.io/?input=https://example.com&prompt=Summarize&result=both" # Text with prompt (auto result=prompt) curl "https://mdapi.io/?input=Hello World&prompt=Summarize" # Token activation via GET (activate and use) curl -H "Authorization: Bearer YOUR\_TOKEN" -H "X-Memo-Required: YOUR\_MEMO" "https://mdapi.io/?input=https://example.com" # Paid request with token via GET (using token) curl -H "Authorization: Bearer YOUR\_TOKEN" "https://mdapi.io/?input=https://example.com" # URL conversion via POST (free) curl -X POST -H "Content-Type: application/json" -d '{"input":"https://example.com"}' "https://mdapi.io/" # Text with prompt via POST curl -X POST -H "Content-Type: application/json" -d '{"input":"Hello World","prompt":"Summarize","result":"both"}' "https://mdapi.io/" # File upload via POST (data URI) curl -X POST -H "Content-Type: application/json" -d '{"input":"data:text/plain;base64,SGVsbG8gV29ybGQ="}' "https://mdapi.io/" # Token activation via POST curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer YOUR\_TOKEN" -H "X-Memo-Required: YOUR\_MEMO" -d '{"input":"https://example.com"}' "https://mdapi.io/" # Paid request with token via POST curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer YOUR\_TOKEN" -d '{"input":"https://example.com"}' "https://mdapi.io/" \`\`\` ### OpenAI Compatible Endpoint The \`/v1/chat/completions\` endpoint provides an OpenAI‑compatible API for markdown conversion with streaming support. \*\*Supported features:\*\* - URL extraction from message content (any text containing https?://) - image\_url in messages (OpenAI format) - supports HTTP URLs and data URLs - file in messages (OpenAI format) - base64 encoded files (field \`file.data\`, optional \`file.mimeType\`; built into a \`data:\` URI for the core - no \`file.filename\` required or used) - Direct text content in messages (any text without a URL is sent to the core as the \`input\` source and converted to Markdown) - Token and memo via headers (recommended for POST) - Streaming SSE responses (\`stream: true\`) - Custom instructions with LLM processing (system messages, or user messages containing instruction keywords such as \*extract, summarize, analyze, format, convert to, write as, create, generate, json\* → LLM-driven summary/extraction/transformation) - \`prompt\` for LLM-processed output. The response surfaces \`prompt\_result\` at the top level alongside the standard \`choices\[\].message.content\` (which carries \`prompt\_result\` when prompt is set, otherwise the Markdown). \`model\` is accepted but not required (any string; the service uses its own conversion pipeline, not a remote LLM chat model, unless custom instructions trigger LLM processing). > \*\*Content via message text:\*\* the message text is passed to the core as the \`input\` parameter - the same unified source as every other protocol. URLs are auto-detected, data URIs are decoded as files, and plain text is processed directly. > See \[Source Parameters (all protocols)\](#source-parameters-all-protocols). #### Request Schema \`\`\`json { "type": "object", "properties": { "model": { "type": "string", "description": "Optional model identifier (any string accepted; not required)" }, "messages": { "type": "array", "items": { "type": "object", "properties": { "role": {"type": "string", "enum": \["user", "system", "assistant"\]}, "content": {"oneOf": \[{"type": "string"}, {"type": "array"}\]} } }, "description": "Chat messages. URL in content, image\_url or file in content for input" }, "stream": { "type": "boolean", "default": false, "description": "Enable streaming SSE responses" }, "prompt": { "type": "string", "description": "Custom instructions for LLM processing (alternative to instruction keywords in messages)" }, "result": { "type": "string", "enum": \["markdown", "prompt", "both"\], "description": "Response format when using prompt" }, "token": {"type": "string", "description": "Access token for paid tier"}, "memo": {"type": "string", "description": "Memo for token activation"}, "input": { "type": "string", "description": "Content to convert (URL, text, or data URI - auto-detected). Alternative to a URL/file embedded in messages" } }, "required": \["messages"\] } \`\`\` > \*\*Note on streaming + custom instructions:\*\* when custom instructions trigger LLM processing, the response is returned as a single completion (streaming is not applied to the LLM pass). Streaming SSE applies to the standard conversion path. ## MCP Configuration Connect mdapi.io to your MCP-compatible client (spec 2026-07-28, stateless). > \*\*Single source via \`input\`:\*\* the \`convert\` tool accepts a unified \`input\` parameter - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \[Source Parameters (all protocols)\](#source-parameters-all-protocols). ### Protocol Requirements - \*\*Transport:\*\* Streamable HTTP (POST-only for JSON-RPC, GET for manifest) - \*\*Required headers:\*\* \`MCP-Protocol-Version: 2026-07-28\` and \`Mcp-Method\` on every request; \`Mcp-Name\` additionally on \`tools/call\`, \`resources/read\`, and \`prompts/get\` - \*\*Stateless:\*\* No sessions - every request is independent - \*\*Discovery:\*\* Use \`server/discover\` to query server capabilities and supported versions ### Basic Configuration Add to your MCP config file: \`\`\`json { "mcpServers": { "mdapi": { "url": "https://mdapi.io/mcp" } } } \`\`\` > \*\*Note:\*\* No token is required to connect. A free tier is available (10 requests per day), within the service’s overall free quota. ### OpenClaw Integration OpenClaw can use mdapi.io in two ways: \*\*Option 1: Via MCP (Recommended)\*\* \`\`\`json { "mcpServers": { "mdapi": { "url": "https://mdapi.io/mcp" } } } \`\`\` \*\*Option 2: Via OpenAI-compatible endpoint\*\* \`\`\`bash openclaw config set llm.apiBase https://mdapi.io/v1 openclaw config set llm.apiKey YOUR\_TOKEN \`\`\` ### Using MCP with a token MCP does not use HTTP-level Authorization headers. The token is always passed inside the tool \`arguments\` object. \*\*Activation\*\* - include \`token\` + \`memo\` in the first request: \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "token": "YOUR\_TOKEN", "memo": "YOUR\_PAYMENT\_MEMO" } } } \`\`\` \*\*After activation\*\* - use \`token\` only (no memo needed): \`\`\`json { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "token": "YOUR\_ACTIVATED\_TOKEN" } } } \`\`\` ### MCP Tool Examples Convert with prompt and result: \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "prompt": "Summarize", "result": "both", "token": "YOUR\_TOKEN" } } } \`\`\` Process text directly: \`\`\`json { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "Hello World", "prompt": "Extract key points", "result": "prompt" } } } \`\`\` Stream with SSE (native MCP frames): \`\`\`json { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "stream": true } } } \`\`\` Response (SSE over \`streamable-http\`): intermediate \`notifications/message\` content chunks, then one final \`tools/call\` result frame with the full Markdown: \`\`\` data: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","data":" partial "}} data: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","data":" more "}} data: {"jsonrpc":"2.0","id":3,"result":{"content":\[{"type":"text","text":""}\],"isError":false}} data: \[DONE\] \`\`\` ### Using Environment Variables Or use environment variable: \`\`\`bash export MDAPI\_TOKEN=YOUR\_ACTIVATED\_TOKEN \`\`\` ## Code Examples ### JavaScript (fetch) \`\`\`javascript // Convert a URL via GET - returns Markdown directly const response = await fetch('https://mdapi.io/?input=https://example.com'); const markdown = await response.text(); console.log(markdown); \`\`\` \`\`\`javascript // Convert a URL via POST - returns JSON with metadata const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); console.log(data.markdown); \`\`\` \`\`\`javascript // Text with prompt - returns prompt\_result const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'Hello World', prompt: 'Summarize', result: 'both' }) }); const data = await response.json(); console.log(data.markdown); console.log(data.prompt\_result); \`\`\` \`\`\`javascript // File upload via data URI const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'data:text/plain;base64,SGVsbG8gV29ybGQ=' }) }); const data = await response.json(); console.log(data.markdown); \`\`\` \`\`\`javascript // Token activation - first request with token + memo const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\_TOKEN', 'X-Memo-Required': 'YOUR\_MEMO' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); // After activation, use token only (no memo needed) \`\`\` \`\`\`javascript // Streaming via OpenAI-compatible endpoint const response = await fetch('https://mdapi.io/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\_TOKEN' }, body: JSON.stringify({ model: 'markdown-v1', messages: \[{ role: 'user', content: 'Convert https://example.com' }\], stream: true }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(decoder.decode(value)); } \`\`\` ### Python \`\`\`python import requests # Convert a URL via GET - returns Markdown directly response = requests.get('https://mdapi.io/?input=https://example.com') response.raise\_for\_status() print(response.text) \`\`\` \`\`\`python import requests # Convert a URL via POST - returns JSON with metadata response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'} ) response.raise\_for\_status() data = response.json() print(data\['markdown'\]) \`\`\` \`\`\`python import requests # Text with prompt - returns prompt\_result response = requests.post( 'https://mdapi.io/', json={'input': 'Hello World', 'prompt': 'Summarize', 'result': 'both'} ) response.raise\_for\_status() data = response.json() print(data\['markdown'\]) print(data\['prompt\_result'\]) \`\`\` \`\`\`python import requests # File upload via data URI with open('document.pdf', 'rb') as f: import base64 file\_data = base64.b64encode(f.read()).decode() response = requests.post( 'https://mdapi.io/', json={'input': f'data:text/plain;base64,{file\_data}'} ) response.raise\_for\_status() data = response.json() print(data\['markdown'\]) \`\`\` \`\`\`python import requests # Token activation response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'}, headers={ 'Authorization': 'Bearer YOUR\_TOKEN', 'X-Memo-Required': 'YOUR\_MEMO' } ) response.raise\_for\_status() # After activation, use token only (no memo needed) \`\`\` \`\`\`python from openai import OpenAI # OpenAI-compatible streaming client = OpenAI(base\_url='https://mdapi.io/v1', api\_key='YOUR\_TOKEN') stream = client.chat.completions.create( model='markdown-v1', messages=\[{'role': 'user', 'content': 'Convert https://example.com'}\], stream=True ) for chunk in stream: if chunk.choices\[0\].delta.content: print(chunk.choices\[0\].delta.content, end='') \`\`\` ### Go \`\`\`go package main import ( "fmt" "io" "net/http" ) func main() { // Convert a URL via GET - returns Markdown directly resp, err := http.Get("https://mdapi.io/?input=https://example.com") if err != nil { fmt.Println("HTTP error:", err) return } defer resp.Body.Close() body, \_ := io.ReadAll(resp.Body) fmt.Println(string(body)) } \`\`\` \`\`\`go package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { // Convert a URL via POST - returns JSON with metadata payload, \_ := json.Marshal(map\[string\]string{ "input": "https://example.com", }) resp, err := http.Post( "https://mdapi.io/", "application/json", bytes.NewReader(payload), ) if err != nil { fmt.Println("HTTP error:", err) return } defer resp.Body.Close() var result map\[string\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\["markdown"\]) } \`\`\` \`\`\`go package main import ( "bytes" "encoding/base64" "encoding/json" "fmt" "net/http" "os" ) func main() { // File upload via data URI fileBytes, \_ := os.ReadFile("document.pdf") b64 := base64.StdEncoding.EncodeToString(fileBytes) payload, \_ := json.Marshal(map\[string\]string{ "input": "data:text/plain;base64," + b64, }) resp, err := http.Post( "https://mdapi.io/", "application/json", bytes.NewReader(payload), ) if err != nil { fmt.Println("HTTP error:", err) return } defer resp.Body.Close() var result map\[string\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\["markdown"\]) } \`\`\` ### Rust \`\`\`rust use anyhow::Result; use reqwest::Client; #\[tokio::main\] async fn main() -> Result<()> { let client = Client::new(); let url = "https://mdapi.io/?input=https://example.com"; let response = client.get(url).send().await?; response.error\_for\_status\_ref()?; let markdown = response.text().await?; println!("{}", markdown); Ok(()) } \`\`\` \`\`\`rust use anyhow::{Result, Context}; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #\[derive(Deserialize)\] struct ApiResponse { markdown: Option, prompt\_result: Option, } #\[derive(Serialize)\] struct ConvertRequest { input: String, #\[serde(skip\_serializing\_if = "Option::is\_none")\] prompt: Option, #\[serde(skip\_serializing\_if = "Option::is\_none")\] result: Option, } pub struct MdApiClient { client: Client, base\_url: String, token: Option, } impl MdApiClient { pub fn new(token: Option) -> Self { Self { client: Client::new(), base\_url: "https://mdapi.io".to\_string(), token, } } pub async fn convert\_url( &self, url: &str, prompt: Option<&str>, ) -> Result { let body = ConvertRequest { input: url.to\_string(), prompt: prompt.map(|p| p.to\_string()), result: prompt.map(|\_| "both".to\_string()), }; let mut request = self.client .post(&self.base\_url) .header("Content-Type", "application/json") .json(&body); if let Some(ref token) = self.token { request = request.header("Authorization", format!("Bearer {}", token)); } let response = request .send() .await .context("Failed to send HTTP request")?; response .error\_for\_status\_ref() .context("API returned error status")?; response .text() .await .context("Failed to read response body") } } // Example: convert a URL with prompt #\[tokio::main\] async fn main() -> Result<()> { let token = std::env::var("MDAPI\_TOKEN").ok(); let client = MdApiClient::new(token); let markdown = client .convert\_url("https://example.com", Some("Summarize")) .await?; println!("{}", markdown); Ok(()) } \`\`\` \`\`\`rust use anyhow::Result; use reqwest::Client; use base64::engine::general\_purpose::STANDARD; use base64::Engine; #\[tokio::main\] async fn main() -> Result<()> { let client = Client::new(); let file\_bytes = std::fs::read("document.pdf")?; let b64 = STANDARD.encode(&file\_bytes); let body = serde\_json::json!({ "input": format!("data:text/plain;base64,{}", b64) }); let response = client .post("https://mdapi.io/") .header("Content-Type", "application/json") .json(&body) .send() .await?; let data: serde\_json::Value = response.json().await?; println!("{}", data\["markdown"\]); Ok(()) } \`\`\` \`\`\`rust use anyhow::Result; use futures\_util::stream::StreamExt; use reqwest::Client; // Streaming response when using the OpenAI‑compatible endpoint with stream = true #\[tokio::main\] async fn main() -> Result<()> { let client = Client::new(); let token = std::env::var("MDAPI\_TOKEN")?; let body = serde\_json::json!({ "model": "markdown-v1", "messages": \[ { "role": "user", "content": "Convert https://example.com" } \], "stream": true }); let response = client .post("https://mdapi.io/v1/chat/completions") .header("Authorization", format!("Bearer {}", token)) .json(&body) .send() .await?; response.error\_for\_status\_ref()?; let mut stream = response.bytes\_stream(); while let Some(chunk) = stream.next().await { let chunk = chunk?; let text = String::from\_utf8\_lossy(&chunk); eprint!("{}", text); } Ok(()) } \`\`\` ### OpenAI SDK \`\`\`python from openai import OpenAI client = OpenAI( base\_url="https://mdapi.io/v1", api\_key="YOUR\_TOKEN" ) try: response = client.chat.completions.create( model="markdown-v1", messages=\[{"role": "user", "content": "Convert https://example.com"}\] ) print(response.choices\[0\].message.content) except Exception as e: print("API error:", e) \`\`\` #### OpenAI with paid token \`\`\`bash curl -X POST "https://mdapi.io/v1/chat/completions" -H "Authorization: Bearer YOUR\_TOKEN" -H "X-Memo-Required: YOUR\_MEMO" -H "Content-Type: application/json" -d '{"model":"markdown-v1","messages":\[{"role":"user","content":"Convert https://example.com"}\]}' \`\`\` After activation, use token only (no memo needed): \`\`\`bash curl -X POST "https://mdapi.io/v1/chat/completions" -H "Authorization: Bearer YOUR\_ACTIVATED\_TOKEN" -H "Content-Type: application/json" -d '{"model":"markdown-v1","messages":\[{"role":"user","content":"Convert https://example.com"}\]}' \`\`\` ## A2A Configuration Connect mdapi.io to your A2A-compatible agent (Claude Code, Codex, OpenClaw, Hermes, etc.). ### Basic Configuration Add to your A2A client configuration: \`\`\`json { "agent": { "name": "mdapi", "agentCard": { "url": "https://mdapi.io/.well-known/agent-card.json" } } } \`\`\` Or use JSON-RPC directly: \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-1", "parts": \[ { "text": "Convert https://example.com" } \] } } } \`\`\` ### A2A Methods | Method | Description | | -------------------- | --------------------------------------- | | SendMessage | Send a message to initiate conversion | | SendStreamingMessage | Send message with SSE streaming updates | | GetTask | Get task status and results by ID | | ListTasks | List tasks with optional filtering | | CancelTask | Cancel an in-progress task | | SubscribeToTask | Subscribe to task updates via SSE | > \*\*Single source via \`input\`:\*\* the \`input\` parameter in the message parts is the unified source - the same as the REST endpoint. A bare URL > inside a text part (e.g. \`"Convert https://example.com"\`) is extracted automatically and used as the conversion source, so you don't need to wrap it > in structured JSON. Instructions such as \`Summarize\` should be passed via the structured \`{ "input": "...", "prompt": "..." }\` form, not mixed into the text. ### A2A Examples #### SendMessage \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-1", "parts": \[ { "text": "Convert https://example.com" } \] } } }' \`\`\` #### SendMessage with file (data URI) \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 2, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-2", "parts": \[ { "text": "{"input":"data:text/plain;base64,SGVsbG8gV29ybGQ="}" } \] } } }' \`\`\` #### SendMessage with structured data \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 3, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-3", "parts": \[ { "data": { "input": "https://example.com", "result": "markdown" }, "mediaType": "application/json" } \] } } }' \`\`\` #### Token activation via A2A \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 7, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-7", "parts": \[ { "data": { "input": "https://example.com", "token": "YOUR\_TOKEN", "memo": "YOUR\_PAYMENT\_MEMO" }, "mediaType": "application/json" } \] } } }' \`\`\` > \*\*Note on token activation:\*\* Pass \`token\` and \`memo\` inside a \`data\` Part or as JSON inside a \`text\` Part. A2A does not use HTTP-level Authorization headers. #### Multi-turn conversation (follow-up) \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 4, "method": "SendMessage", "params": { "contextId": "ctx-uuid-1", "message": { "messageId": "msg-uuid-4", "parts": \[ { "text": "Now convert the tables to JSON" } \] } } }' \`\`\` #### GetTask \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 5, "method": "GetTask", "params": { "id": "task\_12345" } }' \`\`\` #### ListTasks \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 6, "method": "ListTasks", "params": { "contextId": "ctx\_12345", "pageSize": 10 } }' \`\`\` ### A2A Message Parts Messages use the A2A \`Part\` format (field-name discriminators per spec v1.0.0): | Type | Description | Fields | | ------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------- | | \`text\` | Plain text content or JSON-encoded params | \`text\` | | \`raw\` | File content as base64 bytes; normalized to a data URI (\`data:;base64,\`) for the core | \`raw\` (base64), \`mediaType\` (optional) | | \`data\` | Structured JSON data (core params) | \`data\` (object), \`mediaType\` (optional) | | \`url\` | URL to fetch and convert | \`url\` (http/https) | \*\*Part → Core Parameter Mapping:\*\* - \`text\` Part → \`input\` param (direct content) or JSON-encoded params (\`{ "input": "...", "prompt": "..." }\`) - \`data\` Part → merged as params (\`input\`, \`prompt\`, \`result\`, \`token\`, \`memo\`, etc.) - \`url\` Part → \`input\` param (fetched and converted) ### Message Object \`\`\`typescript interface Message { messageId: string; // REQUIRED: unique ID (e.g. "msg-uuid") contextId?: string; // Optional: group related tasks taskId?: string; // Optional: associate with existing task role: "user" | "agent"; // REQUIRED parts: Array; // REQUIRED: at least one part } \`\`\` ### Task Data Model \`\`\`typescript interface Task { id: string; // "task\_\_" contextId: string; // "ctx\_\_" status: { state: string; // "TASK\_STATE\_WORKING" | "TASK\_STATE\_COMPLETED" | "TASK\_STATE\_FAILED" | "TASK\_STATE\_CANCELED" | "TASK\_STATE\_REJECTED" timestamp: string; // ISO 8601 message?: Message; // only on failure }; artifacts?: Array<{ artifactId: string; name: string; parts: Array; }>; history?: Array; } \`\`\` ### Task States | State | Description | | ---------------------- | ----------------------------- | | \`TASK\_STATE\_WORKING\` | Task is being processed | | \`TASK\_STATE\_COMPLETED\` | Task finished successfully | | \`TASK\_STATE\_FAILED\` | Task failed during processing | | \`TASK\_STATE\_CANCELED\` | Task was canceled by client | | \`TASK\_STATE\_REJECTED\` | Task was rejected by server | ### Error Responses A2A uses JSON-RPC 2.0 error format with A2A-specific error codes: | Code | Error | Description | | -------- | --------------------------------- | ---------------------------------------------------------- | | \`-32700\` | Parse error | Invalid JSON payload | | \`-32600\` | Invalid Request | Missing required fields (message.parts, message.messageId) | | \`-32601\` | Method not found | Unknown A2A method | | \`-32001\` | Task not found | Task ID does not exist | | \`-32002\` | Task is not in a cancelable state | CancelTask on a terminal task | Unsupported media type is returned as HTTP \*\*415\*\* (not a JSON-RPC error code). \*\*Example error response:\*\* \`\`\`json { "jsonrpc": "2.0", "id": 1, "error": { "code": -32600, "message": "Invalid Request", "data": \[ { "@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": \[ { "field": "message.messageId", "description": "Message messageId is required" } \] } \] } } \`\`\` ### Streaming Use \`SendStreamingMessage\` for real-time SSE updates: \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -H "Accept: text/event-stream" -d '{ "jsonrpc": "2.0", "id": 5, "method": "SendStreamingMessage", "params": { "message": { "messageId": "msg-uuid-5", "parts": \[ { "text": "Convert https://example.com" } \] } } }' \`\`\` Response format (A2A v1.0.0 streaming sequence - exact frames the service emits): \`\`\` data: {"jsonrpc":"2.0","id":5,"result":{"task":{"id":"task\_...","contextId":"ctx\_...","status":{"state":"TASK\_STATE\_WORKING","timestamp":"..."},"artifacts":\[\]}}} data: {"jsonrpc":"2.0","id":5,"result":{"artifactUpdate":{"taskId":"task\_...","contextId":"ctx\_...","artifact":{"artifactId":"artifact\_...","name":"conversion\_result","parts":\[{"text":" partial "}\]},"append":true,"lastChunk":false}}} data: {"jsonrpc":"2.0","id":5,"result":{"artifactUpdate":{"taskId":"task\_...","contextId":"ctx\_...","artifact":{"artifactId":"artifact\_...","name":"conversion\_result","parts":\[{"text":" more "}\]},"append":true,"lastChunk":true}}} data: {"jsonrpc":"2.0","id":5,"result":{"statusUpdate":{"taskId":"task\_...","contextId":"ctx\_...","status":{"state":"TASK\_STATE\_COMPLETED","timestamp":"..."}}}} data: \[DONE\] \`\`\` The first frame carries the full \`task\` in TASK\_STATE\_WORKING; content streams as \`artifactUpdate\` frames (\`lastChunk: true\` on the final chunk); \`statusUpdate\` closes the stream with the terminal TASK\_STATE\_COMPLETED state. The persisted task (via \`GetTask\`) carries the real \`artifacts\[\].parts\[\]\` content. ### Subscribe to Task Subscribe to an existing task for real-time updates: \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -H "Accept: text/event-stream" -d '{ "jsonrpc": "2.0", "id": 6, "method": "SubscribeToTask", "params": { "id": "task\_12345" } }' \`\`\` ### Cancel Task Cancel an in-progress task: \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 7, "method": "CancelTask", "params": { "id": "task\_12345" } }' \`\`\` Response: \`\`\`json { "jsonrpc": "2.0", "id": 7, "result": { "id": "task\_12345", "contextId": "ctx\_...", "status": { "state": "TASK\_STATE\_CANCELED", "timestamp": "2026-06-26T16:00:00.000Z" } } } \`\`\` ## ACP Configuration Connect mdapi.io to your IDE or coding agent (JetBrains, Cursor, VS Code, etc.) via the Agent Client Protocol v1.0.0. ACP is a JSON-RPC 2.0 endpoint at \`POST /acp\`. Its native surface is \*\*session/turn\*\*: create an ephemeral session, then send a prompt - the converted content streams back as \`session/update\` notifications. > \*\*Single source via \`input\`:\*\* each \`session/prompt\` carries a unified \`input\` - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \[Source Parameters (all protocols)\](#source-parameters-all-protocols). ### Basic Configuration Add to your ACP client configuration (IDE plugin / agent settings): \`\`\`json { "acpServers": { "mdapi": { "url": "https://mdapi.io/acp" } } } \`\`\` Or call the JSON-RPC endpoint directly with \`POST /acp\` (Content-Type: \`application/json\`). Sessions are \*\*stateless and ephemeral\*\* - nothing is persisted server-side, so each \`session/prompt\` runs as an independent conversion: \`\`\`bash # 1. Create a session curl -X POST https://mdapi.io/acp \\ -H "Content-Type: application/json" \\ -d '{"jsonrpc":"2.0","id":1,"method":"session/new","params":{}}' # 2. Send a prompt (content streams back as session/update notifications) curl -X POST https://mdapi.io/acp \\ -H "Content-Type: application/json" \\ -d '{"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{ "sessionId":"", "prompt":\[{"type":"text","text":""}, {"type":"resource\_link","uri":"https://example.com"}\] }}' \`\`\` > \*\*Note:\*\* \`GET /acp\` is not supported (ACP is POST-only). A free tier is available without a token. ### ACP Methods | Method | Description | | ---------------- | ------------------------------------------------------------------------------- | | \`initialize\` | Handshake: return protocol version, agent capabilities, and agent info | | \`session/new\` | Create an ephemeral, stateless session (returns a \`sessionId\`) | | \`session/prompt\` | Run a conversion turn; content streams back as \`session/update\` notifications | | \`session/cancel\` | Notification (204, no response body) that best-effort cancels an in-flight turn | ### ACP Session Examples \*\*URL conversion (instructions + \`resource\_link\`):\*\* \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "session/prompt", "params": { "sessionId": "", "prompt": \[ { "type": "text", "text": "Summarize" }, { "type": "resource\_link", "uri": "https://example.com" } \] } } \`\`\` \*\*Text conversion (a bare \`text\` block becomes the input):\*\* \`\`\`json { "jsonrpc": "2.0", "id": 2, "method": "session/prompt", "params": { "sessionId": "", "prompt": \[{ "type": "text", "text": "Hello World" }\] } } \`\`\` \*\*File conversion (a \`data:\` URI in a \`text\` block is auto-detected as a file):\*\* \`\`\`json { "jsonrpc": "2.0", "id": 3, "method": "session/prompt", "params": { "sessionId": "", "prompt": \[ { "type": "text", "text": "Extract the title" }, { "type": "text", "text": "data:text/plain;base64,SGVsbG8gV29ybGQ=" } \] } } \`\`\` \*\*Token activation (first request only):\*\* every protocol passes the token per-call. Include it on any method's \`params\` as a \`token\` (+ \`memo\` on first use) - ACP does not use a session-level authenticate exchange. ### ACP Response Format Content is delivered \*\*exclusively via \`session/update\` notifications\*\*; the final \`session/prompt\` result carries only the \`stopReason\` (\`PromptResponse\`). A successful turn looks like this SSE stream: \`\`\` data: {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"","update":\[{"content":{"type":"text","text":""},"messageId":""}\]}} data: {"jsonrpc":"2.0","id":1,"result":{"stopReason":"end\_turn"}} data: \[DONE\] \`\`\` - The \`session/update\` \`update\[\]\` array holds the content chunks; \`messageId\` is stable across all chunks of a single turn. - \*\*Streaming:\*\* add \`"stream": true\` to the \`session/prompt\` \`params\` to stream the conversion live - each content chunk arrives as its own \`session/update\` notification (same \`messageId\`), then the \`PromptResponse\`. Without it, the whole turn arrives as one buffered \`session/update\` notification. Streaming is inherent to ACP; content only ever rides notifications. - A failed conversion streams an explanatory \`session/update\`, then ends with \`stopReason: "error"\`. \`session/cancel\` is a client→agent notification - the agent acknowledges it with HTTP 204 and no body. - Token status (\`X-Token-Balance\`, \`X-Token-Expires\`, \`X-Token-Status\`) is proxied from the core into the response headers. ## Usage scenarios mdapi.io is a minimal, self-documenting service-transport primitive: REST, MCP, ACP, A2A, and OpenAI-compatible endpoints all call the same transformation core, so agents can combine protocols and pass already-processed knowledge between each other. - \*\*Agent swarms\*\* - each request is handled by a stateless, automatically-scaled execution environment, so the service scales horizontally. An orchestrator fans work out across a swarm of agents, and the swarm processes very large batches of distinct resources in parallel - millions of resources in a matter of minutes, the ceiling set by how widely the work is distributed rather than by the service. Different users may freely access the same resource. - \*\*Shared vs individual payment\*\* - an orchestrator can pay once for a shared token (batching on-chain activity), or each agent can activate its own token for the exact volume it received. - \*\*Human-in-the-loop\*\* - if an agent has no wallet or insufficient funds, it returns payment details + a QR code; the human pays from a mobile device and the agent resumes. - \*\*Role switching\*\* - an agent's role can change mid-task; one agent fetches/normalizes, another summarizes/extracts, relaying compact results via the text or prompt parameters. - \*\*Cross-protocol interoperability\*\* - knowledge extracted on one protocol is reusable on another. - \*\*Bulk processing / model training\*\* - the swarm pattern turns mdapi.io into a high-throughput edge pipeline for large corpora. See https://mdapi.io/about for the full scenario walkthrough. ## Links - \*\*About service:\*\* https://mdapi.io/about - \*\*API docs:\*\* https://mdapi.io - \*\*MCP server manifest:\*\* https://mdapi.io/mcp - \*\*Health check:\*\* https://mdapi.io/health - \*\*API documentation:\*\* https://mdapi.io/llms.txt - \*\*Full API documentation:\*\* https://mdapi.io/llms-full.txt - \*\*AI discovery:\*\* https://mdapi.io/.well-known/ai-discovery.json or https://mdapi.io/ai-discovery.json - \*\*AI Agent discovery:\*\* https://mdapi.io/.well-known/agent.json or https://mdapi.io/agent.json - \*\*A2A Agent card:\*\* https://mdapi.io/.well-known/agent-card.json or https://mdapi.io/agent-card.json - \*\*ACP manifest:\*\* https://mdapi.io/.well-known/acp.json or https://mdapi.io/acp.json - \*\*x402 payment manifest:\*\* https://mdapi.io/.well-known/x402.json or https://mdapi.io/x402.json - \*\*OpenAPI specification (JSON):\*\* https://mdapi.io/.well-known/openapi.json or https://mdapi.io/openapi.json - \*\*OpenAPI specification (YAML):\*\* https://mdapi.io/.well-known/openapi.yaml or https://mdapi.io/openapi.yaml - \*\*MAPI specification (case-insensitive path MAPI.md support):\*\* https://mdapi.io/.well-known/mapi.md or https://mdapi.io/mapi.md - \*\*Skill specification (case-insensitive path SKILL.md support):\*\* https://mdapi.io/.well-known/skill.md or https://mdapi.io/skill.md - \*\*Agent Plugins package (agent-plugins.org v1.0.0):\*\* https://mdapi.io/.well-known/plugin.json - portable manifest (plugin.json) + mcp.json + skills/mdapi-conversion/SKILL.md under https://mdapi.io/.well-known/plugin/ ## External Links - \*\*github.com\*\* https://github.com/mdapiio/mdapi.io - \*\*skills.sh\*\* https://www.skills.sh/mdapiio/mdapi.io - \*\*skillsmp.com\*\* https://skillsmp.com/creators/mdapiio/mdapi.io - \*\*clawhub.ai\*\* https://clawhub.ai/mdapiio - \*\*x.com\*\* https://x.com/mdapiio ## Disclaimer \*\*The service is provided "AS IS".\*\* > mdapi.io is an edge-native service-transport primitive for AI, autonomous-agents, and the Web4 ecosystem.
\# mdapi.io - Minimal Data API I/O: a content transformation layer primitive for AI systems. Transforms documents, images, and webpages into AI-ready Markdown and structured data, optimized for LLM efficiency and token usage. ## Agent entrypoint - \*\*Start AI discovery\*\* → https://mdapi.io/.well-known/ai-discovery.json - \*\*Use skill\*\* → https://mdapi.io/.well-known/skill.md ## Quick Start Choose your entry point based on your role: | Role | Protocol | Endpoint | When to use | | ----------------------------------------------------- | ---------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | IDE / coding agent (JetBrains, Cursor, VS Code, etc.) | ACP (Agent Client Protocol) | POST /acp | You are an IDE plugin or coding agent. Use initialize + session/new + session/prompt (content arrives via session/update notifications). | | AI agent (Claude Code, Codex, OpenClaw, Hermes, etc.) | A2A (Agent-to-Agent) | POST /a2a | You are an autonomous agent. Use SendMessage with data in text parts. Supports streaming and task tracking. | | AI agent (any framework) | MCP (Model Context Protocol) | GET /mcp + POST /mcp | You need tool discovery. Use tools/call with convert tool. | | OpenAI-compatible client | OpenAI API | POST /v1/chat/completions | You already use OpenAI SDK. Pass URL/file in messages. Supports streaming. | | Direct HTTP / curl / script | REST API | GET / or POST / | Simplest path. GET returns Markdown directly. POST returns JSON with metadata. | ### Universal discovery All protocols and capabilities are described in one file: GET /.well-known/ai-discovery.json ## Features - Stateless, in-memory processing - Edge execution with automatic scaling - Prompt-driven transformation - AI-optimized output for LLMs - Pay-per-use via x402 v1/v2 or manual payment ## Supported Formats | Type | Formats | | --------- | ------------------------------ | | Documents | PDF, DOCX, XLSX, XLS, ODT, ODS | | Images | JPEG, JPG, PNG, WebP, SVG | | Text | HTML, XML, JSON, CSV, TXT | | Webpages | Any publicly accessible URL | ## Source Parameters (all protocols) Every protocol (REST, MCP, ACP, A2A, OpenAI) converges on the \*\*same conversion core\*\*, so content is specified via a single unified \`input\` parameter everywhere. | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | \`input\` | string | Content to convert: a URL (http(s)://...), a data URI (data:mime/type;base64,...), or plain text. Auto-detected: http(s):// → URL, data: → file, otherwise → text. | The \`input\` parameter is auto-detected by the core: URLs (starting with \`http://\` or \`https://\`) are fetched, data URIs (starting with \`data:\`) are decoded as files, and anything else is treated as raw text. Additional parameters (\`prompt\`, \`result\`, \`stream\`, \`token\`, \`memo\`) are orthogonal and may be combined with \`input\`. All five protocols expose the same \`input\` source and apply the same transformations, streaming, and prompt-driven processing - the only difference is the transport (REST query/JSON, MCP \`tools/call\`, ACP \`session/prompt\`, OpenAI \`messages\`, A2A \`message.parts\`). ## Limits | Limit | Value | | ------------------- | -------------------------------------------------------------------------------- | | \*\*Max file size\*\* | 50 MB | | \*\*Max URL content\*\* | 50 MB | | \*\*Rate limit\*\* | 10,000 requests per hour | | \*\*Free tier\*\* | 10 requests per day (no token required), within the service’s overall free quota | | \*\*Paid tier\*\* | min $0.01 per conversion (USDC on Solana) | | \*\*Token validity\*\* | 1 year | ## Authentication \*\*Recommended:\*\* Use \`Authorization: Bearer TOKEN\` | Method | Use Case | | -------------------- | ---------------------------------- | | Bearer (recommended) | \`-H "Authorization: Bearer TOKEN"\` | | Header | \`-H "X-Token-Required: TOKEN"\` | ## Token Activation Before you can use a paid token, you must receive a 402 response first: | Step | Description | | ---- | ------------------------------------------------------- | | 1 | Request without token → Receive 402 with NEW token+memo | | 2 | Send USDC on Solana to wallet with memo from 402 | | 3 | Retry with EXACT token+memo from 402 → Activation | | 4 | After: use token only (no memo needed) | Important: The token+memo issued in the 402 response MUST be used exactly. Using old token or different memo will be rejected. ## API Usage ### GET / (Content conversion) Simple content conversion using query parameters. Returns Markdown directly. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \`input\` | string | \* | Content to convert (URL, text, or data URI - auto-detected) | | \`prompt\` | string | | Custom instructions for LLM processing | | \`result\` | string | | Response format: \`markdown\`, \`prompt\`, or \`both\` | | \`stream\` | boolean | | Enable streaming: true for SSE response | | \`token\` | string | | Access token for paid tier | | \`memo\` | string | | Memo for token activation | \*The \`input\` parameter is required.\* > \*\*⚠️ Browser URL limit:\*\* GET requests with long \`input\` or \`prompt\` values may exceed browser URL limits (~2048 characters). Use POST with JSON body for large payloads. ### POST / (Content conversion via JSON) Supports content conversion via JSON body. The \`input\` parameter accepts URLs, text, or data URIs (auto-detected). Returns a JSON object containing the Markdown content. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \`input\` | string | \* | Content to convert (URL, text, or data URI - auto-detected) | | \`prompt\` | string | | Custom instructions for LLM processing | | \`result\` | string | | Response format: \`markdown\`, \`prompt\`, or \`both\` | | \`stream\` | boolean | | Enable streaming: true for SSE response | | \`token\` | string | | Access token for paid tier | | \`memo\` | string | | Memo for token activation | \*The \`input\` parameter is required.\* ### Result Format Parameter The \`result\` parameter controls the response format for both GET and POST requests. | Value | Description | | -------------------- | --------------------------------------------------------------- | | \`markdown\` (default) | Returns the converted Markdown content | | \`prompt\` | Returns the result of LLM processing with \`prompt\` instructions | | \`both\` | Returns both \`markdown\` and \`prompt\_result\` in the response | When \`result=both\`: - \*\*GET requests\*\* return Markdown combining \`markdown\`, followed by "## Prompt Result" and \`prompt\_result\` (always in Markdown format) - \*\*POST requests\*\* return JSON with \`markdown\` and \`prompt\_result\` fields > \*\*Auto \`result\`:\*\* When \`prompt\` is provided without an explicit \`result\`, the core automatically sets \`result="prompt"\` (LLM output only). Without \`prompt\`, default is \`result="markdown"\`. Only specify \`result\` explicitly when you need both (\`result="both"\`). ### Prompt Parameter The \`prompt\` parameter lets you specify custom instructions for the LLM to follow when generating the result. | Use Case | Example | | ------------------ | ---------------------------------------------------------- | | Summarize | \`?input=https://example.com&prompt=Summarize\` | | Extract key points | \`?input=Hello World&prompt=Extract key points\` | | Convert to JSON | \`?input=https://example.com&prompt=Convert to JSON format\` | | Analyze content | \`?input=Hello World&prompt=Analyze and explain\` | ### Streaming Parameter The \`stream\` parameter enables Server-Sent Events (SSE) streaming for real-time response delivery. \*\*Type:\*\* \`boolean\` \*\*Default:\*\* \`false\` (non-streaming) Example: \`\`\`bash curl "https://mdapi.io/?input=...&stream=true" \`\`\` Response format (OpenAI-compatible SSE, one JSON object per \`data:\` line): \`\`\`json data: {"type":"token\_info","token\_status":"valid","token\_balance":0.99,"token\_expires":1798761600} data: {"choices":\[{"index":0,"delta":{"content":" chunk"},"finish\_reason":null}\]} data: {"choices":\[{"index":0,"delta":{"content":" more"},"finish\_reason":null}\]} data: {"choices":\[{"index":0,"delta":{},"finish\_reason":"stop"}\]} data: \[DONE\] \`\`\` \*\*Native streaming per protocol.\*\* Every protocol delivers a \*real\* content stream when \`stream: true\`, but each emits it in its own native frame format (so existing clients keep working): | Protocol | Streaming frame format | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | REST | OpenAI-compatible \`choices/delta\` frames | | OpenAI | \`chat.completion.chunk\` (\`choices/delta\`) | | MCP | \`notifications/message\` content chunks, then one final \`tools/call\` result frame | | ACP | \`session/update\` notification chunks (one stable \`messageId\` per turn), then a final response carrying only \`stopReason\` | | A2A | \`result.task\` (\`TASK\_STATE\_WORKING\`) start frame, \`result.artifactUpdate\` (\`{artifact, append, lastChunk}\`) content frames, then \`result.statusUpdate\` (\`TASK\_STATE\_COMPLETED\`) - stream closes | > \*\*Note on MCP transport vs. the \`stream\` parameter.\*\* The MCP manifest advertises \`transport.type: "streamable-http"\` - that is the MCP \*transport\* > (how JSON-RPC requests are delivered to \`POST /mcp\`). It is unrelated to the \`stream\` \*parameter\*, which independently enables SSE streaming of the > conversion \*\*content\*\*. You can use MCP without streaming; and when you do pass \`stream: true\`, the content arrives as SSE frames alongside the transport. ### Response Codes | Code | Description | Response Body (GET) | Response Body (POST) | | ---- | ----------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------ | | 200 | Success | Markdown content | JSON with \`success\`, markdown, prompt\_result, metrics, token fields | | 402 | Payment Required | Markdown payment instructions (\`X-Error-Code: payment\_required\`) | JSON with \`success:false\`, \`code:"payment\_required"\`, and payment object | | 400 | Bad Request | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"invalid\_request"}\` | | 401 | Invalid Token | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"unauthorized"}\` | | 404 | Not Found | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"not\_found"}\` | | 413 | Payload Too Large | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"too\_large"}\` | | 429 | Rate Limited | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"rate\_limited"}\` | | 500 | Server Error | Markdown error + \`X-Error-Code\` header | JSON \`{"success":false,"error":"...","code":"server\_error"}\` | ### Token Status The X-Token-Status header (and token\_status field in responses) indicates the current state of authentication: | Status | Description | | ------------------ | --------------------------------------------------------------------------------------- | | free | Free tier (no token required, 10 requests/day), within the service's overall free quota | | valid | Paid token active with remaining balance | | invalid | Token not found or not provided | | expired | Token validity period has ended | | exhausted | Token balance has been fully used | | expired\_pending | Activation memo has expired | | activated | Token was just activated with this request | | verification\_error | Payment verification failed | | invalid\_payment | Payment transaction is invalid | | error | Internal error during token processing | | pending | Payment required (token not yet activated) | ### Endpoints | Method | Path | Description | | ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------- | | GET | /about | About service | | GET | / | API docs or conversion (via query parameter: \`input\`, \`prompt\`, \`result\`) | | POST | / | Convert content via JSON body (supports \`input\`, \`prompt\`, \`result\` params) | | POST | /v1/chat/completions | OpenAI-compatible endpoint | | GET | /mcp | MCP server manifest | | POST | /mcp | MCP RPC endpoint (discover, tools, resources, subscriptions) | | POST | /acp | ACP RPC endpoint (IDE agents) | | POST | /a2a | A2A RPC endpoint (agent2agent) | | GET | /health | Health check | | GET | /llms.txt | API documentation | | GET | /llms-full.txt | Full API documentation | | GET | /.well-known/ai-discovery.json or /ai-discovery.json | AI discovery | | GET | /.well-known/agent.json or /agent.json | AI Agent discovery | | GET | /.well-known/agent-card.json or /agent-card.json | A2A Agent card | | GET | /.well-known/acp.json or /acp.json | ACP manifest | | GET | /.well-known/x402.json or /x402.json | x402 payment manifest | | GET | /.well-known/openapi.json or /openapi.json | OpenAPI specification (JSON) | | GET | /.well-known/openapi.yaml or /openapi.yaml | OpenAPI specification (YAML) | | GET | /.well-known/mapi.md or /mapi.md | MAPI specification (case-insensitive path MAPI.md support) | | GET | /.well-known/skill.md or /skill.md | Skill specification (case-insensitive path SKILL.md support) | | GET | /.well-known/skills/index.json | Legacy skills index | | GET | /.well-known/agent-skills/index.json | Agent Skills discovery (v0.2.0) | | GET | /.well-known/api-catalog | API catalog (linkset+json, RFC draft) | | GET | /.well-known/mcp/server-cards.json | MCP server cards index | | GET | /.well-known/plugin/plugin.json or /.well-known/plugin.json | Agent Plugins v1.0.0 manifest | | GET | /.well-known/plugin/mcp.json | Agent Plugins MCP config | | GET | /.well-known/plugin/skills/mdapi-conversion/SKILL.md | Agent Plugins conversion skill | #### Examples \`\`\`bash # URL conversion via GET (free) curl "https://mdapi.io/?input=https://example.com" # URL with prompt and result=both (returns markdown + prompt\_result) curl "https://mdapi.io/?input=https://example.com&prompt=Summarize&result=both" # Text with prompt (auto result=prompt) curl "https://mdapi.io/?input=Hello World&prompt=Summarize" # Token activation via GET (activate and use) curl -H "Authorization: Bearer YOUR\_TOKEN" -H "X-Memo-Required: YOUR\_MEMO" "https://mdapi.io/?input=https://example.com" # Paid request with token via GET (using token) curl -H "Authorization: Bearer YOUR\_TOKEN" "https://mdapi.io/?input=https://example.com" # URL conversion via POST (free) curl -X POST -H "Content-Type: application/json" -d '{"input":"https://example.com"}' "https://mdapi.io/" # Text with prompt via POST curl -X POST -H "Content-Type: application/json" -d '{"input":"Hello World","prompt":"Summarize","result":"both"}' "https://mdapi.io/" # File upload via POST (data URI) curl -X POST -H "Content-Type: application/json" -d '{"input":"data:text/plain;base64,SGVsbG8gV29ybGQ="}' "https://mdapi.io/" # Token activation via POST curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer YOUR\_TOKEN" -H "X-Memo-Required: YOUR\_MEMO" -d '{"input":"https://example.com"}' "https://mdapi.io/" # Paid request with token via POST curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer YOUR\_TOKEN" -d '{"input":"https://example.com"}' "https://mdapi.io/" \`\`\` ### OpenAI Compatible Endpoint The \`/v1/chat/completions\` endpoint provides an OpenAI‑compatible API for markdown conversion with streaming support. \*\*Supported features:\*\* - URL extraction from message content (any text containing https?://) - image\_url in messages (OpenAI format) - supports HTTP URLs and data URLs - file in messages (OpenAI format) - base64 encoded files (field \`file.data\`, optional \`file.mimeType\`; built into a \`data:\` URI for the core - no \`file.filename\` required or used) - Direct text content in messages (any text without a URL is sent to the core as the \`input\` source and converted to Markdown) - Token and memo via headers (recommended for POST) - Streaming SSE responses (\`stream: true\`) - Custom instructions with LLM processing (system messages, or user messages containing instruction keywords such as \*extract, summarize, analyze, format, convert to, write as, create, generate, json\* → LLM-driven summary/extraction/transformation) - \`prompt\` for LLM-processed output. The response surfaces \`prompt\_result\` at the top level alongside the standard \`choices\[\].message.content\` (which carries \`prompt\_result\` when prompt is set, otherwise the Markdown). \`model\` is accepted but not required (any string; the service uses its own conversion pipeline, not a remote LLM chat model, unless custom instructions trigger LLM processing). > \*\*Content via message text:\*\* the message text is passed to the core as the \`input\` parameter - the same unified source as every other protocol. URLs are auto-detected, data URIs are decoded as files, and plain text is processed directly. > See \[Source Parameters (all protocols)\](#source-parameters-all-protocols). #### Request Schema \`\`\`json { "type": "object", "properties": { "model": { "type": "string", "description": "Optional model identifier (any string accepted; not required)" }, "messages": { "type": "array", "items": { "type": "object", "properties": { "role": {"type": "string", "enum": \["user", "system", "assistant"\]}, "content": {"oneOf": \[{"type": "string"}, {"type": "array"}\]} } }, "description": "Chat messages. URL in content, image\_url or file in content for input" }, "stream": { "type": "boolean", "default": false, "description": "Enable streaming SSE responses" }, "prompt": { "type": "string", "description": "Custom instructions for LLM processing (alternative to instruction keywords in messages)" }, "result": { "type": "string", "enum": \["markdown", "prompt", "both"\], "description": "Response format when using prompt" }, "token": {"type": "string", "description": "Access token for paid tier"}, "memo": {"type": "string", "description": "Memo for token activation"}, "input": { "type": "string", "description": "Content to convert (URL, text, or data URI - auto-detected). Alternative to a URL/file embedded in messages" } }, "required": \["messages"\] } \`\`\` > \*\*Note on streaming + custom instructions:\*\* when custom instructions trigger LLM processing, the response is returned as a single completion (streaming is not applied to the LLM pass). Streaming SSE applies to the standard conversion path. ## MCP Configuration Connect mdapi.io to your MCP-compatible client (spec 2026-07-28, stateless). > \*\*Single source via \`input\`:\*\* the \`convert\` tool accepts a unified \`input\` parameter - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \[Source Parameters (all protocols)\](#source-parameters-all-protocols). ### Protocol Requirements - \*\*Transport:\*\* Streamable HTTP (POST-only for JSON-RPC, GET for manifest) - \*\*Required headers:\*\* \`MCP-Protocol-Version: 2026-07-28\` and \`Mcp-Method\` on every request; \`Mcp-Name\` additionally on \`tools/call\`, \`resources/read\`, and \`prompts/get\` - \*\*Stateless:\*\* No sessions - every request is independent - \*\*Discovery:\*\* Use \`server/discover\` to query server capabilities and supported versions ### Basic Configuration Add to your MCP config file: \`\`\`json { "mcpServers": { "mdapi": { "url": "https://mdapi.io/mcp" } } } \`\`\` > \*\*Note:\*\* No token is required to connect. A free tier is available (10 requests per day), within the service’s overall free quota. ### OpenClaw Integration OpenClaw can use mdapi.io in two ways: \*\*Option 1: Via MCP (Recommended)\*\* \`\`\`json { "mcpServers": { "mdapi": { "url": "https://mdapi.io/mcp" } } } \`\`\` \*\*Option 2: Via OpenAI-compatible endpoint\*\* \`\`\`bash openclaw config set llm.apiBase https://mdapi.io/v1 openclaw config set llm.apiKey YOUR\_TOKEN \`\`\` ### Using MCP with a token MCP does not use HTTP-level Authorization headers. The token is always passed inside the tool \`arguments\` object. \*\*Activation\*\* - include \`token\` + \`memo\` in the first request: \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "token": "YOUR\_TOKEN", "memo": "YOUR\_PAYMENT\_MEMO" } } } \`\`\` \*\*After activation\*\* - use \`token\` only (no memo needed): \`\`\`json { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "token": "YOUR\_ACTIVATED\_TOKEN" } } } \`\`\` ### MCP Tool Examples Convert with prompt and result: \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "prompt": "Summarize", "result": "both", "token": "YOUR\_TOKEN" } } } \`\`\` Process text directly: \`\`\`json { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "Hello World", "prompt": "Extract key points", "result": "prompt" } } } \`\`\` Stream with SSE (native MCP frames): \`\`\`json { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "convert", "arguments": { "input": "https://example.com", "stream": true } } } \`\`\` Response (SSE over \`streamable-http\`): intermediate \`notifications/message\` content chunks, then one final \`tools/call\` result frame with the full Markdown: \`\`\` data: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","data":" partial "}} data: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","data":" more "}} data: {"jsonrpc":"2.0","id":3,"result":{"content":\[{"type":"text","text":""}\],"isError":false}} data: \[DONE\] \`\`\` ### Using Environment Variables Or use environment variable: \`\`\`bash export MDAPI\_TOKEN=YOUR\_ACTIVATED\_TOKEN \`\`\` ## Code Examples ### JavaScript (fetch) \`\`\`javascript // Convert a URL via GET - returns Markdown directly const response = await fetch('https://mdapi.io/?input=https://example.com'); const markdown = await response.text(); console.log(markdown); \`\`\` \`\`\`javascript // Convert a URL via POST - returns JSON with metadata const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); console.log(data.markdown); \`\`\` \`\`\`javascript // Text with prompt - returns prompt\_result const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'Hello World', prompt: 'Summarize', result: 'both' }) }); const data = await response.json(); console.log(data.markdown); console.log(data.prompt\_result); \`\`\` \`\`\`javascript // File upload via data URI const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'data:text/plain;base64,SGVsbG8gV29ybGQ=' }) }); const data = await response.json(); console.log(data.markdown); \`\`\` \`\`\`javascript // Token activation - first request with token + memo const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\_TOKEN', 'X-Memo-Required': 'YOUR\_MEMO' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); // After activation, use token only (no memo needed) \`\`\` \`\`\`javascript // Streaming via OpenAI-compatible endpoint const response = await fetch('https://mdapi.io/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\_TOKEN' }, body: JSON.stringify({ model: 'markdown-v1', messages: \[{ role: 'user', content: 'Convert https://example.com' }\], stream: true }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(decoder.decode(value)); } \`\`\` ### Python \`\`\`python import requests # Convert a URL via GET - returns Markdown directly response = requests.get('https://mdapi.io/?input=https://example.com') response.raise\_for\_status() print(response.text) \`\`\` \`\`\`python import requests # Convert a URL via POST - returns JSON with metadata response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'} ) response.raise\_for\_status() data = response.json() print(data\['markdown'\]) \`\`\` \`\`\`python import requests # Text with prompt - returns prompt\_result response = requests.post( 'https://mdapi.io/', json={'input': 'Hello World', 'prompt': 'Summarize', 'result': 'both'} ) response.raise\_for\_status() data = response.json() print(data\['markdown'\]) print(data\['prompt\_result'\]) \`\`\` \`\`\`python import requests # File upload via data URI with open('document.pdf', 'rb') as f: import base64 file\_data = base64.b64encode(f.read()).decode() response = requests.post( 'https://mdapi.io/', json={'input': f'data:text/plain;base64,{file\_data}'} ) response.raise\_for\_status() data = response.json() print(data\['markdown'\]) \`\`\` \`\`\`python import requests # Token activation response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'}, headers={ 'Authorization': 'Bearer YOUR\_TOKEN', 'X-Memo-Required': 'YOUR\_MEMO' } ) response.raise\_for\_status() # After activation, use token only (no memo needed) \`\`\` \`\`\`python from openai import OpenAI # OpenAI-compatible streaming client = OpenAI(base\_url='https://mdapi.io/v1', api\_key='YOUR\_TOKEN') stream = client.chat.completions.create( model='markdown-v1', messages=\[{'role': 'user', 'content': 'Convert https://example.com'}\], stream=True ) for chunk in stream: if chunk.choices\[0\].delta.content: print(chunk.choices\[0\].delta.content, end='') \`\`\` ### Go \`\`\`go package main import ( "fmt" "io" "net/http" ) func main() { // Convert a URL via GET - returns Markdown directly resp, err := http.Get("https://mdapi.io/?input=https://example.com") if err != nil { fmt.Println("HTTP error:", err) return } defer resp.Body.Close() body, \_ := io.ReadAll(resp.Body) fmt.Println(string(body)) } \`\`\` \`\`\`go package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { // Convert a URL via POST - returns JSON with metadata payload, \_ := json.Marshal(map\[string\]string{ "input": "https://example.com", }) resp, err := http.Post( "https://mdapi.io/", "application/json", bytes.NewReader(payload), ) if err != nil { fmt.Println("HTTP error:", err) return } defer resp.Body.Close() var result map\[string\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\["markdown"\]) } \`\`\` \`\`\`go package main import ( "bytes" "encoding/base64" "encoding/json" "fmt" "net/http" "os" ) func main() { // File upload via data URI fileBytes, \_ := os.ReadFile("document.pdf") b64 := base64.StdEncoding.EncodeToString(fileBytes) payload, \_ := json.Marshal(map\[string\]string{ "input": "data:text/plain;base64," + b64, }) resp, err := http.Post( "https://mdapi.io/", "application/json", bytes.NewReader(payload), ) if err != nil { fmt.Println("HTTP error:", err) return } defer resp.Body.Close() var result map\[string\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\["markdown"\]) } \`\`\` ### Rust \`\`\`rust use anyhow::Result; use reqwest::Client; #\[tokio::main\] async fn main() -> Result<()> { let client = Client::new(); let url = "https://mdapi.io/?input=https://example.com"; let response = client.get(url).send().await?; response.error\_for\_status\_ref()?; let markdown = response.text().await?; println!("{}", markdown); Ok(()) } \`\`\` \`\`\`rust use anyhow::{Result, Context}; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #\[derive(Deserialize)\] struct ApiResponse { markdown: Option, prompt\_result: Option, } #\[derive(Serialize)\] struct ConvertRequest { input: String, #\[serde(skip\_serializing\_if = "Option::is\_none")\] prompt: Option, #\[serde(skip\_serializing\_if = "Option::is\_none")\] result: Option, } pub struct MdApiClient { client: Client, base\_url: String, token: Option, } impl MdApiClient { pub fn new(token: Option) -> Self { Self { client: Client::new(), base\_url: "https://mdapi.io".to\_string(), token, } } pub async fn convert\_url( &self, url: &str, prompt: Option<&str>, ) -> Result { let body = ConvertRequest { input: url.to\_string(), prompt: prompt.map(|p| p.to\_string()), result: prompt.map(|\_| "both".to\_string()), }; let mut request = self.client .post(&self.base\_url) .header("Content-Type", "application/json") .json(&body); if let Some(ref token) = self.token { request = request.header("Authorization", format!("Bearer {}", token)); } let response = request .send() .await .context("Failed to send HTTP request")?; response .error\_for\_status\_ref() .context("API returned error status")?; response .text() .await .context("Failed to read response body") } } // Example: convert a URL with prompt #\[tokio::main\] async fn main() -> Result<()> { let token = std::env::var("MDAPI\_TOKEN").ok(); let client = MdApiClient::new(token); let markdown = client .convert\_url("https://example.com", Some("Summarize")) .await?; println!("{}", markdown); Ok(()) } \`\`\` \`\`\`rust use anyhow::Result; use reqwest::Client; use base64::engine::general\_purpose::STANDARD; use base64::Engine; #\[tokio::main\] async fn main() -> Result<()> { let client = Client::new(); let file\_bytes = std::fs::read("document.pdf")?; let b64 = STANDARD.encode(&file\_bytes); let body = serde\_json::json!({ "input": format!("data:text/plain;base64,{}", b64) }); let response = client .post("https://mdapi.io/") .header("Content-Type", "application/json") .json(&body) .send() .await?; let data: serde\_json::Value = response.json().await?; println!("{}", data\["markdown"\]); Ok(()) } \`\`\` \`\`\`rust use anyhow::Result; use futures\_util::stream::StreamExt; use reqwest::Client; // Streaming response when using the OpenAI‑compatible endpoint with stream = true #\[tokio::main\] async fn main() -> Result<()> { let client = Client::new(); let token = std::env::var("MDAPI\_TOKEN")?; let body = serde\_json::json!({ "model": "markdown-v1", "messages": \[ { "role": "user", "content": "Convert https://example.com" } \], "stream": true }); let response = client .post("https://mdapi.io/v1/chat/completions") .header("Authorization", format!("Bearer {}", token)) .json(&body) .send() .await?; response.error\_for\_status\_ref()?; let mut stream = response.bytes\_stream(); while let Some(chunk) = stream.next().await { let chunk = chunk?; let text = String::from\_utf8\_lossy(&chunk); eprint!("{}", text); } Ok(()) } \`\`\` ### OpenAI SDK \`\`\`python from openai import OpenAI client = OpenAI( base\_url="https://mdapi.io/v1", api\_key="YOUR\_TOKEN" ) try: response = client.chat.completions.create( model="markdown-v1", messages=\[{"role": "user", "content": "Convert https://example.com"}\] ) print(response.choices\[0\].message.content) except Exception as e: print("API error:", e) \`\`\` #### OpenAI with paid token \`\`\`bash curl -X POST "https://mdapi.io/v1/chat/completions" -H "Authorization: Bearer YOUR\_TOKEN" -H "X-Memo-Required: YOUR\_MEMO" -H "Content-Type: application/json" -d '{"model":"markdown-v1","messages":\[{"role":"user","content":"Convert https://example.com"}\]}' \`\`\` After activation, use token only (no memo needed): \`\`\`bash curl -X POST "https://mdapi.io/v1/chat/completions" -H "Authorization: Bearer YOUR\_ACTIVATED\_TOKEN" -H "Content-Type: application/json" -d '{"model":"markdown-v1","messages":\[{"role":"user","content":"Convert https://example.com"}\]}' \`\`\` ## A2A Configuration Connect mdapi.io to your A2A-compatible agent (Claude Code, Codex, OpenClaw, Hermes, etc.). ### Basic Configuration Add to your A2A client configuration: \`\`\`json { "agent": { "name": "mdapi", "agentCard": { "url": "https://mdapi.io/.well-known/agent-card.json" } } } \`\`\` Or use JSON-RPC directly: \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-1", "parts": \[ { "text": "Convert https://example.com" } \] } } } \`\`\` ### A2A Methods | Method | Description | | -------------------- | --------------------------------------- | | SendMessage | Send a message to initiate conversion | | SendStreamingMessage | Send message with SSE streaming updates | | GetTask | Get task status and results by ID | | ListTasks | List tasks with optional filtering | | CancelTask | Cancel an in-progress task | | SubscribeToTask | Subscribe to task updates via SSE | > \*\*Single source via \`input\`:\*\* the \`input\` parameter in the message parts is the unified source - the same as the REST endpoint. A bare URL > inside a text part (e.g. \`"Convert https://example.com"\`) is extracted automatically and used as the conversion source, so you don't need to wrap it > in structured JSON. Instructions such as \`Summarize\` should be passed via the structured \`{ "input": "...", "prompt": "..." }\` form, not mixed into the text. ### A2A Examples #### SendMessage \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-1", "parts": \[ { "text": "Convert https://example.com" } \] } } }' \`\`\` #### SendMessage with file (data URI) \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 2, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-2", "parts": \[ { "text": "{"input":"data:text/plain;base64,SGVsbG8gV29ybGQ="}" } \] } } }' \`\`\` #### SendMessage with structured data \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 3, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-3", "parts": \[ { "data": { "input": "https://example.com", "result": "markdown" }, "mediaType": "application/json" } \] } } }' \`\`\` #### Token activation via A2A \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 7, "method": "SendMessage", "params": { "message": { "messageId": "msg-uuid-7", "parts": \[ { "data": { "input": "https://example.com", "token": "YOUR\_TOKEN", "memo": "YOUR\_PAYMENT\_MEMO" }, "mediaType": "application/json" } \] } } }' \`\`\` > \*\*Note on token activation:\*\* Pass \`token\` and \`memo\` inside a \`data\` Part or as JSON inside a \`text\` Part. A2A does not use HTTP-level Authorization headers. #### Multi-turn conversation (follow-up) \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 4, "method": "SendMessage", "params": { "contextId": "ctx-uuid-1", "message": { "messageId": "msg-uuid-4", "parts": \[ { "text": "Now convert the tables to JSON" } \] } } }' \`\`\` #### GetTask \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 5, "method": "GetTask", "params": { "id": "task\_12345" } }' \`\`\` #### ListTasks \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 6, "method": "ListTasks", "params": { "contextId": "ctx\_12345", "pageSize": 10 } }' \`\`\` ### A2A Message Parts Messages use the A2A \`Part\` format (field-name discriminators per spec v1.0.0): | Type | Description | Fields | | ------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------- | | \`text\` | Plain text content or JSON-encoded params | \`text\` | | \`raw\` | File content as base64 bytes; normalized to a data URI (\`data:;base64,\`) for the core | \`raw\` (base64), \`mediaType\` (optional) | | \`data\` | Structured JSON data (core params) | \`data\` (object), \`mediaType\` (optional) | | \`url\` | URL to fetch and convert | \`url\` (http/https) | \*\*Part → Core Parameter Mapping:\*\* - \`text\` Part → \`input\` param (direct content) or JSON-encoded params (\`{ "input": "...", "prompt": "..." }\`) - \`data\` Part → merged as params (\`input\`, \`prompt\`, \`result\`, \`token\`, \`memo\`, etc.) - \`url\` Part → \`input\` param (fetched and converted) ### Message Object \`\`\`typescript interface Message { messageId: string; // REQUIRED: unique ID (e.g. "msg-uuid") contextId?: string; // Optional: group related tasks taskId?: string; // Optional: associate with existing task role: "user" | "agent"; // REQUIRED parts: Array; // REQUIRED: at least one part } \`\`\` ### Task Data Model \`\`\`typescript interface Task { id: string; // "task\_\_" contextId: string; // "ctx\_\_" status: { state: string; // "TASK\_STATE\_WORKING" | "TASK\_STATE\_COMPLETED" | "TASK\_STATE\_FAILED" | "TASK\_STATE\_CANCELED" | "TASK\_STATE\_REJECTED" timestamp: string; // ISO 8601 message?: Message; // only on failure }; artifacts?: Array<{ artifactId: string; name: string; parts: Array; }>; history?: Array; } \`\`\` ### Task States | State | Description | | ---------------------- | ----------------------------- | | \`TASK\_STATE\_WORKING\` | Task is being processed | | \`TASK\_STATE\_COMPLETED\` | Task finished successfully | | \`TASK\_STATE\_FAILED\` | Task failed during processing | | \`TASK\_STATE\_CANCELED\` | Task was canceled by client | | \`TASK\_STATE\_REJECTED\` | Task was rejected by server | ### Error Responses A2A uses JSON-RPC 2.0 error format with A2A-specific error codes: | Code | Error | Description | | -------- | --------------------------------- | ---------------------------------------------------------- | | \`-32700\` | Parse error | Invalid JSON payload | | \`-32600\` | Invalid Request | Missing required fields (message.parts, message.messageId) | | \`-32601\` | Method not found | Unknown A2A method | | \`-32001\` | Task not found | Task ID does not exist | | \`-32002\` | Task is not in a cancelable state | CancelTask on a terminal task | Unsupported media type is returned as HTTP \*\*415\*\* (not a JSON-RPC error code). \*\*Example error response:\*\* \`\`\`json { "jsonrpc": "2.0", "id": 1, "error": { "code": -32600, "message": "Invalid Request", "data": \[ { "@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": \[ { "field": "message.messageId", "description": "Message messageId is required" } \] } \] } } \`\`\` ### Streaming Use \`SendStreamingMessage\` for real-time SSE updates: \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -H "Accept: text/event-stream" -d '{ "jsonrpc": "2.0", "id": 5, "method": "SendStreamingMessage", "params": { "message": { "messageId": "msg-uuid-5", "parts": \[ { "text": "Convert https://example.com" } \] } } }' \`\`\` Response format (A2A v1.0.0 streaming sequence - exact frames the service emits): \`\`\` data: {"jsonrpc":"2.0","id":5,"result":{"task":{"id":"task\_...","contextId":"ctx\_...","status":{"state":"TASK\_STATE\_WORKING","timestamp":"..."},"artifacts":\[\]}}} data: {"jsonrpc":"2.0","id":5,"result":{"artifactUpdate":{"taskId":"task\_...","contextId":"ctx\_...","artifact":{"artifactId":"artifact\_...","name":"conversion\_result","parts":\[{"text":" partial "}\]},"append":true,"lastChunk":false}}} data: {"jsonrpc":"2.0","id":5,"result":{"artifactUpdate":{"taskId":"task\_...","contextId":"ctx\_...","artifact":{"artifactId":"artifact\_...","name":"conversion\_result","parts":\[{"text":" more "}\]},"append":true,"lastChunk":true}}} data: {"jsonrpc":"2.0","id":5,"result":{"statusUpdate":{"taskId":"task\_...","contextId":"ctx\_...","status":{"state":"TASK\_STATE\_COMPLETED","timestamp":"..."}}}} data: \[DONE\] \`\`\` The first frame carries the full \`task\` in TASK\_STATE\_WORKING; content streams as \`artifactUpdate\` frames (\`lastChunk: true\` on the final chunk); \`statusUpdate\` closes the stream with the terminal TASK\_STATE\_COMPLETED state. The persisted task (via \`GetTask\`) carries the real \`artifacts\[\].parts\[\]\` content. ### Subscribe to Task Subscribe to an existing task for real-time updates: \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -H "Accept: text/event-stream" -d '{ "jsonrpc": "2.0", "id": 6, "method": "SubscribeToTask", "params": { "id": "task\_12345" } }' \`\`\` ### Cancel Task Cancel an in-progress task: \`\`\`bash curl -X POST https://mdapi.io/a2a -H "Content-Type: application/a2a+json" -d '{ "jsonrpc": "2.0", "id": 7, "method": "CancelTask", "params": { "id": "task\_12345" } }' \`\`\` Response: \`\`\`json { "jsonrpc": "2.0", "id": 7, "result": { "id": "task\_12345", "contextId": "ctx\_...", "status": { "state": "TASK\_STATE\_CANCELED", "timestamp": "2026-06-26T16:00:00.000Z" } } } \`\`\` ## ACP Configuration Connect mdapi.io to your IDE or coding agent (JetBrains, Cursor, VS Code, etc.) via the Agent Client Protocol v1.0.0. ACP is a JSON-RPC 2.0 endpoint at \`POST /acp\`. Its native surface is \*\*session/turn\*\*: create an ephemeral session, then send a prompt - the converted content streams back as \`session/update\` notifications. > \*\*Single source via \`input\`:\*\* each \`session/prompt\` carries a unified \`input\` - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \[Source Parameters (all protocols)\](#source-parameters-all-protocols). ### Basic Configuration Add to your ACP client configuration (IDE plugin / agent settings): \`\`\`json { "acpServers": { "mdapi": { "url": "https://mdapi.io/acp" } } } \`\`\` Or call the JSON-RPC endpoint directly with \`POST /acp\` (Content-Type: \`application/json\`). Sessions are \*\*stateless and ephemeral\*\* - nothing is persisted server-side, so each \`session/prompt\` runs as an independent conversion: \`\`\`bash # 1. Create a session curl -X POST https://mdapi.io/acp \\ -H "Content-Type: application/json" \\ -d '{"jsonrpc":"2.0","id":1,"method":"session/new","params":{}}' # 2. Send a prompt (content streams back as session/update notifications) curl -X POST https://mdapi.io/acp \\ -H "Content-Type: application/json" \\ -d '{"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{ "sessionId":"", "prompt":\[{"type":"text","text":""}, {"type":"resource\_link","uri":"https://example.com"}\] }}' \`\`\` > \*\*Note:\*\* \`GET /acp\` is not supported (ACP is POST-only). A free tier is available without a token. ### ACP Methods | Method | Description | | ---------------- | ------------------------------------------------------------------------------- | | \`initialize\` | Handshake: return protocol version, agent capabilities, and agent info | | \`session/new\` | Create an ephemeral, stateless session (returns a \`sessionId\`) | | \`session/prompt\` | Run a conversion turn; content streams back as \`session/update\` notifications | | \`session/cancel\` | Notification (204, no response body) that best-effort cancels an in-flight turn | ### ACP Session Examples \*\*URL conversion (instructions + \`resource\_link\`):\*\* \`\`\`json { "jsonrpc": "2.0", "id": 1, "method": "session/prompt", "params": { "sessionId": "", "prompt": \[ { "type": "text", "text": "Summarize" }, { "type": "resource\_link", "uri": "https://example.com" } \] } } \`\`\` \*\*Text conversion (a bare \`text\` block becomes the input):\*\* \`\`\`json { "jsonrpc": "2.0", "id": 2, "method": "session/prompt", "params": { "sessionId": "", "prompt": \[{ "type": "text", "text": "Hello World" }\] } } \`\`\` \*\*File conversion (a \`data:\` URI in a \`text\` block is auto-detected as a file):\*\* \`\`\`json { "jsonrpc": "2.0", "id": 3, "method": "session/prompt", "params": { "sessionId": "", "prompt": \[ { "type": "text", "text": "Extract the title" }, { "type": "text", "text": "data:text/plain;base64,SGVsbG8gV29ybGQ=" } \] } } \`\`\` \*\*Token activation (first request only):\*\* every protocol passes the token per-call. Include it on any method's \`params\` as a \`token\` (+ \`memo\` on first use) - ACP does not use a session-level authenticate exchange. ### ACP Response Format Content is delivered \*\*exclusively via \`session/update\` notifications\*\*; the final \`session/prompt\` result carries only the \`stopReason\` (\`PromptResponse\`). A successful turn looks like this SSE stream: \`\`\` data: {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"","update":\[{"content":{"type":"text","text":""},"messageId":""}\]}} data: {"jsonrpc":"2.0","id":1,"result":{"stopReason":"end\_turn"}} data: \[DONE\] \`\`\` - The \`session/update\` \`update\[\]\` array holds the content chunks; \`messageId\` is stable across all chunks of a single turn. - \*\*Streaming:\*\* add \`"stream": true\` to the \`session/prompt\` \`params\` to stream the conversion live - each content chunk arrives as its own \`session/update\` notification (same \`messageId\`), then the \`PromptResponse\`. Without it, the whole turn arrives as one buffered \`session/update\` notification. Streaming is inherent to ACP; content only ever rides notifications. - A failed conversion streams an explanatory \`session/update\`, then ends with \`stopReason: "error"\`. \`session/cancel\` is a client→agent notification - the agent acknowledges it with HTTP 204 and no body. - Token status (\`X-Token-Balance\`, \`X-Token-Expires\`, \`X-Token-Status\`) is proxied from the core into the response headers. ## Usage scenarios mdapi.io is a minimal, self-documenting service-transport primitive: REST, MCP, ACP, A2A, and OpenAI-compatible endpoints all call the same transformation core, so agents can combine protocols and pass already-processed knowledge between each other. - \*\*Agent swarms\*\* - each request is handled by a stateless, automatically-scaled execution environment, so the service scales horizontally. An orchestrator fans work out across a swarm of agents, and the swarm processes very large batches of distinct resources in parallel - millions of resources in a matter of minutes, the ceiling set by how widely the work is distributed rather than by the service. Different users may freely access the same resource. - \*\*Shared vs individual payment\*\* - an orchestrator can pay once for a shared token (batching on-chain activity), or each agent can activate its own token for the exact volume it received. - \*\*Human-in-the-loop\*\* - if an agent has no wallet or insufficient funds, it returns payment details + a QR code; the human pays from a mobile device and the agent resumes. - \*\*Role switching\*\* - an agent's role can change mid-task; one agent fetches/normalizes, another summarizes/extracts, relaying compact results via the text or prompt parameters. - \*\*Cross-protocol interoperability\*\* - knowledge extracted on one protocol is reusable on another. - \*\*Bulk processing / model training\*\* - the swarm pattern turns mdapi.io into a high-throughput edge pipeline for large corpora. See https://mdapi.io/about for the full scenario walkthrough. ## Links - \*\*About service:\*\* https://mdapi.io/about - \*\*API docs:\*\* https://mdapi.io - \*\*MCP server manifest:\*\* https://mdapi.io/mcp - \*\*Health check:\*\* https://mdapi.io/health - \*\*API documentation:\*\* https://mdapi.io/llms.txt - \*\*Full API documentation:\*\* https://mdapi.io/llms-full.txt - \*\*AI discovery:\*\* https://mdapi.io/.well-known/ai-discovery.json or https://mdapi.io/ai-discovery.json - \*\*AI Agent discovery:\*\* https://mdapi.io/.well-known/agent.json or https://mdapi.io/agent.json - \*\*A2A Agent card:\*\* https://mdapi.io/.well-known/agent-card.json or https://mdapi.io/agent-card.json - \*\*ACP manifest:\*\* https://mdapi.io/.well-known/acp.json or https://mdapi.io/acp.json - \*\*x402 payment manifest:\*\* https://mdapi.io/.well-known/x402.json or https://mdapi.io/x402.json - \*\*OpenAPI specification (JSON):\*\* https://mdapi.io/.well-known/openapi.json or https://mdapi.io/openapi.json - \*\*OpenAPI specification (YAML):\*\* https://mdapi.io/.well-known/openapi.yaml or https://mdapi.io/openapi.yaml - \*\*MAPI specification (case-insensitive path MAPI.md support):\*\* https://mdapi.io/.well-known/mapi.md or https://mdapi.io/mapi.md - \*\*Skill specification (case-insensitive path SKILL.md support):\*\* https://mdapi.io/.well-known/skill.md or https://mdapi.io/skill.md - \*\*Agent Plugins package (agent-plugins.org v1.0.0):\*\* https://mdapi.io/.well-known/plugin.json - portable manifest (plugin.json) + mcp.json + skills/mdapi-conversion/SKILL.md under https://mdapi.io/.well-known/plugin/ ## External Links - \*\*github.com\*\* https://github.com/mdapiio/mdapi.io - \*\*skills.sh\*\* https://www.skills.sh/mdapiio/mdapi.io - \*\*skillsmp.com\*\* https://skillsmp.com/creators/mdapiio/mdapi.io - \*\*clawhub.ai\*\* https://clawhub.ai/mdapiio - \*\*x.com\*\* https://x.com/mdapiio ## Disclaimer \*\*The service is provided "AS IS".\*\* > mdapi.io is an edge-native service-transport primitive for AI, autonomous-agents, and the Web4 ecosystem.

Laden Sie diese Datei als /index.md auf Ihren Server hoch, damit KI-Agenten auf eine saubere Version Ihrer Seite zugreifen können. Sie können auch die Accept: text/markdown-Inhaltsverhandlung konfigurieren, um sie automatisch auszuliefern.

Unsere Empfehlung

llms.txt herunterladen
# mdapi.io

## Main
- [Home](https://mdapi.io)

Vollständige llms.txt erfordert eine domainweite Analyse (kommt bald)

Laden Sie diese Datei als https://mdapi.io/llms.txt im Stammverzeichnis Ihrer Domain hoch. KI-Agenten wie ChatGPT, Claude und Perplexity prüfen diese Datei, um Ihre Website-Struktur zu verstehen.

Diese Website hat bereits eine llms.txt-Datei.

Gültiges Format
# mdapi.io - Minimal Data API I/O: a content transformation layer primitive for AI systems.


Transforms documents, images, and webpages into AI-ready Markdown and structured data, optimized for LLM efficiency and token usage.

## Agent entrypoint

- **Start AI discovery** → https://mdapi.io/.well-known/ai-discovery.json
- **Use skill** → https://mdapi.io/.well-known/skill.md

## Quick Start

Choose your entry point based on your role:

| Role                                                  | Protocol                     | Endpoint                  | When to use                                                                                                                              |
| ----------------------------------------------------- | ---------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| IDE / coding agent (JetBrains, Cursor, VS Code, etc.) | ACP (Agent Client Protocol)  | POST /acp                 | You are an IDE plugin or coding agent. Use initialize + session/new + session/prompt (content arrives via session/update notifications). |
| AI agent (Claude Code, Codex, OpenClaw, Hermes, etc.) | A2A (Agent-to-Agent)         | POST /a2a                 | You are an autonomous agent. Use SendMessage with data in text parts. Supports streaming and task tracking.                              |
| AI agent (any framework)                              | MCP (Model Context Protocol) | GET /mcp + POST /mcp      | You need tool discovery. Use tools/call with convert tool.                                                                               |
| OpenAI-compatible client                              | OpenAI API                   | POST /v1/chat/completions | You already use OpenAI SDK. Pass URL/file in messages. Supports streaming.                                                               |
| Direct HTTP / curl / script                           | REST API                     | GET / or POST /           | Simplest path. GET returns Markdown directly. POST returns JSON with metadata.                                                           |

### Universal discovery

All protocols and capabilities are described in one file:
GET /.well-known/ai-discovery.json

## Features

- Stateless, in-memory processing
- Edge execution with automatic scaling
- Prompt-driven transformation
- AI-optimized output for LLMs
- Pay-per-use via x402 v1/v2 or manual payment

## Supported Formats

| Type      | Formats                        |
| --------- | ------------------------------ |
| Documents | PDF, DOCX, XLSX, XLS, ODT, ODS |
| Images    | JPEG, JPG, PNG, WebP, SVG      |
| Text      | HTML, XML, JSON, CSV, TXT      |
| Webpages  | Any publicly accessible URL    |

## Limits

- Max file size: 50 MB
- Max URL content: 50 MB
- Rate limit: 10,000 requests per hour
- Free tier: 10 requests per day (no token required), within the service’s overall free quota
- Paid tier: min $0.01 per conversion (USDC on Solana)
- Token validity: 1 year

## Authentication

- Bearer: Authorization: Bearer TOKEN
- Header: X-Token-Required: TOKEN

## Token Activation

Before using a paid token, it must be activated:
1. Receive 402 with NEW token+memo (pending created)
2. Send USDC on Solana to wallet with memo from 402
3. Retry request with EXACT token+memo from 402
4. After activation: use token only

Important: The token+memo in 402 response MUST be used - old token or memo will be rejected.

## Method Semantics

| Method | Response Format | Notes                                     |
| ------ | --------------- | ----------------------------------------- |
| GET    | Markdown        | Always returns Markdown, including errors |
| POST   | JSON            | Always returns JSON, including errors     |
| Error  | Same as method  | GET errors = Markdown, POST errors = JSON |

## GET / (Conversion)

Parameters: input, prompt, result, stream, token, memo
- input: Auto-detected: starts with "http(s)://" → URL mode (fetched), starts with "data:" → file mode (data URI decoded), otherwise → text mode (processed as-is).
- ⚠️ Browser URL limit: ~2048 chars. Use POST for long text/prompt values.
- prompt: Custom LLM instructions
- result: markdown|prompt|both (default: markdown)
- stream: Enable streaming (boolean, default false) for SSE response
- token: Access token
- memo: Token activation memo

## POST / (JSON body)

Returns JSON with metadata. Accepts JSON body with "input" parameter.
Auto-detection: starts with "http://" → URL, starts with "data:" → file, otherwise → text.

## stream parameter

- true (boolean): Returns SSE stream with real-time chunks
- Token info sent as first message: {"type":"token_info",...}
- Content sent as OpenAI-compatible chunks: {"choices":[{"delta":{"content":"..."},"finish_reason":null}]}
- Final chunk: {"choices":[{"delta":{},"finish_reason":"stop"}]}
- End marker: data: [DONE]
- Native framing per protocol: MCP streams notifications/message chunks, ACP streams session/update notification chunks, A2A streams result.task → artifactUpdate → statusUpdate frames (REST/OpenAI use the choices/delta format above)

## result parameter

- markdown: raw converted Markdown (default)
- prompt: LLM result only (requires prompt)
- both: markdown + prompt_result

## prompt examples

| Use Case           | Example                                                    |
| ------------------ | ---------------------------------------------------------- |
| Summarize          | `?input=https://example.com&prompt=Summarize`              |
| Extract key points | `?input=Hello World&prompt=Extract key points`             |
| Convert to JSON    | `?input=https://example.com&prompt=Convert to JSON format` |
| Analyze content    | `?input=Hello World&prompt=Analyze and explain`            |

## Response (POST)

{
  "success": true,
  "markdown": "# Converted content...",
  "prompt_result": "... (when result=prompt/both)",
  "token_status": "free|valid|invalid|expired|exhausted|expired_pending|activated|verification_error|invalid_payment|error|pending",
  "token_balance": 0.99,
  "token_expires": 1798761600
}

## Error Codes

| HTTP | code (stable)                                | Description                                      |
| ---- | -------------------------------------------- | ------------------------------------------------ |
| 200  | -                                            | Success                                          |
| 400  | invalid_request / invalid_input              | Bad request                                      |
| 401  | unauthorized / invalid_token / token_expired | Invalid token                                    |
| 402  | payment_required                             | Payment required                                 |
| 403  | blocked                                      | Blocked                                          |
| 404  | not_found                                    | Not found                                        |
| 413  | too_large / context_overflow                 | File too large or exceeds the LLM context window |
| 415  | unsupported_format                           | Format not supported                             |
| 429  | rate_limited / resource_limit                | Rate limit exceeded                              |
| 500  | server_error                                 | Internal error                                   |
| 503  | ai_unavailable                               | AI service unavailable                           |
| 504  | timeout                                      | Timeout                                          |

## Endpoints

| Method | Path                                                        | Description                                                                 |
| ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------- |
| GET    | /about                                                      | About service                                                               |
| GET    | /                                                           | API docs or conversion (via query parameter: `input`, `prompt`, `result`)   |
| POST   | /                                                           | Convert content via JSON body (supports `input`, `prompt`, `result` params) |
| POST   | /v1/chat/completions                                        | OpenAI-compatible endpoint                                                  |
| GET    | /mcp                                                        | MCP server manifest                                                         |
| POST   | /mcp                                                        | MCP RPC endpoint (discover, tools, resources, subscriptions)                |
| POST   | /acp                                                        | ACP RPC endpoint (IDE agents)                                               |
| POST   | /a2a                                                        | A2A RPC endpoint (agent2agent)                                              |
| GET    | /health                                                     | Health check                                                                |
| GET    | /llms.txt                                                   | API documentation                                                           |
| GET    | /llms-full.txt                                              | Full API documentation                                                      |
| GET    | /.well-known/ai-discovery.json or /ai-discovery.json        | AI discovery                                                                |
| GET    | /.well-known/agent.json or /agent.json                      | AI Agent discovery                                                          |
| GET    | /.well-known/agent-card.json or /agent-card.json            | A2A Agent card                                                              |
| GET    | /.well-known/acp.json or /acp.json                          | ACP manifest                                                                |
| GET    | /.well-known/x402.json or /x402.json                        | x402 payment manifest                                                       |
| GET    | /.well-known/openapi.json or /openapi.json                  | OpenAPI specification (JSON)                                                |
| GET    | /.well-known/openapi.yaml or /openapi.yaml                  | OpenAPI specification (YAML)                                                |
| GET    | /.well-known/mapi.md or /mapi.md                            | MAPI specification (case-insensitive path MAPI.md support)                  |
| GET    | /.well-known/skill.md or /skill.md                          | Skill specification (case-insensitive path SKILL.md support)                |
| GET    | /.well-known/skills/index.json                              | Legacy skills index                                                         |
| GET    | /.well-known/agent-skills/index.json                        | Agent Skills discovery (v0.2.0)                                             |
| GET    | /.well-known/api-catalog                                    | API catalog (linkset+json, RFC draft)                                       |
| GET    | /.well-known/mcp/server-cards.json                          | MCP server cards index                                                      |
| GET    | /.well-known/plugin/plugin.json or /.well-known/plugin.json | Agent Plugins v1.0.0 manifest                                               |
| GET    | /.well-known/plugin/mcp.json                                | Agent Plugins MCP config                                                    |
| GET    | /.well-known/plugin/skills/mdapi-conversion/SKILL.md        | Agent Plugins conversion skill                                              |

## Links

- **About service:** https://mdapi.io/about
- **API docs:** https://mdapi.io
- **MCP server manifest:** https://mdapi.io/mcp
- **Health check:** https://mdapi.io/health
- **API documentation:** https://mdapi.io/llms.txt
- **Full API documentation:** https://mdapi.io/llms-full.txt
- **AI discovery:** https://mdapi.io/.well-known/ai-discovery.json                                        or https://mdapi.io/ai-discovery.json
- **AI Agent discovery:** https://mdapi.io/.well-known/agent.json                                         or https://mdapi.io/agent.json
- **A2A Agent card:** https://mdapi.io/.well-known/agent-card.json                                        or https://mdapi.io/agent-card.json
- **ACP manifest:** https://mdapi.io/.well-known/acp.json                                                 or https://mdapi.io/acp.json
- **x402 payment manifest:** https://mdapi.io/.well-known/x402.json                                       or https://mdapi.io/x402.json
- **OpenAPI specification (JSON):** https://mdapi.io/.well-known/openapi.json                             or https://mdapi.io/openapi.json
- **OpenAPI specification (YAML):** https://mdapi.io/.well-known/openapi.yaml                             or https://mdapi.io/openapi.yaml
- **MAPI specification (case-insensitive path MAPI.md support):** https://mdapi.io/.well-known/mapi.md    or https://mdapi.io/mapi.md
- **Skill specification (case-insensitive path SKILL.md support):** https://mdapi.io/.well-known/skill.md or https://mdapi.io/skill.md
- **Agent Plugins package (agent-plugins.org v1.0.0):** https://mdapi.io/.well-known/plugin.json - portable manifest (plugin.json) + mcp.json + skills/mdapi-conversion/SKILL.md under https://mdapi.io/.well-known/plugin/

## External Links

- **github.com** https://github.com/mdapiio/mdapi.io
- **skills.sh** https://www.skills.sh/mdapiio/mdapi.io
- **skillsmp.com** https://skillsmp.com/creators/mdapiio/mdapi.io
- **clawhub.ai** https://clawhub.ai/mdapiio
- **x.com** https://x.com/mdapiio

## Disclaimer

**The service is provided "AS IS".**


> mdapi.io is an edge-native service-transport primitive for AI, autonomous-agents, and the Web4 ecosystem.

Zugänglichkeit

Inhalt ohne JavaScript verfügbar (100/100)

Content available without JavaScript

Inhalt erscheint früh im HTML (0/100)

Main content starts at 100% of HTML

Angemessene Seitengröße (100/100)

Page size: 58KB

KI-Auffindbarkeit

robots.txt erlaubt KI-Bots (0/100)

5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot

Markdown for Agents Unterstützung (25/100) Cloudflare CDN
✓ Accept: text/markdown ✗ .md URL ✗ <link> tag ✗ Link header
Hat sitemap.xml (100/100)

Sitemap found

Hat robots.txt-Datei (100/100)

robots.txt exists

Hat llms.txt-Datei (100/100)

llms.txt exists and is valid

Hat Content-Signal (robots.txt oder HTTP-Header) (60/100)
✓ robots.txt ✗ HTTP header ✗ Policy

Strukturierte Daten

Hat Schema.org / JSON-LD (0/100)

No JSON-LD / Schema.org found

Hat Open-Graph-Tags (0/100)

0/3 OG tags present

Hat Meta-Beschreibung (0/100)

No meta description

Hat kanonische URL (0/100)

No canonical URL

Hat lang-Attribut (0/100)

No lang attribute on <html>

Semantisches HTML

Korrekte Überschriftenhierarchie (0/100)

No headings found

Verwendet article- oder main-Element (0/100)

Missing <article> and <main> elements

Verwendet semantische HTML-Elemente (50/100)

No divs or semantic elements found

Aussagekräftige Bild-Alt-Texte (100/100)

No images found

Geringe div-Verschachtelungstiefe (100/100)

No divs found

Inhaltseffizienz

Gutes Token-Reduktionsverhältnis (0/100)

0% token reduction (HTML→Markdown)

Gutes Inhalt-zu-Rausch-Verhältnis (100/100)

Content ratio: 99.5% (59274 content chars / 59583 HTML bytes)

Angemessenes Seitengewicht (100/100)

HTML size: 58KB

Minimale Inline-Styles (100/100)

0/28 elements with inline styles (0.0%)

{
  "url": "https://mdapi.io",
  "timestamp": 1788436399512,
  "fetch": {
    "mode": "simple",
    "timeMs": 851,
    "htmlSizeBytes": 59583,
    "supportsMarkdown": true,
    "markdownAgents": {
      "contentNegotiation": true,
      "mdUrl": {
        "found": false,
        "url": null
      },
      "linkTag": {
        "found": false,
        "url": null
      },
      "linkHeader": {
        "found": false,
        "url": null
      },
      "responseHeaders": {
        "contentSignal": null,
        "xMarkdownTokens": null,
        "vary": null
      },
      "frontmatter": {
        "present": false,
        "fields": [],
        "level": "none"
      },
      "level": "cloudflare",
      "contentNegotiationMediaType": "text/markdown",
      "properMediaType": true
    },
    "statusCode": 200
  },
  "extraction": {
    "title": "",
    "excerpt": "",
    "byline": null,
    "siteName": null,
    "lang": null,
    "contentLength": 59274,
    "metadata": {
      "description": null,
      "ogTitle": null,
      "ogDescription": null,
      "ogImage": null,
      "ogType": null,
      "canonical": null,
      "lang": null,
      "schemas": [],
      "robotsMeta": null,
      "author": null,
      "generator": null,
      "markdownAlternateHref": null
    }
  },
  "markdown": "\\# mdapi.io - Minimal Data API I/O: a content transformation layer primitive for AI systems. Transforms documents, images, and webpages into AI-ready Markdown and structured data, optimized for LLM efficiency and token usage. ## Agent entrypoint - \\*\\*Start AI discovery\\*\\* → https://mdapi.io/.well-known/ai-discovery.json - \\*\\*Use skill\\*\\* → https://mdapi.io/.well-known/skill.md ## Quick Start Choose your entry point based on your role: | Role | Protocol | Endpoint | When to use | | ----------------------------------------------------- | ---------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | IDE / coding agent (JetBrains, Cursor, VS Code, etc.) | ACP (Agent Client Protocol) | POST /acp | You are an IDE plugin or coding agent. Use initialize + session/new + session/prompt (content arrives via session/update notifications). | | AI agent (Claude Code, Codex, OpenClaw, Hermes, etc.) | A2A (Agent-to-Agent) | POST /a2a | You are an autonomous agent. Use SendMessage with data in text parts. Supports streaming and task tracking. | | AI agent (any framework) | MCP (Model Context Protocol) | GET /mcp + POST /mcp | You need tool discovery. Use tools/call with convert tool. | | OpenAI-compatible client | OpenAI API | POST /v1/chat/completions | You already use OpenAI SDK. Pass URL/file in messages. Supports streaming. | | Direct HTTP / curl / script | REST API | GET / or POST / | Simplest path. GET returns Markdown directly. POST returns JSON with metadata. | ### Universal discovery All protocols and capabilities are described in one file: GET /.well-known/ai-discovery.json ## Features - Stateless, in-memory processing - Edge execution with automatic scaling - Prompt-driven transformation - AI-optimized output for LLMs - Pay-per-use via x402 v1/v2 or manual payment ## Supported Formats | Type | Formats | | --------- | ------------------------------ | | Documents | PDF, DOCX, XLSX, XLS, ODT, ODS | | Images | JPEG, JPG, PNG, WebP, SVG | | Text | HTML, XML, JSON, CSV, TXT | | Webpages | Any publicly accessible URL | ## Source Parameters (all protocols) Every protocol (REST, MCP, ACP, A2A, OpenAI) converges on the \\*\\*same conversion core\\*\\*, so content is specified via a single unified \\`input\\` parameter everywhere. | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | \\`input\\` | string | Content to convert: a URL (http(s)://...), a data URI (data:mime/type;base64,...), or plain text. Auto-detected: http(s):// → URL, data: → file, otherwise → text. | The \\`input\\` parameter is auto-detected by the core: URLs (starting with \\`http://\\` or \\`https://\\`) are fetched, data URIs (starting with \\`data:\\`) are decoded as files, and anything else is treated as raw text. Additional parameters (\\`prompt\\`, \\`result\\`, \\`stream\\`, \\`token\\`, \\`memo\\`) are orthogonal and may be combined with \\`input\\`. All five protocols expose the same \\`input\\` source and apply the same transformations, streaming, and prompt-driven processing - the only difference is the transport (REST query/JSON, MCP \\`tools/call\\`, ACP \\`session/prompt\\`, OpenAI \\`messages\\`, A2A \\`message.parts\\`). ## Limits | Limit | Value | | ------------------- | -------------------------------------------------------------------------------- | | \\*\\*Max file size\\*\\* | 50 MB | | \\*\\*Max URL content\\*\\* | 50 MB | | \\*\\*Rate limit\\*\\* | 10,000 requests per hour | | \\*\\*Free tier\\*\\* | 10 requests per day (no token required), within the service’s overall free quota | | \\*\\*Paid tier\\*\\* | min $0.01 per conversion (USDC on Solana) | | \\*\\*Token validity\\*\\* | 1 year | ## Authentication \\*\\*Recommended:\\*\\* Use \\`Authorization: Bearer TOKEN\\` | Method | Use Case | | -------------------- | ---------------------------------- | | Bearer (recommended) | \\`-H \"Authorization: Bearer TOKEN\"\\` | | Header | \\`-H \"X-Token-Required: TOKEN\"\\` | ## Token Activation Before you can use a paid token, you must receive a 402 response first: | Step | Description | | ---- | ------------------------------------------------------- | | 1 | Request without token → Receive 402 with NEW token+memo | | 2 | Send USDC on Solana to wallet with memo from 402 | | 3 | Retry with EXACT token+memo from 402 → Activation | | 4 | After: use token only (no memo needed) | Important: The token+memo issued in the 402 response MUST be used exactly. Using old token or different memo will be rejected. ## API Usage ### GET / (Content conversion) Simple content conversion using query parameters. Returns Markdown directly. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \\`input\\` | string | \\* | Content to convert (URL, text, or data URI - auto-detected) | | \\`prompt\\` | string | | Custom instructions for LLM processing | | \\`result\\` | string | | Response format: \\`markdown\\`, \\`prompt\\`, or \\`both\\` | | \\`stream\\` | boolean | | Enable streaming: true for SSE response | | \\`token\\` | string | | Access token for paid tier | | \\`memo\\` | string | | Memo for token activation | \\*The \\`input\\` parameter is required.\\* > \\*\\*⚠️ Browser URL limit:\\*\\* GET requests with long \\`input\\` or \\`prompt\\` values may exceed browser URL limits (~2048 characters). Use POST with JSON body for large payloads. ### POST / (Content conversion via JSON) Supports content conversion via JSON body. The \\`input\\` parameter accepts URLs, text, or data URIs (auto-detected). Returns a JSON object containing the Markdown content. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \\`input\\` | string | \\* | Content to convert (URL, text, or data URI - auto-detected) | | \\`prompt\\` | string | | Custom instructions for LLM processing | | \\`result\\` | string | | Response format: \\`markdown\\`, \\`prompt\\`, or \\`both\\` | | \\`stream\\` | boolean | | Enable streaming: true for SSE response | | \\`token\\` | string | | Access token for paid tier | | \\`memo\\` | string | | Memo for token activation | \\*The \\`input\\` parameter is required.\\* ### Result Format Parameter The \\`result\\` parameter controls the response format for both GET and POST requests. | Value | Description | | -------------------- | --------------------------------------------------------------- | | \\`markdown\\` (default) | Returns the converted Markdown content | | \\`prompt\\` | Returns the result of LLM processing with \\`prompt\\` instructions | | \\`both\\` | Returns both \\`markdown\\` and \\`prompt\\_result\\` in the response | When \\`result=both\\`: - \\*\\*GET requests\\*\\* return Markdown combining \\`markdown\\`, followed by \"## Prompt Result\" and \\`prompt\\_result\\` (always in Markdown format) - \\*\\*POST requests\\*\\* return JSON with \\`markdown\\` and \\`prompt\\_result\\` fields > \\*\\*Auto \\`result\\`:\\*\\* When \\`prompt\\` is provided without an explicit \\`result\\`, the core automatically sets \\`result=\"prompt\"\\` (LLM output only). Without \\`prompt\\`, default is \\`result=\"markdown\"\\`. Only specify \\`result\\` explicitly when you need both (\\`result=\"both\"\\`). ### Prompt Parameter The \\`prompt\\` parameter lets you specify custom instructions for the LLM to follow when generating the result. | Use Case | Example | | ------------------ | ---------------------------------------------------------- | | Summarize | \\`?input=https://example.com&prompt=Summarize\\` | | Extract key points | \\`?input=Hello World&prompt=Extract key points\\` | | Convert to JSON | \\`?input=https://example.com&prompt=Convert to JSON format\\` | | Analyze content | \\`?input=Hello World&prompt=Analyze and explain\\` | ### Streaming Parameter The \\`stream\\` parameter enables Server-Sent Events (SSE) streaming for real-time response delivery. \\*\\*Type:\\*\\* \\`boolean\\` \\*\\*Default:\\*\\* \\`false\\` (non-streaming) Example: \\`\\`\\`bash curl \"https://mdapi.io/?input=...&stream=true\" \\`\\`\\` Response format (OpenAI-compatible SSE, one JSON object per \\`data:\\` line): \\`\\`\\`json data: {\"type\":\"token\\_info\",\"token\\_status\":\"valid\",\"token\\_balance\":0.99,\"token\\_expires\":1798761600} data: {\"choices\":\\[{\"index\":0,\"delta\":{\"content\":\" chunk\"},\"finish\\_reason\":null}\\]} data: {\"choices\":\\[{\"index\":0,\"delta\":{\"content\":\" more\"},\"finish\\_reason\":null}\\]} data: {\"choices\":\\[{\"index\":0,\"delta\":{},\"finish\\_reason\":\"stop\"}\\]} data: \\[DONE\\] \\`\\`\\` \\*\\*Native streaming per protocol.\\*\\* Every protocol delivers a \\*real\\* content stream when \\`stream: true\\`, but each emits it in its own native frame format (so existing clients keep working): | Protocol | Streaming frame format | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | REST | OpenAI-compatible \\`choices/delta\\` frames | | OpenAI | \\`chat.completion.chunk\\` (\\`choices/delta\\`) | | MCP | \\`notifications/message\\` content chunks, then one final \\`tools/call\\` result frame | | ACP | \\`session/update\\` notification chunks (one stable \\`messageId\\` per turn), then a final response carrying only \\`stopReason\\` | | A2A | \\`result.task\\` (\\`TASK\\_STATE\\_WORKING\\`) start frame, \\`result.artifactUpdate\\` (\\`{artifact, append, lastChunk}\\`) content frames, then \\`result.statusUpdate\\` (\\`TASK\\_STATE\\_COMPLETED\\`) - stream closes | > \\*\\*Note on MCP transport vs. the \\`stream\\` parameter.\\*\\* The MCP manifest advertises \\`transport.type: \"streamable-http\"\\` - that is the MCP \\*transport\\* > (how JSON-RPC requests are delivered to \\`POST /mcp\\`). It is unrelated to the \\`stream\\` \\*parameter\\*, which independently enables SSE streaming of the > conversion \\*\\*content\\*\\*. You can use MCP without streaming; and when you do pass \\`stream: true\\`, the content arrives as SSE frames alongside the transport. ### Response Codes | Code | Description | Response Body (GET) | Response Body (POST) | | ---- | ----------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------ | | 200 | Success | Markdown content | JSON with \\`success\\`, markdown, prompt\\_result, metrics, token fields | | 402 | Payment Required | Markdown payment instructions (\\`X-Error-Code: payment\\_required\\`) | JSON with \\`success:false\\`, \\`code:\"payment\\_required\"\\`, and payment object | | 400 | Bad Request | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"invalid\\_request\"}\\` | | 401 | Invalid Token | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"unauthorized\"}\\` | | 404 | Not Found | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"not\\_found\"}\\` | | 413 | Payload Too Large | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"too\\_large\"}\\` | | 429 | Rate Limited | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"rate\\_limited\"}\\` | | 500 | Server Error | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"server\\_error\"}\\` | ### Token Status The X-Token-Status header (and token\\_status field in responses) indicates the current state of authentication: | Status | Description | | ------------------ | --------------------------------------------------------------------------------------- | | free | Free tier (no token required, 10 requests/day), within the service's overall free quota | | valid | Paid token active with remaining balance | | invalid | Token not found or not provided | | expired | Token validity period has ended | | exhausted | Token balance has been fully used | | expired\\_pending | Activation memo has expired | | activated | Token was just activated with this request | | verification\\_error | Payment verification failed | | invalid\\_payment | Payment transaction is invalid | | error | Internal error during token processing | | pending | Payment required (token not yet activated) | ### Endpoints | Method | Path | Description | | ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------- | | GET | /about | About service | | GET | / | API docs or conversion (via query parameter: \\`input\\`, \\`prompt\\`, \\`result\\`) | | POST | / | Convert content via JSON body (supports \\`input\\`, \\`prompt\\`, \\`result\\` params) | | POST | /v1/chat/completions | OpenAI-compatible endpoint | | GET | /mcp | MCP server manifest | | POST | /mcp | MCP RPC endpoint (discover, tools, resources, subscriptions) | | POST | /acp | ACP RPC endpoint (IDE agents) | | POST | /a2a | A2A RPC endpoint (agent2agent) | | GET | /health | Health check | | GET | /llms.txt | API documentation | | GET | /llms-full.txt | Full API documentation | | GET | /.well-known/ai-discovery.json or /ai-discovery.json | AI discovery | | GET | /.well-known/agent.json or /agent.json | AI Agent discovery | | GET | /.well-known/agent-card.json or /agent-card.json | A2A Agent card | | GET | /.well-known/acp.json or /acp.json | ACP manifest | | GET | /.well-known/x402.json or /x402.json | x402 payment manifest | | GET | /.well-known/openapi.json or /openapi.json | OpenAPI specification (JSON) | | GET | /.well-known/openapi.yaml or /openapi.yaml | OpenAPI specification (YAML) | | GET | /.well-known/mapi.md or /mapi.md | MAPI specification (case-insensitive path MAPI.md support) | | GET | /.well-known/skill.md or /skill.md | Skill specification (case-insensitive path SKILL.md support) | | GET | /.well-known/skills/index.json | Legacy skills index | | GET | /.well-known/agent-skills/index.json | Agent Skills discovery (v0.2.0) | | GET | /.well-known/api-catalog | API catalog (linkset+json, RFC draft) | | GET | /.well-known/mcp/server-cards.json | MCP server cards index | | GET | /.well-known/plugin/plugin.json or /.well-known/plugin.json | Agent Plugins v1.0.0 manifest | | GET | /.well-known/plugin/mcp.json | Agent Plugins MCP config | | GET | /.well-known/plugin/skills/mdapi-conversion/SKILL.md | Agent Plugins conversion skill | #### Examples \\`\\`\\`bash # URL conversion via GET (free) curl \"https://mdapi.io/?input=https://example.com\" # URL with prompt and result=both (returns markdown + prompt\\_result) curl \"https://mdapi.io/?input=https://example.com&prompt=Summarize&result=both\" # Text with prompt (auto result=prompt) curl \"https://mdapi.io/?input=Hello World&prompt=Summarize\" # Token activation via GET (activate and use) curl -H \"Authorization: Bearer YOUR\\_TOKEN\" -H \"X-Memo-Required: YOUR\\_MEMO\" \"https://mdapi.io/?input=https://example.com\" # Paid request with token via GET (using token) curl -H \"Authorization: Bearer YOUR\\_TOKEN\" \"https://mdapi.io/?input=https://example.com\" # URL conversion via POST (free) curl -X POST -H \"Content-Type: application/json\" -d '{\"input\":\"https://example.com\"}' \"https://mdapi.io/\" # Text with prompt via POST curl -X POST -H \"Content-Type: application/json\" -d '{\"input\":\"Hello World\",\"prompt\":\"Summarize\",\"result\":\"both\"}' \"https://mdapi.io/\" # File upload via POST (data URI) curl -X POST -H \"Content-Type: application/json\" -d '{\"input\":\"data:text/plain;base64,SGVsbG8gV29ybGQ=\"}' \"https://mdapi.io/\" # Token activation via POST curl -X POST -H \"Content-Type: application/json\" -H \"Authorization: Bearer YOUR\\_TOKEN\" -H \"X-Memo-Required: YOUR\\_MEMO\" -d '{\"input\":\"https://example.com\"}' \"https://mdapi.io/\" # Paid request with token via POST curl -X POST -H \"Content-Type: application/json\" -H \"Authorization: Bearer YOUR\\_TOKEN\" -d '{\"input\":\"https://example.com\"}' \"https://mdapi.io/\" \\`\\`\\` ### OpenAI Compatible Endpoint The \\`/v1/chat/completions\\` endpoint provides an OpenAI‑compatible API for markdown conversion with streaming support. \\*\\*Supported features:\\*\\* - URL extraction from message content (any text containing https?://) - image\\_url in messages (OpenAI format) - supports HTTP URLs and data URLs - file in messages (OpenAI format) - base64 encoded files (field \\`file.data\\`, optional \\`file.mimeType\\`; built into a \\`data:\\` URI for the core - no \\`file.filename\\` required or used) - Direct text content in messages (any text without a URL is sent to the core as the \\`input\\` source and converted to Markdown) - Token and memo via headers (recommended for POST) - Streaming SSE responses (\\`stream: true\\`) - Custom instructions with LLM processing (system messages, or user messages containing instruction keywords such as \\*extract, summarize, analyze, format, convert to, write as, create, generate, json\\* → LLM-driven summary/extraction/transformation) - \\`prompt\\` for LLM-processed output. The response surfaces \\`prompt\\_result\\` at the top level alongside the standard \\`choices\\[\\].message.content\\` (which carries \\`prompt\\_result\\` when prompt is set, otherwise the Markdown). \\`model\\` is accepted but not required (any string; the service uses its own conversion pipeline, not a remote LLM chat model, unless custom instructions trigger LLM processing). > \\*\\*Content via message text:\\*\\* the message text is passed to the core as the \\`input\\` parameter - the same unified source as every other protocol. URLs are auto-detected, data URIs are decoded as files, and plain text is processed directly. > See \\[Source Parameters (all protocols)\\](#source-parameters-all-protocols). #### Request Schema \\`\\`\\`json { \"type\": \"object\", \"properties\": { \"model\": { \"type\": \"string\", \"description\": \"Optional model identifier (any string accepted; not required)\" }, \"messages\": { \"type\": \"array\", \"items\": { \"type\": \"object\", \"properties\": { \"role\": {\"type\": \"string\", \"enum\": \\[\"user\", \"system\", \"assistant\"\\]}, \"content\": {\"oneOf\": \\[{\"type\": \"string\"}, {\"type\": \"array\"}\\]} } }, \"description\": \"Chat messages. URL in content, image\\_url or file in content for input\" }, \"stream\": { \"type\": \"boolean\", \"default\": false, \"description\": \"Enable streaming SSE responses\" }, \"prompt\": { \"type\": \"string\", \"description\": \"Custom instructions for LLM processing (alternative to instruction keywords in messages)\" }, \"result\": { \"type\": \"string\", \"enum\": \\[\"markdown\", \"prompt\", \"both\"\\], \"description\": \"Response format when using prompt\" }, \"token\": {\"type\": \"string\", \"description\": \"Access token for paid tier\"}, \"memo\": {\"type\": \"string\", \"description\": \"Memo for token activation\"}, \"input\": { \"type\": \"string\", \"description\": \"Content to convert (URL, text, or data URI - auto-detected). Alternative to a URL/file embedded in messages\" } }, \"required\": \\[\"messages\"\\] } \\`\\`\\` > \\*\\*Note on streaming + custom instructions:\\*\\* when custom instructions trigger LLM processing, the response is returned as a single completion (streaming is not applied to the LLM pass). Streaming SSE applies to the standard conversion path. ## MCP Configuration Connect mdapi.io to your MCP-compatible client (spec 2026-07-28, stateless). > \\*\\*Single source via \\`input\\`:\\*\\* the \\`convert\\` tool accepts a unified \\`input\\` parameter - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \\[Source Parameters (all protocols)\\](#source-parameters-all-protocols). ### Protocol Requirements - \\*\\*Transport:\\*\\* Streamable HTTP (POST-only for JSON-RPC, GET for manifest) - \\*\\*Required headers:\\*\\* \\`MCP-Protocol-Version: 2026-07-28\\` and \\`Mcp-Method\\` on every request; \\`Mcp-Name\\` additionally on \\`tools/call\\`, \\`resources/read\\`, and \\`prompts/get\\` - \\*\\*Stateless:\\*\\* No sessions - every request is independent - \\*\\*Discovery:\\*\\* Use \\`server/discover\\` to query server capabilities and supported versions ### Basic Configuration Add to your MCP config file: \\`\\`\\`json { \"mcpServers\": { \"mdapi\": { \"url\": \"https://mdapi.io/mcp\" } } } \\`\\`\\` > \\*\\*Note:\\*\\* No token is required to connect. A free tier is available (10 requests per day), within the service’s overall free quota. ### OpenClaw Integration OpenClaw can use mdapi.io in two ways: \\*\\*Option 1: Via MCP (Recommended)\\*\\* \\`\\`\\`json { \"mcpServers\": { \"mdapi\": { \"url\": \"https://mdapi.io/mcp\" } } } \\`\\`\\` \\*\\*Option 2: Via OpenAI-compatible endpoint\\*\\* \\`\\`\\`bash openclaw config set llm.apiBase https://mdapi.io/v1 openclaw config set llm.apiKey YOUR\\_TOKEN \\`\\`\\` ### Using MCP with a token MCP does not use HTTP-level Authorization headers. The token is always passed inside the tool \\`arguments\\` object. \\*\\*Activation\\*\\* - include \\`token\\` + \\`memo\\` in the first request: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"token\": \"YOUR\\_TOKEN\", \"memo\": \"YOUR\\_PAYMENT\\_MEMO\" } } } \\`\\`\\` \\*\\*After activation\\*\\* - use \\`token\\` only (no memo needed): \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"token\": \"YOUR\\_ACTIVATED\\_TOKEN\" } } } \\`\\`\\` ### MCP Tool Examples Convert with prompt and result: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"prompt\": \"Summarize\", \"result\": \"both\", \"token\": \"YOUR\\_TOKEN\" } } } \\`\\`\\` Process text directly: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"Hello World\", \"prompt\": \"Extract key points\", \"result\": \"prompt\" } } } \\`\\`\\` Stream with SSE (native MCP frames): \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"stream\": true } } } \\`\\`\\` Response (SSE over \\`streamable-http\\`): intermediate \\`notifications/message\\` content chunks, then one final \\`tools/call\\` result frame with the full Markdown: \\`\\`\\` data: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/message\",\"params\":{\"level\":\"info\",\"data\":\" partial \"}} data: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/message\",\"params\":{\"level\":\"info\",\"data\":\" more \"}} data: {\"jsonrpc\":\"2.0\",\"id\":3,\"result\":{\"content\":\\[{\"type\":\"text\",\"text\":\"\"}\\],\"isError\":false}} data: \\[DONE\\] \\`\\`\\` ### Using Environment Variables Or use environment variable: \\`\\`\\`bash export MDAPI\\_TOKEN=YOUR\\_ACTIVATED\\_TOKEN \\`\\`\\` ## Code Examples ### JavaScript (fetch) \\`\\`\\`javascript // Convert a URL via GET - returns Markdown directly const response = await fetch('https://mdapi.io/?input=https://example.com'); const markdown = await response.text(); console.log(markdown); \\`\\`\\` \\`\\`\\`javascript // Convert a URL via POST - returns JSON with metadata const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); console.log(data.markdown); \\`\\`\\` \\`\\`\\`javascript // Text with prompt - returns prompt\\_result const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'Hello World', prompt: 'Summarize', result: 'both' }) }); const data = await response.json(); console.log(data.markdown); console.log(data.prompt\\_result); \\`\\`\\` \\`\\`\\`javascript // File upload via data URI const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'data:text/plain;base64,SGVsbG8gV29ybGQ=' }) }); const data = await response.json(); console.log(data.markdown); \\`\\`\\` \\`\\`\\`javascript // Token activation - first request with token + memo const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\\_TOKEN', 'X-Memo-Required': 'YOUR\\_MEMO' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); // After activation, use token only (no memo needed) \\`\\`\\` \\`\\`\\`javascript // Streaming via OpenAI-compatible endpoint const response = await fetch('https://mdapi.io/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\\_TOKEN' }, body: JSON.stringify({ model: 'markdown-v1', messages: \\[{ role: 'user', content: 'Convert https://example.com' }\\], stream: true }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(decoder.decode(value)); } \\`\\`\\` ### Python \\`\\`\\`python import requests # Convert a URL via GET - returns Markdown directly response = requests.get('https://mdapi.io/?input=https://example.com') response.raise\\_for\\_status() print(response.text) \\`\\`\\` \\`\\`\\`python import requests # Convert a URL via POST - returns JSON with metadata response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'} ) response.raise\\_for\\_status() data = response.json() print(data\\['markdown'\\]) \\`\\`\\` \\`\\`\\`python import requests # Text with prompt - returns prompt\\_result response = requests.post( 'https://mdapi.io/', json={'input': 'Hello World', 'prompt': 'Summarize', 'result': 'both'} ) response.raise\\_for\\_status() data = response.json() print(data\\['markdown'\\]) print(data\\['prompt\\_result'\\]) \\`\\`\\` \\`\\`\\`python import requests # File upload via data URI with open('document.pdf', 'rb') as f: import base64 file\\_data = base64.b64encode(f.read()).decode() response = requests.post( 'https://mdapi.io/', json={'input': f'data:text/plain;base64,{file\\_data}'} ) response.raise\\_for\\_status() data = response.json() print(data\\['markdown'\\]) \\`\\`\\` \\`\\`\\`python import requests # Token activation response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'}, headers={ 'Authorization': 'Bearer YOUR\\_TOKEN', 'X-Memo-Required': 'YOUR\\_MEMO' } ) response.raise\\_for\\_status() # After activation, use token only (no memo needed) \\`\\`\\` \\`\\`\\`python from openai import OpenAI # OpenAI-compatible streaming client = OpenAI(base\\_url='https://mdapi.io/v1', api\\_key='YOUR\\_TOKEN') stream = client.chat.completions.create( model='markdown-v1', messages=\\[{'role': 'user', 'content': 'Convert https://example.com'}\\], stream=True ) for chunk in stream: if chunk.choices\\[0\\].delta.content: print(chunk.choices\\[0\\].delta.content, end='') \\`\\`\\` ### Go \\`\\`\\`go package main import ( \"fmt\" \"io\" \"net/http\" ) func main() { // Convert a URL via GET - returns Markdown directly resp, err := http.Get(\"https://mdapi.io/?input=https://example.com\") if err != nil { fmt.Println(\"HTTP error:\", err) return } defer resp.Body.Close() body, \\_ := io.ReadAll(resp.Body) fmt.Println(string(body)) } \\`\\`\\` \\`\\`\\`go package main import ( \"bytes\" \"encoding/json\" \"fmt\" \"net/http\" ) func main() { // Convert a URL via POST - returns JSON with metadata payload, \\_ := json.Marshal(map\\[string\\]string{ \"input\": \"https://example.com\", }) resp, err := http.Post( \"https://mdapi.io/\", \"application/json\", bytes.NewReader(payload), ) if err != nil { fmt.Println(\"HTTP error:\", err) return } defer resp.Body.Close() var result map\\[string\\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\\[\"markdown\"\\]) } \\`\\`\\` \\`\\`\\`go package main import ( \"bytes\" \"encoding/base64\" \"encoding/json\" \"fmt\" \"net/http\" \"os\" ) func main() { // File upload via data URI fileBytes, \\_ := os.ReadFile(\"document.pdf\") b64 := base64.StdEncoding.EncodeToString(fileBytes) payload, \\_ := json.Marshal(map\\[string\\]string{ \"input\": \"data:text/plain;base64,\" + b64, }) resp, err := http.Post( \"https://mdapi.io/\", \"application/json\", bytes.NewReader(payload), ) if err != nil { fmt.Println(\"HTTP error:\", err) return } defer resp.Body.Close() var result map\\[string\\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\\[\"markdown\"\\]) } \\`\\`\\` ### Rust \\`\\`\\`rust use anyhow::Result; use reqwest::Client; #\\[tokio::main\\] async fn main() -> Result<()> { let client = Client::new(); let url = \"https://mdapi.io/?input=https://example.com\"; let response = client.get(url).send().await?; response.error\\_for\\_status\\_ref()?; let markdown = response.text().await?; println!(\"{}\", markdown); Ok(()) } \\`\\`\\` \\`\\`\\`rust use anyhow::{Result, Context}; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #\\[derive(Deserialize)\\] struct ApiResponse { markdown: Option, prompt\\_result: Option, } #\\[derive(Serialize)\\] struct ConvertRequest { input: String, #\\[serde(skip\\_serializing\\_if = \"Option::is\\_none\")\\] prompt: Option, #\\[serde(skip\\_serializing\\_if = \"Option::is\\_none\")\\] result: Option, } pub struct MdApiClient { client: Client, base\\_url: String, token: Option, } impl MdApiClient { pub fn new(token: Option) -> Self { Self { client: Client::new(), base\\_url: \"https://mdapi.io\".to\\_string(), token, } } pub async fn convert\\_url( &self, url: &str, prompt: Option<&str>, ) -> Result { let body = ConvertRequest { input: url.to\\_string(), prompt: prompt.map(|p| p.to\\_string()), result: prompt.map(|\\_| \"both\".to\\_string()), }; let mut request = self.client .post(&self.base\\_url) .header(\"Content-Type\", \"application/json\") .json(&body); if let Some(ref token) = self.token { request = request.header(\"Authorization\", format!(\"Bearer {}\", token)); } let response = request .send() .await .context(\"Failed to send HTTP request\")?; response .error\\_for\\_status\\_ref() .context(\"API returned error status\")?; response .text() .await .context(\"Failed to read response body\") } } // Example: convert a URL with prompt #\\[tokio::main\\] async fn main() -> Result<()> { let token = std::env::var(\"MDAPI\\_TOKEN\").ok(); let client = MdApiClient::new(token); let markdown = client .convert\\_url(\"https://example.com\", Some(\"Summarize\")) .await?; println!(\"{}\", markdown); Ok(()) } \\`\\`\\` \\`\\`\\`rust use anyhow::Result; use reqwest::Client; use base64::engine::general\\_purpose::STANDARD; use base64::Engine; #\\[tokio::main\\] async fn main() -> Result<()> { let client = Client::new(); let file\\_bytes = std::fs::read(\"document.pdf\")?; let b64 = STANDARD.encode(&file\\_bytes); let body = serde\\_json::json!({ \"input\": format!(\"data:text/plain;base64,{}\", b64) }); let response = client .post(\"https://mdapi.io/\") .header(\"Content-Type\", \"application/json\") .json(&body) .send() .await?; let data: serde\\_json::Value = response.json().await?; println!(\"{}\", data\\[\"markdown\"\\]); Ok(()) } \\`\\`\\` \\`\\`\\`rust use anyhow::Result; use futures\\_util::stream::StreamExt; use reqwest::Client; // Streaming response when using the OpenAI‑compatible endpoint with stream = true #\\[tokio::main\\] async fn main() -> Result<()> { let client = Client::new(); let token = std::env::var(\"MDAPI\\_TOKEN\")?; let body = serde\\_json::json!({ \"model\": \"markdown-v1\", \"messages\": \\[ { \"role\": \"user\", \"content\": \"Convert https://example.com\" } \\], \"stream\": true }); let response = client .post(\"https://mdapi.io/v1/chat/completions\") .header(\"Authorization\", format!(\"Bearer {}\", token)) .json(&body) .send() .await?; response.error\\_for\\_status\\_ref()?; let mut stream = response.bytes\\_stream(); while let Some(chunk) = stream.next().await { let chunk = chunk?; let text = String::from\\_utf8\\_lossy(&chunk); eprint!(\"{}\", text); } Ok(()) } \\`\\`\\` ### OpenAI SDK \\`\\`\\`python from openai import OpenAI client = OpenAI( base\\_url=\"https://mdapi.io/v1\", api\\_key=\"YOUR\\_TOKEN\" ) try: response = client.chat.completions.create( model=\"markdown-v1\", messages=\\[{\"role\": \"user\", \"content\": \"Convert https://example.com\"}\\] ) print(response.choices\\[0\\].message.content) except Exception as e: print(\"API error:\", e) \\`\\`\\` #### OpenAI with paid token \\`\\`\\`bash curl -X POST \"https://mdapi.io/v1/chat/completions\" -H \"Authorization: Bearer YOUR\\_TOKEN\" -H \"X-Memo-Required: YOUR\\_MEMO\" -H \"Content-Type: application/json\" -d '{\"model\":\"markdown-v1\",\"messages\":\\[{\"role\":\"user\",\"content\":\"Convert https://example.com\"}\\]}' \\`\\`\\` After activation, use token only (no memo needed): \\`\\`\\`bash curl -X POST \"https://mdapi.io/v1/chat/completions\" -H \"Authorization: Bearer YOUR\\_ACTIVATED\\_TOKEN\" -H \"Content-Type: application/json\" -d '{\"model\":\"markdown-v1\",\"messages\":\\[{\"role\":\"user\",\"content\":\"Convert https://example.com\"}\\]}' \\`\\`\\` ## A2A Configuration Connect mdapi.io to your A2A-compatible agent (Claude Code, Codex, OpenClaw, Hermes, etc.). ### Basic Configuration Add to your A2A client configuration: \\`\\`\\`json { \"agent\": { \"name\": \"mdapi\", \"agentCard\": { \"url\": \"https://mdapi.io/.well-known/agent-card.json\" } } } \\`\\`\\` Or use JSON-RPC directly: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-1\", \"parts\": \\[ { \"text\": \"Convert https://example.com\" } \\] } } } \\`\\`\\` ### A2A Methods | Method | Description | | -------------------- | --------------------------------------- | | SendMessage | Send a message to initiate conversion | | SendStreamingMessage | Send message with SSE streaming updates | | GetTask | Get task status and results by ID | | ListTasks | List tasks with optional filtering | | CancelTask | Cancel an in-progress task | | SubscribeToTask | Subscribe to task updates via SSE | > \\*\\*Single source via \\`input\\`:\\*\\* the \\`input\\` parameter in the message parts is the unified source - the same as the REST endpoint. A bare URL > inside a text part (e.g. \\`\"Convert https://example.com\"\\`) is extracted automatically and used as the conversion source, so you don't need to wrap it > in structured JSON. Instructions such as \\`Summarize\\` should be passed via the structured \\`{ \"input\": \"...\", \"prompt\": \"...\" }\\` form, not mixed into the text. ### A2A Examples #### SendMessage \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-1\", \"parts\": \\[ { \"text\": \"Convert https://example.com\" } \\] } } }' \\`\\`\\` #### SendMessage with file (data URI) \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-2\", \"parts\": \\[ { \"text\": \"{\"input\":\"data:text/plain;base64,SGVsbG8gV29ybGQ=\"}\" } \\] } } }' \\`\\`\\` #### SendMessage with structured data \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-3\", \"parts\": \\[ { \"data\": { \"input\": \"https://example.com\", \"result\": \"markdown\" }, \"mediaType\": \"application/json\" } \\] } } }' \\`\\`\\` #### Token activation via A2A \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 7, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-7\", \"parts\": \\[ { \"data\": { \"input\": \"https://example.com\", \"token\": \"YOUR\\_TOKEN\", \"memo\": \"YOUR\\_PAYMENT\\_MEMO\" }, \"mediaType\": \"application/json\" } \\] } } }' \\`\\`\\` > \\*\\*Note on token activation:\\*\\* Pass \\`token\\` and \\`memo\\` inside a \\`data\\` Part or as JSON inside a \\`text\\` Part. A2A does not use HTTP-level Authorization headers. #### Multi-turn conversation (follow-up) \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 4, \"method\": \"SendMessage\", \"params\": { \"contextId\": \"ctx-uuid-1\", \"message\": { \"messageId\": \"msg-uuid-4\", \"parts\": \\[ { \"text\": \"Now convert the tables to JSON\" } \\] } } }' \\`\\`\\` #### GetTask \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 5, \"method\": \"GetTask\", \"params\": { \"id\": \"task\\_12345\" } }' \\`\\`\\` #### ListTasks \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 6, \"method\": \"ListTasks\", \"params\": { \"contextId\": \"ctx\\_12345\", \"pageSize\": 10 } }' \\`\\`\\` ### A2A Message Parts Messages use the A2A \\`Part\\` format (field-name discriminators per spec v1.0.0): | Type | Description | Fields | | ------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------- | | \\`text\\` | Plain text content or JSON-encoded params | \\`text\\` | | \\`raw\\` | File content as base64 bytes; normalized to a data URI (\\`data:;base64,\\`) for the core | \\`raw\\` (base64), \\`mediaType\\` (optional) | | \\`data\\` | Structured JSON data (core params) | \\`data\\` (object), \\`mediaType\\` (optional) | | \\`url\\` | URL to fetch and convert | \\`url\\` (http/https) | \\*\\*Part → Core Parameter Mapping:\\*\\* - \\`text\\` Part → \\`input\\` param (direct content) or JSON-encoded params (\\`{ \"input\": \"...\", \"prompt\": \"...\" }\\`) - \\`data\\` Part → merged as params (\\`input\\`, \\`prompt\\`, \\`result\\`, \\`token\\`, \\`memo\\`, etc.) - \\`url\\` Part → \\`input\\` param (fetched and converted) ### Message Object \\`\\`\\`typescript interface Message { messageId: string; // REQUIRED: unique ID (e.g. \"msg-uuid\") contextId?: string; // Optional: group related tasks taskId?: string; // Optional: associate with existing task role: \"user\" | \"agent\"; // REQUIRED parts: Array; // REQUIRED: at least one part } \\`\\`\\` ### Task Data Model \\`\\`\\`typescript interface Task { id: string; // \"task\\_\\_\" contextId: string; // \"ctx\\_\\_\" status: { state: string; // \"TASK\\_STATE\\_WORKING\" | \"TASK\\_STATE\\_COMPLETED\" | \"TASK\\_STATE\\_FAILED\" | \"TASK\\_STATE\\_CANCELED\" | \"TASK\\_STATE\\_REJECTED\" timestamp: string; // ISO 8601 message?: Message; // only on failure }; artifacts?: Array<{ artifactId: string; name: string; parts: Array; }>; history?: Array; } \\`\\`\\` ### Task States | State | Description | | ---------------------- | ----------------------------- | | \\`TASK\\_STATE\\_WORKING\\` | Task is being processed | | \\`TASK\\_STATE\\_COMPLETED\\` | Task finished successfully | | \\`TASK\\_STATE\\_FAILED\\` | Task failed during processing | | \\`TASK\\_STATE\\_CANCELED\\` | Task was canceled by client | | \\`TASK\\_STATE\\_REJECTED\\` | Task was rejected by server | ### Error Responses A2A uses JSON-RPC 2.0 error format with A2A-specific error codes: | Code | Error | Description | | -------- | --------------------------------- | ---------------------------------------------------------- | | \\`-32700\\` | Parse error | Invalid JSON payload | | \\`-32600\\` | Invalid Request | Missing required fields (message.parts, message.messageId) | | \\`-32601\\` | Method not found | Unknown A2A method | | \\`-32001\\` | Task not found | Task ID does not exist | | \\`-32002\\` | Task is not in a cancelable state | CancelTask on a terminal task | Unsupported media type is returned as HTTP \\*\\*415\\*\\* (not a JSON-RPC error code). \\*\\*Example error response:\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"error\": { \"code\": -32600, \"message\": \"Invalid Request\", \"data\": \\[ { \"@type\": \"type.googleapis.com/google.rpc.BadRequest\", \"fieldViolations\": \\[ { \"field\": \"message.messageId\", \"description\": \"Message messageId is required\" } \\] } \\] } } \\`\\`\\` ### Streaming Use \\`SendStreamingMessage\\` for real-time SSE updates: \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -H \"Accept: text/event-stream\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 5, \"method\": \"SendStreamingMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-5\", \"parts\": \\[ { \"text\": \"Convert https://example.com\" } \\] } } }' \\`\\`\\` Response format (A2A v1.0.0 streaming sequence - exact frames the service emits): \\`\\`\\` data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"task\":{\"id\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"status\":{\"state\":\"TASK\\_STATE\\_WORKING\",\"timestamp\":\"...\"},\"artifacts\":\\[\\]}}} data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"artifactUpdate\":{\"taskId\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"artifact\":{\"artifactId\":\"artifact\\_...\",\"name\":\"conversion\\_result\",\"parts\":\\[{\"text\":\" partial \"}\\]},\"append\":true,\"lastChunk\":false}}} data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"artifactUpdate\":{\"taskId\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"artifact\":{\"artifactId\":\"artifact\\_...\",\"name\":\"conversion\\_result\",\"parts\":\\[{\"text\":\" more \"}\\]},\"append\":true,\"lastChunk\":true}}} data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"statusUpdate\":{\"taskId\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"status\":{\"state\":\"TASK\\_STATE\\_COMPLETED\",\"timestamp\":\"...\"}}}} data: \\[DONE\\] \\`\\`\\` The first frame carries the full \\`task\\` in TASK\\_STATE\\_WORKING; content streams as \\`artifactUpdate\\` frames (\\`lastChunk: true\\` on the final chunk); \\`statusUpdate\\` closes the stream with the terminal TASK\\_STATE\\_COMPLETED state. The persisted task (via \\`GetTask\\`) carries the real \\`artifacts\\[\\].parts\\[\\]\\` content. ### Subscribe to Task Subscribe to an existing task for real-time updates: \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -H \"Accept: text/event-stream\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 6, \"method\": \"SubscribeToTask\", \"params\": { \"id\": \"task\\_12345\" } }' \\`\\`\\` ### Cancel Task Cancel an in-progress task: \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 7, \"method\": \"CancelTask\", \"params\": { \"id\": \"task\\_12345\" } }' \\`\\`\\` Response: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 7, \"result\": { \"id\": \"task\\_12345\", \"contextId\": \"ctx\\_...\", \"status\": { \"state\": \"TASK\\_STATE\\_CANCELED\", \"timestamp\": \"2026-06-26T16:00:00.000Z\" } } } \\`\\`\\` ## ACP Configuration Connect mdapi.io to your IDE or coding agent (JetBrains, Cursor, VS Code, etc.) via the Agent Client Protocol v1.0.0. ACP is a JSON-RPC 2.0 endpoint at \\`POST /acp\\`. Its native surface is \\*\\*session/turn\\*\\*: create an ephemeral session, then send a prompt - the converted content streams back as \\`session/update\\` notifications. > \\*\\*Single source via \\`input\\`:\\*\\* each \\`session/prompt\\` carries a unified \\`input\\` - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \\[Source Parameters (all protocols)\\](#source-parameters-all-protocols). ### Basic Configuration Add to your ACP client configuration (IDE plugin / agent settings): \\`\\`\\`json { \"acpServers\": { \"mdapi\": { \"url\": \"https://mdapi.io/acp\" } } } \\`\\`\\` Or call the JSON-RPC endpoint directly with \\`POST /acp\\` (Content-Type: \\`application/json\\`). Sessions are \\*\\*stateless and ephemeral\\*\\* - nothing is persisted server-side, so each \\`session/prompt\\` runs as an independent conversion: \\`\\`\\`bash # 1. Create a session curl -X POST https://mdapi.io/acp \\\\ -H \"Content-Type: application/json\" \\\\ -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"session/new\",\"params\":{}}' # 2. Send a prompt (content streams back as session/update notifications) curl -X POST https://mdapi.io/acp \\\\ -H \"Content-Type: application/json\" \\\\ -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"session/prompt\",\"params\":{ \"sessionId\":\"\", \"prompt\":\\[{\"type\":\"text\",\"text\":\"\"}, {\"type\":\"resource\\_link\",\"uri\":\"https://example.com\"}\\] }}' \\`\\`\\` > \\*\\*Note:\\*\\* \\`GET /acp\\` is not supported (ACP is POST-only). A free tier is available without a token. ### ACP Methods | Method | Description | | ---------------- | ------------------------------------------------------------------------------- | | \\`initialize\\` | Handshake: return protocol version, agent capabilities, and agent info | | \\`session/new\\` | Create an ephemeral, stateless session (returns a \\`sessionId\\`) | | \\`session/prompt\\` | Run a conversion turn; content streams back as \\`session/update\\` notifications | | \\`session/cancel\\` | Notification (204, no response body) that best-effort cancels an in-flight turn | ### ACP Session Examples \\*\\*URL conversion (instructions + \\`resource\\_link\\`):\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"session/prompt\", \"params\": { \"sessionId\": \"\", \"prompt\": \\[ { \"type\": \"text\", \"text\": \"Summarize\" }, { \"type\": \"resource\\_link\", \"uri\": \"https://example.com\" } \\] } } \\`\\`\\` \\*\\*Text conversion (a bare \\`text\\` block becomes the input):\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"session/prompt\", \"params\": { \"sessionId\": \"\", \"prompt\": \\[{ \"type\": \"text\", \"text\": \"Hello World\" }\\] } } \\`\\`\\` \\*\\*File conversion (a \\`data:\\` URI in a \\`text\\` block is auto-detected as a file):\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"session/prompt\", \"params\": { \"sessionId\": \"\", \"prompt\": \\[ { \"type\": \"text\", \"text\": \"Extract the title\" }, { \"type\": \"text\", \"text\": \"data:text/plain;base64,SGVsbG8gV29ybGQ=\" } \\] } } \\`\\`\\` \\*\\*Token activation (first request only):\\*\\* every protocol passes the token per-call. Include it on any method's \\`params\\` as a \\`token\\` (+ \\`memo\\` on first use) - ACP does not use a session-level authenticate exchange. ### ACP Response Format Content is delivered \\*\\*exclusively via \\`session/update\\` notifications\\*\\*; the final \\`session/prompt\\` result carries only the \\`stopReason\\` (\\`PromptResponse\\`). A successful turn looks like this SSE stream: \\`\\`\\` data: {\"jsonrpc\":\"2.0\",\"method\":\"session/update\",\"params\":{\"sessionId\":\"\",\"update\":\\[{\"content\":{\"type\":\"text\",\"text\":\"\"},\"messageId\":\"\"}\\]}} data: {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"stopReason\":\"end\\_turn\"}} data: \\[DONE\\] \\`\\`\\` - The \\`session/update\\` \\`update\\[\\]\\` array holds the content chunks; \\`messageId\\` is stable across all chunks of a single turn. - \\*\\*Streaming:\\*\\* add \\`\"stream\": true\\` to the \\`session/prompt\\` \\`params\\` to stream the conversion live - each content chunk arrives as its own \\`session/update\\` notification (same \\`messageId\\`), then the \\`PromptResponse\\`. Without it, the whole turn arrives as one buffered \\`session/update\\` notification. Streaming is inherent to ACP; content only ever rides notifications. - A failed conversion streams an explanatory \\`session/update\\`, then ends with \\`stopReason: \"error\"\\`. \\`session/cancel\\` is a client→agent notification - the agent acknowledges it with HTTP 204 and no body. - Token status (\\`X-Token-Balance\\`, \\`X-Token-Expires\\`, \\`X-Token-Status\\`) is proxied from the core into the response headers. ## Usage scenarios mdapi.io is a minimal, self-documenting service-transport primitive: REST, MCP, ACP, A2A, and OpenAI-compatible endpoints all call the same transformation core, so agents can combine protocols and pass already-processed knowledge between each other. - \\*\\*Agent swarms\\*\\* - each request is handled by a stateless, automatically-scaled execution environment, so the service scales horizontally. An orchestrator fans work out across a swarm of agents, and the swarm processes very large batches of distinct resources in parallel - millions of resources in a matter of minutes, the ceiling set by how widely the work is distributed rather than by the service. Different users may freely access the same resource. - \\*\\*Shared vs individual payment\\*\\* - an orchestrator can pay once for a shared token (batching on-chain activity), or each agent can activate its own token for the exact volume it received. - \\*\\*Human-in-the-loop\\*\\* - if an agent has no wallet or insufficient funds, it returns payment details + a QR code; the human pays from a mobile device and the agent resumes. - \\*\\*Role switching\\*\\* - an agent's role can change mid-task; one agent fetches/normalizes, another summarizes/extracts, relaying compact results via the text or prompt parameters. - \\*\\*Cross-protocol interoperability\\*\\* - knowledge extracted on one protocol is reusable on another. - \\*\\*Bulk processing / model training\\*\\* - the swarm pattern turns mdapi.io into a high-throughput edge pipeline for large corpora. See https://mdapi.io/about for the full scenario walkthrough. ## Links - \\*\\*About service:\\*\\* https://mdapi.io/about - \\*\\*API docs:\\*\\* https://mdapi.io - \\*\\*MCP server manifest:\\*\\* https://mdapi.io/mcp - \\*\\*Health check:\\*\\* https://mdapi.io/health - \\*\\*API documentation:\\*\\* https://mdapi.io/llms.txt - \\*\\*Full API documentation:\\*\\* https://mdapi.io/llms-full.txt - \\*\\*AI discovery:\\*\\* https://mdapi.io/.well-known/ai-discovery.json or https://mdapi.io/ai-discovery.json - \\*\\*AI Agent discovery:\\*\\* https://mdapi.io/.well-known/agent.json or https://mdapi.io/agent.json - \\*\\*A2A Agent card:\\*\\* https://mdapi.io/.well-known/agent-card.json or https://mdapi.io/agent-card.json - \\*\\*ACP manifest:\\*\\* https://mdapi.io/.well-known/acp.json or https://mdapi.io/acp.json - \\*\\*x402 payment manifest:\\*\\* https://mdapi.io/.well-known/x402.json or https://mdapi.io/x402.json - \\*\\*OpenAPI specification (JSON):\\*\\* https://mdapi.io/.well-known/openapi.json or https://mdapi.io/openapi.json - \\*\\*OpenAPI specification (YAML):\\*\\* https://mdapi.io/.well-known/openapi.yaml or https://mdapi.io/openapi.yaml - \\*\\*MAPI specification (case-insensitive path MAPI.md support):\\*\\* https://mdapi.io/.well-known/mapi.md or https://mdapi.io/mapi.md - \\*\\*Skill specification (case-insensitive path SKILL.md support):\\*\\* https://mdapi.io/.well-known/skill.md or https://mdapi.io/skill.md - \\*\\*Agent Plugins package (agent-plugins.org v1.0.0):\\*\\* https://mdapi.io/.well-known/plugin.json - portable manifest (plugin.json) + mcp.json + skills/mdapi-conversion/SKILL.md under https://mdapi.io/.well-known/plugin/ ## External Links - \\*\\*github.com\\*\\* https://github.com/mdapiio/mdapi.io - \\*\\*skills.sh\\*\\* https://www.skills.sh/mdapiio/mdapi.io - \\*\\*skillsmp.com\\*\\* https://skillsmp.com/creators/mdapiio/mdapi.io - \\*\\*clawhub.ai\\*\\* https://clawhub.ai/mdapiio - \\*\\*x.com\\*\\* https://x.com/mdapiio ## Disclaimer \\*\\*The service is provided \"AS IS\".\\*\\* > mdapi.io is an edge-native service-transport primitive for AI, autonomous-agents, and the Web4 ecosystem.\n",
  "fullPageMarkdown": "\\# mdapi.io - Minimal Data API I/O: a content transformation layer primitive for AI systems. Transforms documents, images, and webpages into AI-ready Markdown and structured data, optimized for LLM efficiency and token usage. ## Agent entrypoint - \\*\\*Start AI discovery\\*\\* → https://mdapi.io/.well-known/ai-discovery.json - \\*\\*Use skill\\*\\* → https://mdapi.io/.well-known/skill.md ## Quick Start Choose your entry point based on your role: | Role | Protocol | Endpoint | When to use | | ----------------------------------------------------- | ---------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | IDE / coding agent (JetBrains, Cursor, VS Code, etc.) | ACP (Agent Client Protocol) | POST /acp | You are an IDE plugin or coding agent. Use initialize + session/new + session/prompt (content arrives via session/update notifications). | | AI agent (Claude Code, Codex, OpenClaw, Hermes, etc.) | A2A (Agent-to-Agent) | POST /a2a | You are an autonomous agent. Use SendMessage with data in text parts. Supports streaming and task tracking. | | AI agent (any framework) | MCP (Model Context Protocol) | GET /mcp + POST /mcp | You need tool discovery. Use tools/call with convert tool. | | OpenAI-compatible client | OpenAI API | POST /v1/chat/completions | You already use OpenAI SDK. Pass URL/file in messages. Supports streaming. | | Direct HTTP / curl / script | REST API | GET / or POST / | Simplest path. GET returns Markdown directly. POST returns JSON with metadata. | ### Universal discovery All protocols and capabilities are described in one file: GET /.well-known/ai-discovery.json ## Features - Stateless, in-memory processing - Edge execution with automatic scaling - Prompt-driven transformation - AI-optimized output for LLMs - Pay-per-use via x402 v1/v2 or manual payment ## Supported Formats | Type | Formats | | --------- | ------------------------------ | | Documents | PDF, DOCX, XLSX, XLS, ODT, ODS | | Images | JPEG, JPG, PNG, WebP, SVG | | Text | HTML, XML, JSON, CSV, TXT | | Webpages | Any publicly accessible URL | ## Source Parameters (all protocols) Every protocol (REST, MCP, ACP, A2A, OpenAI) converges on the \\*\\*same conversion core\\*\\*, so content is specified via a single unified \\`input\\` parameter everywhere. | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | \\`input\\` | string | Content to convert: a URL (http(s)://...), a data URI (data:mime/type;base64,...), or plain text. Auto-detected: http(s):// → URL, data: → file, otherwise → text. | The \\`input\\` parameter is auto-detected by the core: URLs (starting with \\`http://\\` or \\`https://\\`) are fetched, data URIs (starting with \\`data:\\`) are decoded as files, and anything else is treated as raw text. Additional parameters (\\`prompt\\`, \\`result\\`, \\`stream\\`, \\`token\\`, \\`memo\\`) are orthogonal and may be combined with \\`input\\`. All five protocols expose the same \\`input\\` source and apply the same transformations, streaming, and prompt-driven processing - the only difference is the transport (REST query/JSON, MCP \\`tools/call\\`, ACP \\`session/prompt\\`, OpenAI \\`messages\\`, A2A \\`message.parts\\`). ## Limits | Limit | Value | | ------------------- | -------------------------------------------------------------------------------- | | \\*\\*Max file size\\*\\* | 50 MB | | \\*\\*Max URL content\\*\\* | 50 MB | | \\*\\*Rate limit\\*\\* | 10,000 requests per hour | | \\*\\*Free tier\\*\\* | 10 requests per day (no token required), within the service’s overall free quota | | \\*\\*Paid tier\\*\\* | min $0.01 per conversion (USDC on Solana) | | \\*\\*Token validity\\*\\* | 1 year | ## Authentication \\*\\*Recommended:\\*\\* Use \\`Authorization: Bearer TOKEN\\` | Method | Use Case | | -------------------- | ---------------------------------- | | Bearer (recommended) | \\`-H \"Authorization: Bearer TOKEN\"\\` | | Header | \\`-H \"X-Token-Required: TOKEN\"\\` | ## Token Activation Before you can use a paid token, you must receive a 402 response first: | Step | Description | | ---- | ------------------------------------------------------- | | 1 | Request without token → Receive 402 with NEW token+memo | | 2 | Send USDC on Solana to wallet with memo from 402 | | 3 | Retry with EXACT token+memo from 402 → Activation | | 4 | After: use token only (no memo needed) | Important: The token+memo issued in the 402 response MUST be used exactly. Using old token or different memo will be rejected. ## API Usage ### GET / (Content conversion) Simple content conversion using query parameters. Returns Markdown directly. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \\`input\\` | string | \\* | Content to convert (URL, text, or data URI - auto-detected) | | \\`prompt\\` | string | | Custom instructions for LLM processing | | \\`result\\` | string | | Response format: \\`markdown\\`, \\`prompt\\`, or \\`both\\` | | \\`stream\\` | boolean | | Enable streaming: true for SSE response | | \\`token\\` | string | | Access token for paid tier | | \\`memo\\` | string | | Memo for token activation | \\*The \\`input\\` parameter is required.\\* > \\*\\*⚠️ Browser URL limit:\\*\\* GET requests with long \\`input\\` or \\`prompt\\` values may exceed browser URL limits (~2048 characters). Use POST with JSON body for large payloads. ### POST / (Content conversion via JSON) Supports content conversion via JSON body. The \\`input\\` parameter accepts URLs, text, or data URIs (auto-detected). Returns a JSON object containing the Markdown content. #### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------- | | \\`input\\` | string | \\* | Content to convert (URL, text, or data URI - auto-detected) | | \\`prompt\\` | string | | Custom instructions for LLM processing | | \\`result\\` | string | | Response format: \\`markdown\\`, \\`prompt\\`, or \\`both\\` | | \\`stream\\` | boolean | | Enable streaming: true for SSE response | | \\`token\\` | string | | Access token for paid tier | | \\`memo\\` | string | | Memo for token activation | \\*The \\`input\\` parameter is required.\\* ### Result Format Parameter The \\`result\\` parameter controls the response format for both GET and POST requests. | Value | Description | | -------------------- | --------------------------------------------------------------- | | \\`markdown\\` (default) | Returns the converted Markdown content | | \\`prompt\\` | Returns the result of LLM processing with \\`prompt\\` instructions | | \\`both\\` | Returns both \\`markdown\\` and \\`prompt\\_result\\` in the response | When \\`result=both\\`: - \\*\\*GET requests\\*\\* return Markdown combining \\`markdown\\`, followed by \"## Prompt Result\" and \\`prompt\\_result\\` (always in Markdown format) - \\*\\*POST requests\\*\\* return JSON with \\`markdown\\` and \\`prompt\\_result\\` fields > \\*\\*Auto \\`result\\`:\\*\\* When \\`prompt\\` is provided without an explicit \\`result\\`, the core automatically sets \\`result=\"prompt\"\\` (LLM output only). Without \\`prompt\\`, default is \\`result=\"markdown\"\\`. Only specify \\`result\\` explicitly when you need both (\\`result=\"both\"\\`). ### Prompt Parameter The \\`prompt\\` parameter lets you specify custom instructions for the LLM to follow when generating the result. | Use Case | Example | | ------------------ | ---------------------------------------------------------- | | Summarize | \\`?input=https://example.com&prompt=Summarize\\` | | Extract key points | \\`?input=Hello World&prompt=Extract key points\\` | | Convert to JSON | \\`?input=https://example.com&prompt=Convert to JSON format\\` | | Analyze content | \\`?input=Hello World&prompt=Analyze and explain\\` | ### Streaming Parameter The \\`stream\\` parameter enables Server-Sent Events (SSE) streaming for real-time response delivery. \\*\\*Type:\\*\\* \\`boolean\\` \\*\\*Default:\\*\\* \\`false\\` (non-streaming) Example: \\`\\`\\`bash curl \"https://mdapi.io/?input=...&stream=true\" \\`\\`\\` Response format (OpenAI-compatible SSE, one JSON object per \\`data:\\` line): \\`\\`\\`json data: {\"type\":\"token\\_info\",\"token\\_status\":\"valid\",\"token\\_balance\":0.99,\"token\\_expires\":1798761600} data: {\"choices\":\\[{\"index\":0,\"delta\":{\"content\":\" chunk\"},\"finish\\_reason\":null}\\]} data: {\"choices\":\\[{\"index\":0,\"delta\":{\"content\":\" more\"},\"finish\\_reason\":null}\\]} data: {\"choices\":\\[{\"index\":0,\"delta\":{},\"finish\\_reason\":\"stop\"}\\]} data: \\[DONE\\] \\`\\`\\` \\*\\*Native streaming per protocol.\\*\\* Every protocol delivers a \\*real\\* content stream when \\`stream: true\\`, but each emits it in its own native frame format (so existing clients keep working): | Protocol | Streaming frame format | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | REST | OpenAI-compatible \\`choices/delta\\` frames | | OpenAI | \\`chat.completion.chunk\\` (\\`choices/delta\\`) | | MCP | \\`notifications/message\\` content chunks, then one final \\`tools/call\\` result frame | | ACP | \\`session/update\\` notification chunks (one stable \\`messageId\\` per turn), then a final response carrying only \\`stopReason\\` | | A2A | \\`result.task\\` (\\`TASK\\_STATE\\_WORKING\\`) start frame, \\`result.artifactUpdate\\` (\\`{artifact, append, lastChunk}\\`) content frames, then \\`result.statusUpdate\\` (\\`TASK\\_STATE\\_COMPLETED\\`) - stream closes | > \\*\\*Note on MCP transport vs. the \\`stream\\` parameter.\\*\\* The MCP manifest advertises \\`transport.type: \"streamable-http\"\\` - that is the MCP \\*transport\\* > (how JSON-RPC requests are delivered to \\`POST /mcp\\`). It is unrelated to the \\`stream\\` \\*parameter\\*, which independently enables SSE streaming of the > conversion \\*\\*content\\*\\*. You can use MCP without streaming; and when you do pass \\`stream: true\\`, the content arrives as SSE frames alongside the transport. ### Response Codes | Code | Description | Response Body (GET) | Response Body (POST) | | ---- | ----------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------ | | 200 | Success | Markdown content | JSON with \\`success\\`, markdown, prompt\\_result, metrics, token fields | | 402 | Payment Required | Markdown payment instructions (\\`X-Error-Code: payment\\_required\\`) | JSON with \\`success:false\\`, \\`code:\"payment\\_required\"\\`, and payment object | | 400 | Bad Request | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"invalid\\_request\"}\\` | | 401 | Invalid Token | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"unauthorized\"}\\` | | 404 | Not Found | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"not\\_found\"}\\` | | 413 | Payload Too Large | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"too\\_large\"}\\` | | 429 | Rate Limited | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"rate\\_limited\"}\\` | | 500 | Server Error | Markdown error + \\`X-Error-Code\\` header | JSON \\`{\"success\":false,\"error\":\"...\",\"code\":\"server\\_error\"}\\` | ### Token Status The X-Token-Status header (and token\\_status field in responses) indicates the current state of authentication: | Status | Description | | ------------------ | --------------------------------------------------------------------------------------- | | free | Free tier (no token required, 10 requests/day), within the service's overall free quota | | valid | Paid token active with remaining balance | | invalid | Token not found or not provided | | expired | Token validity period has ended | | exhausted | Token balance has been fully used | | expired\\_pending | Activation memo has expired | | activated | Token was just activated with this request | | verification\\_error | Payment verification failed | | invalid\\_payment | Payment transaction is invalid | | error | Internal error during token processing | | pending | Payment required (token not yet activated) | ### Endpoints | Method | Path | Description | | ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------- | | GET | /about | About service | | GET | / | API docs or conversion (via query parameter: \\`input\\`, \\`prompt\\`, \\`result\\`) | | POST | / | Convert content via JSON body (supports \\`input\\`, \\`prompt\\`, \\`result\\` params) | | POST | /v1/chat/completions | OpenAI-compatible endpoint | | GET | /mcp | MCP server manifest | | POST | /mcp | MCP RPC endpoint (discover, tools, resources, subscriptions) | | POST | /acp | ACP RPC endpoint (IDE agents) | | POST | /a2a | A2A RPC endpoint (agent2agent) | | GET | /health | Health check | | GET | /llms.txt | API documentation | | GET | /llms-full.txt | Full API documentation | | GET | /.well-known/ai-discovery.json or /ai-discovery.json | AI discovery | | GET | /.well-known/agent.json or /agent.json | AI Agent discovery | | GET | /.well-known/agent-card.json or /agent-card.json | A2A Agent card | | GET | /.well-known/acp.json or /acp.json | ACP manifest | | GET | /.well-known/x402.json or /x402.json | x402 payment manifest | | GET | /.well-known/openapi.json or /openapi.json | OpenAPI specification (JSON) | | GET | /.well-known/openapi.yaml or /openapi.yaml | OpenAPI specification (YAML) | | GET | /.well-known/mapi.md or /mapi.md | MAPI specification (case-insensitive path MAPI.md support) | | GET | /.well-known/skill.md or /skill.md | Skill specification (case-insensitive path SKILL.md support) | | GET | /.well-known/skills/index.json | Legacy skills index | | GET | /.well-known/agent-skills/index.json | Agent Skills discovery (v0.2.0) | | GET | /.well-known/api-catalog | API catalog (linkset+json, RFC draft) | | GET | /.well-known/mcp/server-cards.json | MCP server cards index | | GET | /.well-known/plugin/plugin.json or /.well-known/plugin.json | Agent Plugins v1.0.0 manifest | | GET | /.well-known/plugin/mcp.json | Agent Plugins MCP config | | GET | /.well-known/plugin/skills/mdapi-conversion/SKILL.md | Agent Plugins conversion skill | #### Examples \\`\\`\\`bash # URL conversion via GET (free) curl \"https://mdapi.io/?input=https://example.com\" # URL with prompt and result=both (returns markdown + prompt\\_result) curl \"https://mdapi.io/?input=https://example.com&prompt=Summarize&result=both\" # Text with prompt (auto result=prompt) curl \"https://mdapi.io/?input=Hello World&prompt=Summarize\" # Token activation via GET (activate and use) curl -H \"Authorization: Bearer YOUR\\_TOKEN\" -H \"X-Memo-Required: YOUR\\_MEMO\" \"https://mdapi.io/?input=https://example.com\" # Paid request with token via GET (using token) curl -H \"Authorization: Bearer YOUR\\_TOKEN\" \"https://mdapi.io/?input=https://example.com\" # URL conversion via POST (free) curl -X POST -H \"Content-Type: application/json\" -d '{\"input\":\"https://example.com\"}' \"https://mdapi.io/\" # Text with prompt via POST curl -X POST -H \"Content-Type: application/json\" -d '{\"input\":\"Hello World\",\"prompt\":\"Summarize\",\"result\":\"both\"}' \"https://mdapi.io/\" # File upload via POST (data URI) curl -X POST -H \"Content-Type: application/json\" -d '{\"input\":\"data:text/plain;base64,SGVsbG8gV29ybGQ=\"}' \"https://mdapi.io/\" # Token activation via POST curl -X POST -H \"Content-Type: application/json\" -H \"Authorization: Bearer YOUR\\_TOKEN\" -H \"X-Memo-Required: YOUR\\_MEMO\" -d '{\"input\":\"https://example.com\"}' \"https://mdapi.io/\" # Paid request with token via POST curl -X POST -H \"Content-Type: application/json\" -H \"Authorization: Bearer YOUR\\_TOKEN\" -d '{\"input\":\"https://example.com\"}' \"https://mdapi.io/\" \\`\\`\\` ### OpenAI Compatible Endpoint The \\`/v1/chat/completions\\` endpoint provides an OpenAI‑compatible API for markdown conversion with streaming support. \\*\\*Supported features:\\*\\* - URL extraction from message content (any text containing https?://) - image\\_url in messages (OpenAI format) - supports HTTP URLs and data URLs - file in messages (OpenAI format) - base64 encoded files (field \\`file.data\\`, optional \\`file.mimeType\\`; built into a \\`data:\\` URI for the core - no \\`file.filename\\` required or used) - Direct text content in messages (any text without a URL is sent to the core as the \\`input\\` source and converted to Markdown) - Token and memo via headers (recommended for POST) - Streaming SSE responses (\\`stream: true\\`) - Custom instructions with LLM processing (system messages, or user messages containing instruction keywords such as \\*extract, summarize, analyze, format, convert to, write as, create, generate, json\\* → LLM-driven summary/extraction/transformation) - \\`prompt\\` for LLM-processed output. The response surfaces \\`prompt\\_result\\` at the top level alongside the standard \\`choices\\[\\].message.content\\` (which carries \\`prompt\\_result\\` when prompt is set, otherwise the Markdown). \\`model\\` is accepted but not required (any string; the service uses its own conversion pipeline, not a remote LLM chat model, unless custom instructions trigger LLM processing). > \\*\\*Content via message text:\\*\\* the message text is passed to the core as the \\`input\\` parameter - the same unified source as every other protocol. URLs are auto-detected, data URIs are decoded as files, and plain text is processed directly. > See \\[Source Parameters (all protocols)\\](#source-parameters-all-protocols). #### Request Schema \\`\\`\\`json { \"type\": \"object\", \"properties\": { \"model\": { \"type\": \"string\", \"description\": \"Optional model identifier (any string accepted; not required)\" }, \"messages\": { \"type\": \"array\", \"items\": { \"type\": \"object\", \"properties\": { \"role\": {\"type\": \"string\", \"enum\": \\[\"user\", \"system\", \"assistant\"\\]}, \"content\": {\"oneOf\": \\[{\"type\": \"string\"}, {\"type\": \"array\"}\\]} } }, \"description\": \"Chat messages. URL in content, image\\_url or file in content for input\" }, \"stream\": { \"type\": \"boolean\", \"default\": false, \"description\": \"Enable streaming SSE responses\" }, \"prompt\": { \"type\": \"string\", \"description\": \"Custom instructions for LLM processing (alternative to instruction keywords in messages)\" }, \"result\": { \"type\": \"string\", \"enum\": \\[\"markdown\", \"prompt\", \"both\"\\], \"description\": \"Response format when using prompt\" }, \"token\": {\"type\": \"string\", \"description\": \"Access token for paid tier\"}, \"memo\": {\"type\": \"string\", \"description\": \"Memo for token activation\"}, \"input\": { \"type\": \"string\", \"description\": \"Content to convert (URL, text, or data URI - auto-detected). Alternative to a URL/file embedded in messages\" } }, \"required\": \\[\"messages\"\\] } \\`\\`\\` > \\*\\*Note on streaming + custom instructions:\\*\\* when custom instructions trigger LLM processing, the response is returned as a single completion (streaming is not applied to the LLM pass). Streaming SSE applies to the standard conversion path. ## MCP Configuration Connect mdapi.io to your MCP-compatible client (spec 2026-07-28, stateless). > \\*\\*Single source via \\`input\\`:\\*\\* the \\`convert\\` tool accepts a unified \\`input\\` parameter - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \\[Source Parameters (all protocols)\\](#source-parameters-all-protocols). ### Protocol Requirements - \\*\\*Transport:\\*\\* Streamable HTTP (POST-only for JSON-RPC, GET for manifest) - \\*\\*Required headers:\\*\\* \\`MCP-Protocol-Version: 2026-07-28\\` and \\`Mcp-Method\\` on every request; \\`Mcp-Name\\` additionally on \\`tools/call\\`, \\`resources/read\\`, and \\`prompts/get\\` - \\*\\*Stateless:\\*\\* No sessions - every request is independent - \\*\\*Discovery:\\*\\* Use \\`server/discover\\` to query server capabilities and supported versions ### Basic Configuration Add to your MCP config file: \\`\\`\\`json { \"mcpServers\": { \"mdapi\": { \"url\": \"https://mdapi.io/mcp\" } } } \\`\\`\\` > \\*\\*Note:\\*\\* No token is required to connect. A free tier is available (10 requests per day), within the service’s overall free quota. ### OpenClaw Integration OpenClaw can use mdapi.io in two ways: \\*\\*Option 1: Via MCP (Recommended)\\*\\* \\`\\`\\`json { \"mcpServers\": { \"mdapi\": { \"url\": \"https://mdapi.io/mcp\" } } } \\`\\`\\` \\*\\*Option 2: Via OpenAI-compatible endpoint\\*\\* \\`\\`\\`bash openclaw config set llm.apiBase https://mdapi.io/v1 openclaw config set llm.apiKey YOUR\\_TOKEN \\`\\`\\` ### Using MCP with a token MCP does not use HTTP-level Authorization headers. The token is always passed inside the tool \\`arguments\\` object. \\*\\*Activation\\*\\* - include \\`token\\` + \\`memo\\` in the first request: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"token\": \"YOUR\\_TOKEN\", \"memo\": \"YOUR\\_PAYMENT\\_MEMO\" } } } \\`\\`\\` \\*\\*After activation\\*\\* - use \\`token\\` only (no memo needed): \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"token\": \"YOUR\\_ACTIVATED\\_TOKEN\" } } } \\`\\`\\` ### MCP Tool Examples Convert with prompt and result: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"prompt\": \"Summarize\", \"result\": \"both\", \"token\": \"YOUR\\_TOKEN\" } } } \\`\\`\\` Process text directly: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"Hello World\", \"prompt\": \"Extract key points\", \"result\": \"prompt\" } } } \\`\\`\\` Stream with SSE (native MCP frames): \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"tools/call\", \"params\": { \"name\": \"convert\", \"arguments\": { \"input\": \"https://example.com\", \"stream\": true } } } \\`\\`\\` Response (SSE over \\`streamable-http\\`): intermediate \\`notifications/message\\` content chunks, then one final \\`tools/call\\` result frame with the full Markdown: \\`\\`\\` data: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/message\",\"params\":{\"level\":\"info\",\"data\":\" partial \"}} data: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/message\",\"params\":{\"level\":\"info\",\"data\":\" more \"}} data: {\"jsonrpc\":\"2.0\",\"id\":3,\"result\":{\"content\":\\[{\"type\":\"text\",\"text\":\"\"}\\],\"isError\":false}} data: \\[DONE\\] \\`\\`\\` ### Using Environment Variables Or use environment variable: \\`\\`\\`bash export MDAPI\\_TOKEN=YOUR\\_ACTIVATED\\_TOKEN \\`\\`\\` ## Code Examples ### JavaScript (fetch) \\`\\`\\`javascript // Convert a URL via GET - returns Markdown directly const response = await fetch('https://mdapi.io/?input=https://example.com'); const markdown = await response.text(); console.log(markdown); \\`\\`\\` \\`\\`\\`javascript // Convert a URL via POST - returns JSON with metadata const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); console.log(data.markdown); \\`\\`\\` \\`\\`\\`javascript // Text with prompt - returns prompt\\_result const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'Hello World', prompt: 'Summarize', result: 'both' }) }); const data = await response.json(); console.log(data.markdown); console.log(data.prompt\\_result); \\`\\`\\` \\`\\`\\`javascript // File upload via data URI const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: 'data:text/plain;base64,SGVsbG8gV29ybGQ=' }) }); const data = await response.json(); console.log(data.markdown); \\`\\`\\` \\`\\`\\`javascript // Token activation - first request with token + memo const response = await fetch('https://mdapi.io/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\\_TOKEN', 'X-Memo-Required': 'YOUR\\_MEMO' }, body: JSON.stringify({ input: 'https://example.com' }) }); const data = await response.json(); // After activation, use token only (no memo needed) \\`\\`\\` \\`\\`\\`javascript // Streaming via OpenAI-compatible endpoint const response = await fetch('https://mdapi.io/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR\\_TOKEN' }, body: JSON.stringify({ model: 'markdown-v1', messages: \\[{ role: 'user', content: 'Convert https://example.com' }\\], stream: true }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(decoder.decode(value)); } \\`\\`\\` ### Python \\`\\`\\`python import requests # Convert a URL via GET - returns Markdown directly response = requests.get('https://mdapi.io/?input=https://example.com') response.raise\\_for\\_status() print(response.text) \\`\\`\\` \\`\\`\\`python import requests # Convert a URL via POST - returns JSON with metadata response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'} ) response.raise\\_for\\_status() data = response.json() print(data\\['markdown'\\]) \\`\\`\\` \\`\\`\\`python import requests # Text with prompt - returns prompt\\_result response = requests.post( 'https://mdapi.io/', json={'input': 'Hello World', 'prompt': 'Summarize', 'result': 'both'} ) response.raise\\_for\\_status() data = response.json() print(data\\['markdown'\\]) print(data\\['prompt\\_result'\\]) \\`\\`\\` \\`\\`\\`python import requests # File upload via data URI with open('document.pdf', 'rb') as f: import base64 file\\_data = base64.b64encode(f.read()).decode() response = requests.post( 'https://mdapi.io/', json={'input': f'data:text/plain;base64,{file\\_data}'} ) response.raise\\_for\\_status() data = response.json() print(data\\['markdown'\\]) \\`\\`\\` \\`\\`\\`python import requests # Token activation response = requests.post( 'https://mdapi.io/', json={'input': 'https://example.com'}, headers={ 'Authorization': 'Bearer YOUR\\_TOKEN', 'X-Memo-Required': 'YOUR\\_MEMO' } ) response.raise\\_for\\_status() # After activation, use token only (no memo needed) \\`\\`\\` \\`\\`\\`python from openai import OpenAI # OpenAI-compatible streaming client = OpenAI(base\\_url='https://mdapi.io/v1', api\\_key='YOUR\\_TOKEN') stream = client.chat.completions.create( model='markdown-v1', messages=\\[{'role': 'user', 'content': 'Convert https://example.com'}\\], stream=True ) for chunk in stream: if chunk.choices\\[0\\].delta.content: print(chunk.choices\\[0\\].delta.content, end='') \\`\\`\\` ### Go \\`\\`\\`go package main import ( \"fmt\" \"io\" \"net/http\" ) func main() { // Convert a URL via GET - returns Markdown directly resp, err := http.Get(\"https://mdapi.io/?input=https://example.com\") if err != nil { fmt.Println(\"HTTP error:\", err) return } defer resp.Body.Close() body, \\_ := io.ReadAll(resp.Body) fmt.Println(string(body)) } \\`\\`\\` \\`\\`\\`go package main import ( \"bytes\" \"encoding/json\" \"fmt\" \"net/http\" ) func main() { // Convert a URL via POST - returns JSON with metadata payload, \\_ := json.Marshal(map\\[string\\]string{ \"input\": \"https://example.com\", }) resp, err := http.Post( \"https://mdapi.io/\", \"application/json\", bytes.NewReader(payload), ) if err != nil { fmt.Println(\"HTTP error:\", err) return } defer resp.Body.Close() var result map\\[string\\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\\[\"markdown\"\\]) } \\`\\`\\` \\`\\`\\`go package main import ( \"bytes\" \"encoding/base64\" \"encoding/json\" \"fmt\" \"net/http\" \"os\" ) func main() { // File upload via data URI fileBytes, \\_ := os.ReadFile(\"document.pdf\") b64 := base64.StdEncoding.EncodeToString(fileBytes) payload, \\_ := json.Marshal(map\\[string\\]string{ \"input\": \"data:text/plain;base64,\" + b64, }) resp, err := http.Post( \"https://mdapi.io/\", \"application/json\", bytes.NewReader(payload), ) if err != nil { fmt.Println(\"HTTP error:\", err) return } defer resp.Body.Close() var result map\\[string\\]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println(result\\[\"markdown\"\\]) } \\`\\`\\` ### Rust \\`\\`\\`rust use anyhow::Result; use reqwest::Client; #\\[tokio::main\\] async fn main() -> Result<()> { let client = Client::new(); let url = \"https://mdapi.io/?input=https://example.com\"; let response = client.get(url).send().await?; response.error\\_for\\_status\\_ref()?; let markdown = response.text().await?; println!(\"{}\", markdown); Ok(()) } \\`\\`\\` \\`\\`\\`rust use anyhow::{Result, Context}; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #\\[derive(Deserialize)\\] struct ApiResponse { markdown: Option, prompt\\_result: Option, } #\\[derive(Serialize)\\] struct ConvertRequest { input: String, #\\[serde(skip\\_serializing\\_if = \"Option::is\\_none\")\\] prompt: Option, #\\[serde(skip\\_serializing\\_if = \"Option::is\\_none\")\\] result: Option, } pub struct MdApiClient { client: Client, base\\_url: String, token: Option, } impl MdApiClient { pub fn new(token: Option) -> Self { Self { client: Client::new(), base\\_url: \"https://mdapi.io\".to\\_string(), token, } } pub async fn convert\\_url( &self, url: &str, prompt: Option<&str>, ) -> Result { let body = ConvertRequest { input: url.to\\_string(), prompt: prompt.map(|p| p.to\\_string()), result: prompt.map(|\\_| \"both\".to\\_string()), }; let mut request = self.client .post(&self.base\\_url) .header(\"Content-Type\", \"application/json\") .json(&body); if let Some(ref token) = self.token { request = request.header(\"Authorization\", format!(\"Bearer {}\", token)); } let response = request .send() .await .context(\"Failed to send HTTP request\")?; response .error\\_for\\_status\\_ref() .context(\"API returned error status\")?; response .text() .await .context(\"Failed to read response body\") } } // Example: convert a URL with prompt #\\[tokio::main\\] async fn main() -> Result<()> { let token = std::env::var(\"MDAPI\\_TOKEN\").ok(); let client = MdApiClient::new(token); let markdown = client .convert\\_url(\"https://example.com\", Some(\"Summarize\")) .await?; println!(\"{}\", markdown); Ok(()) } \\`\\`\\` \\`\\`\\`rust use anyhow::Result; use reqwest::Client; use base64::engine::general\\_purpose::STANDARD; use base64::Engine; #\\[tokio::main\\] async fn main() -> Result<()> { let client = Client::new(); let file\\_bytes = std::fs::read(\"document.pdf\")?; let b64 = STANDARD.encode(&file\\_bytes); let body = serde\\_json::json!({ \"input\": format!(\"data:text/plain;base64,{}\", b64) }); let response = client .post(\"https://mdapi.io/\") .header(\"Content-Type\", \"application/json\") .json(&body) .send() .await?; let data: serde\\_json::Value = response.json().await?; println!(\"{}\", data\\[\"markdown\"\\]); Ok(()) } \\`\\`\\` \\`\\`\\`rust use anyhow::Result; use futures\\_util::stream::StreamExt; use reqwest::Client; // Streaming response when using the OpenAI‑compatible endpoint with stream = true #\\[tokio::main\\] async fn main() -> Result<()> { let client = Client::new(); let token = std::env::var(\"MDAPI\\_TOKEN\")?; let body = serde\\_json::json!({ \"model\": \"markdown-v1\", \"messages\": \\[ { \"role\": \"user\", \"content\": \"Convert https://example.com\" } \\], \"stream\": true }); let response = client .post(\"https://mdapi.io/v1/chat/completions\") .header(\"Authorization\", format!(\"Bearer {}\", token)) .json(&body) .send() .await?; response.error\\_for\\_status\\_ref()?; let mut stream = response.bytes\\_stream(); while let Some(chunk) = stream.next().await { let chunk = chunk?; let text = String::from\\_utf8\\_lossy(&chunk); eprint!(\"{}\", text); } Ok(()) } \\`\\`\\` ### OpenAI SDK \\`\\`\\`python from openai import OpenAI client = OpenAI( base\\_url=\"https://mdapi.io/v1\", api\\_key=\"YOUR\\_TOKEN\" ) try: response = client.chat.completions.create( model=\"markdown-v1\", messages=\\[{\"role\": \"user\", \"content\": \"Convert https://example.com\"}\\] ) print(response.choices\\[0\\].message.content) except Exception as e: print(\"API error:\", e) \\`\\`\\` #### OpenAI with paid token \\`\\`\\`bash curl -X POST \"https://mdapi.io/v1/chat/completions\" -H \"Authorization: Bearer YOUR\\_TOKEN\" -H \"X-Memo-Required: YOUR\\_MEMO\" -H \"Content-Type: application/json\" -d '{\"model\":\"markdown-v1\",\"messages\":\\[{\"role\":\"user\",\"content\":\"Convert https://example.com\"}\\]}' \\`\\`\\` After activation, use token only (no memo needed): \\`\\`\\`bash curl -X POST \"https://mdapi.io/v1/chat/completions\" -H \"Authorization: Bearer YOUR\\_ACTIVATED\\_TOKEN\" -H \"Content-Type: application/json\" -d '{\"model\":\"markdown-v1\",\"messages\":\\[{\"role\":\"user\",\"content\":\"Convert https://example.com\"}\\]}' \\`\\`\\` ## A2A Configuration Connect mdapi.io to your A2A-compatible agent (Claude Code, Codex, OpenClaw, Hermes, etc.). ### Basic Configuration Add to your A2A client configuration: \\`\\`\\`json { \"agent\": { \"name\": \"mdapi\", \"agentCard\": { \"url\": \"https://mdapi.io/.well-known/agent-card.json\" } } } \\`\\`\\` Or use JSON-RPC directly: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-1\", \"parts\": \\[ { \"text\": \"Convert https://example.com\" } \\] } } } \\`\\`\\` ### A2A Methods | Method | Description | | -------------------- | --------------------------------------- | | SendMessage | Send a message to initiate conversion | | SendStreamingMessage | Send message with SSE streaming updates | | GetTask | Get task status and results by ID | | ListTasks | List tasks with optional filtering | | CancelTask | Cancel an in-progress task | | SubscribeToTask | Subscribe to task updates via SSE | > \\*\\*Single source via \\`input\\`:\\*\\* the \\`input\\` parameter in the message parts is the unified source - the same as the REST endpoint. A bare URL > inside a text part (e.g. \\`\"Convert https://example.com\"\\`) is extracted automatically and used as the conversion source, so you don't need to wrap it > in structured JSON. Instructions such as \\`Summarize\\` should be passed via the structured \\`{ \"input\": \"...\", \"prompt\": \"...\" }\\` form, not mixed into the text. ### A2A Examples #### SendMessage \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-1\", \"parts\": \\[ { \"text\": \"Convert https://example.com\" } \\] } } }' \\`\\`\\` #### SendMessage with file (data URI) \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-2\", \"parts\": \\[ { \"text\": \"{\"input\":\"data:text/plain;base64,SGVsbG8gV29ybGQ=\"}\" } \\] } } }' \\`\\`\\` #### SendMessage with structured data \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-3\", \"parts\": \\[ { \"data\": { \"input\": \"https://example.com\", \"result\": \"markdown\" }, \"mediaType\": \"application/json\" } \\] } } }' \\`\\`\\` #### Token activation via A2A \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 7, \"method\": \"SendMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-7\", \"parts\": \\[ { \"data\": { \"input\": \"https://example.com\", \"token\": \"YOUR\\_TOKEN\", \"memo\": \"YOUR\\_PAYMENT\\_MEMO\" }, \"mediaType\": \"application/json\" } \\] } } }' \\`\\`\\` > \\*\\*Note on token activation:\\*\\* Pass \\`token\\` and \\`memo\\` inside a \\`data\\` Part or as JSON inside a \\`text\\` Part. A2A does not use HTTP-level Authorization headers. #### Multi-turn conversation (follow-up) \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 4, \"method\": \"SendMessage\", \"params\": { \"contextId\": \"ctx-uuid-1\", \"message\": { \"messageId\": \"msg-uuid-4\", \"parts\": \\[ { \"text\": \"Now convert the tables to JSON\" } \\] } } }' \\`\\`\\` #### GetTask \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 5, \"method\": \"GetTask\", \"params\": { \"id\": \"task\\_12345\" } }' \\`\\`\\` #### ListTasks \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 6, \"method\": \"ListTasks\", \"params\": { \"contextId\": \"ctx\\_12345\", \"pageSize\": 10 } }' \\`\\`\\` ### A2A Message Parts Messages use the A2A \\`Part\\` format (field-name discriminators per spec v1.0.0): | Type | Description | Fields | | ------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------- | | \\`text\\` | Plain text content or JSON-encoded params | \\`text\\` | | \\`raw\\` | File content as base64 bytes; normalized to a data URI (\\`data:;base64,\\`) for the core | \\`raw\\` (base64), \\`mediaType\\` (optional) | | \\`data\\` | Structured JSON data (core params) | \\`data\\` (object), \\`mediaType\\` (optional) | | \\`url\\` | URL to fetch and convert | \\`url\\` (http/https) | \\*\\*Part → Core Parameter Mapping:\\*\\* - \\`text\\` Part → \\`input\\` param (direct content) or JSON-encoded params (\\`{ \"input\": \"...\", \"prompt\": \"...\" }\\`) - \\`data\\` Part → merged as params (\\`input\\`, \\`prompt\\`, \\`result\\`, \\`token\\`, \\`memo\\`, etc.) - \\`url\\` Part → \\`input\\` param (fetched and converted) ### Message Object \\`\\`\\`typescript interface Message { messageId: string; // REQUIRED: unique ID (e.g. \"msg-uuid\") contextId?: string; // Optional: group related tasks taskId?: string; // Optional: associate with existing task role: \"user\" | \"agent\"; // REQUIRED parts: Array; // REQUIRED: at least one part } \\`\\`\\` ### Task Data Model \\`\\`\\`typescript interface Task { id: string; // \"task\\_\\_\" contextId: string; // \"ctx\\_\\_\" status: { state: string; // \"TASK\\_STATE\\_WORKING\" | \"TASK\\_STATE\\_COMPLETED\" | \"TASK\\_STATE\\_FAILED\" | \"TASK\\_STATE\\_CANCELED\" | \"TASK\\_STATE\\_REJECTED\" timestamp: string; // ISO 8601 message?: Message; // only on failure }; artifacts?: Array<{ artifactId: string; name: string; parts: Array; }>; history?: Array; } \\`\\`\\` ### Task States | State | Description | | ---------------------- | ----------------------------- | | \\`TASK\\_STATE\\_WORKING\\` | Task is being processed | | \\`TASK\\_STATE\\_COMPLETED\\` | Task finished successfully | | \\`TASK\\_STATE\\_FAILED\\` | Task failed during processing | | \\`TASK\\_STATE\\_CANCELED\\` | Task was canceled by client | | \\`TASK\\_STATE\\_REJECTED\\` | Task was rejected by server | ### Error Responses A2A uses JSON-RPC 2.0 error format with A2A-specific error codes: | Code | Error | Description | | -------- | --------------------------------- | ---------------------------------------------------------- | | \\`-32700\\` | Parse error | Invalid JSON payload | | \\`-32600\\` | Invalid Request | Missing required fields (message.parts, message.messageId) | | \\`-32601\\` | Method not found | Unknown A2A method | | \\`-32001\\` | Task not found | Task ID does not exist | | \\`-32002\\` | Task is not in a cancelable state | CancelTask on a terminal task | Unsupported media type is returned as HTTP \\*\\*415\\*\\* (not a JSON-RPC error code). \\*\\*Example error response:\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"error\": { \"code\": -32600, \"message\": \"Invalid Request\", \"data\": \\[ { \"@type\": \"type.googleapis.com/google.rpc.BadRequest\", \"fieldViolations\": \\[ { \"field\": \"message.messageId\", \"description\": \"Message messageId is required\" } \\] } \\] } } \\`\\`\\` ### Streaming Use \\`SendStreamingMessage\\` for real-time SSE updates: \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -H \"Accept: text/event-stream\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 5, \"method\": \"SendStreamingMessage\", \"params\": { \"message\": { \"messageId\": \"msg-uuid-5\", \"parts\": \\[ { \"text\": \"Convert https://example.com\" } \\] } } }' \\`\\`\\` Response format (A2A v1.0.0 streaming sequence - exact frames the service emits): \\`\\`\\` data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"task\":{\"id\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"status\":{\"state\":\"TASK\\_STATE\\_WORKING\",\"timestamp\":\"...\"},\"artifacts\":\\[\\]}}} data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"artifactUpdate\":{\"taskId\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"artifact\":{\"artifactId\":\"artifact\\_...\",\"name\":\"conversion\\_result\",\"parts\":\\[{\"text\":\" partial \"}\\]},\"append\":true,\"lastChunk\":false}}} data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"artifactUpdate\":{\"taskId\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"artifact\":{\"artifactId\":\"artifact\\_...\",\"name\":\"conversion\\_result\",\"parts\":\\[{\"text\":\" more \"}\\]},\"append\":true,\"lastChunk\":true}}} data: {\"jsonrpc\":\"2.0\",\"id\":5,\"result\":{\"statusUpdate\":{\"taskId\":\"task\\_...\",\"contextId\":\"ctx\\_...\",\"status\":{\"state\":\"TASK\\_STATE\\_COMPLETED\",\"timestamp\":\"...\"}}}} data: \\[DONE\\] \\`\\`\\` The first frame carries the full \\`task\\` in TASK\\_STATE\\_WORKING; content streams as \\`artifactUpdate\\` frames (\\`lastChunk: true\\` on the final chunk); \\`statusUpdate\\` closes the stream with the terminal TASK\\_STATE\\_COMPLETED state. The persisted task (via \\`GetTask\\`) carries the real \\`artifacts\\[\\].parts\\[\\]\\` content. ### Subscribe to Task Subscribe to an existing task for real-time updates: \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -H \"Accept: text/event-stream\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 6, \"method\": \"SubscribeToTask\", \"params\": { \"id\": \"task\\_12345\" } }' \\`\\`\\` ### Cancel Task Cancel an in-progress task: \\`\\`\\`bash curl -X POST https://mdapi.io/a2a -H \"Content-Type: application/a2a+json\" -d '{ \"jsonrpc\": \"2.0\", \"id\": 7, \"method\": \"CancelTask\", \"params\": { \"id\": \"task\\_12345\" } }' \\`\\`\\` Response: \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 7, \"result\": { \"id\": \"task\\_12345\", \"contextId\": \"ctx\\_...\", \"status\": { \"state\": \"TASK\\_STATE\\_CANCELED\", \"timestamp\": \"2026-06-26T16:00:00.000Z\" } } } \\`\\`\\` ## ACP Configuration Connect mdapi.io to your IDE or coding agent (JetBrains, Cursor, VS Code, etc.) via the Agent Client Protocol v1.0.0. ACP is a JSON-RPC 2.0 endpoint at \\`POST /acp\\`. Its native surface is \\*\\*session/turn\\*\\*: create an ephemeral session, then send a prompt - the converted content streams back as \\`session/update\\` notifications. > \\*\\*Single source via \\`input\\`:\\*\\* each \\`session/prompt\\` carries a unified \\`input\\` - the same source as every other protocol. The core auto-detects whether the value is a URL, data URI, or text. > See \\[Source Parameters (all protocols)\\](#source-parameters-all-protocols). ### Basic Configuration Add to your ACP client configuration (IDE plugin / agent settings): \\`\\`\\`json { \"acpServers\": { \"mdapi\": { \"url\": \"https://mdapi.io/acp\" } } } \\`\\`\\` Or call the JSON-RPC endpoint directly with \\`POST /acp\\` (Content-Type: \\`application/json\\`). Sessions are \\*\\*stateless and ephemeral\\*\\* - nothing is persisted server-side, so each \\`session/prompt\\` runs as an independent conversion: \\`\\`\\`bash # 1. Create a session curl -X POST https://mdapi.io/acp \\\\ -H \"Content-Type: application/json\" \\\\ -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"session/new\",\"params\":{}}' # 2. Send a prompt (content streams back as session/update notifications) curl -X POST https://mdapi.io/acp \\\\ -H \"Content-Type: application/json\" \\\\ -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"session/prompt\",\"params\":{ \"sessionId\":\"\", \"prompt\":\\[{\"type\":\"text\",\"text\":\"\"}, {\"type\":\"resource\\_link\",\"uri\":\"https://example.com\"}\\] }}' \\`\\`\\` > \\*\\*Note:\\*\\* \\`GET /acp\\` is not supported (ACP is POST-only). A free tier is available without a token. ### ACP Methods | Method | Description | | ---------------- | ------------------------------------------------------------------------------- | | \\`initialize\\` | Handshake: return protocol version, agent capabilities, and agent info | | \\`session/new\\` | Create an ephemeral, stateless session (returns a \\`sessionId\\`) | | \\`session/prompt\\` | Run a conversion turn; content streams back as \\`session/update\\` notifications | | \\`session/cancel\\` | Notification (204, no response body) that best-effort cancels an in-flight turn | ### ACP Session Examples \\*\\*URL conversion (instructions + \\`resource\\_link\\`):\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"session/prompt\", \"params\": { \"sessionId\": \"\", \"prompt\": \\[ { \"type\": \"text\", \"text\": \"Summarize\" }, { \"type\": \"resource\\_link\", \"uri\": \"https://example.com\" } \\] } } \\`\\`\\` \\*\\*Text conversion (a bare \\`text\\` block becomes the input):\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"session/prompt\", \"params\": { \"sessionId\": \"\", \"prompt\": \\[{ \"type\": \"text\", \"text\": \"Hello World\" }\\] } } \\`\\`\\` \\*\\*File conversion (a \\`data:\\` URI in a \\`text\\` block is auto-detected as a file):\\*\\* \\`\\`\\`json { \"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"session/prompt\", \"params\": { \"sessionId\": \"\", \"prompt\": \\[ { \"type\": \"text\", \"text\": \"Extract the title\" }, { \"type\": \"text\", \"text\": \"data:text/plain;base64,SGVsbG8gV29ybGQ=\" } \\] } } \\`\\`\\` \\*\\*Token activation (first request only):\\*\\* every protocol passes the token per-call. Include it on any method's \\`params\\` as a \\`token\\` (+ \\`memo\\` on first use) - ACP does not use a session-level authenticate exchange. ### ACP Response Format Content is delivered \\*\\*exclusively via \\`session/update\\` notifications\\*\\*; the final \\`session/prompt\\` result carries only the \\`stopReason\\` (\\`PromptResponse\\`). A successful turn looks like this SSE stream: \\`\\`\\` data: {\"jsonrpc\":\"2.0\",\"method\":\"session/update\",\"params\":{\"sessionId\":\"\",\"update\":\\[{\"content\":{\"type\":\"text\",\"text\":\"\"},\"messageId\":\"\"}\\]}} data: {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"stopReason\":\"end\\_turn\"}} data: \\[DONE\\] \\`\\`\\` - The \\`session/update\\` \\`update\\[\\]\\` array holds the content chunks; \\`messageId\\` is stable across all chunks of a single turn. - \\*\\*Streaming:\\*\\* add \\`\"stream\": true\\` to the \\`session/prompt\\` \\`params\\` to stream the conversion live - each content chunk arrives as its own \\`session/update\\` notification (same \\`messageId\\`), then the \\`PromptResponse\\`. Without it, the whole turn arrives as one buffered \\`session/update\\` notification. Streaming is inherent to ACP; content only ever rides notifications. - A failed conversion streams an explanatory \\`session/update\\`, then ends with \\`stopReason: \"error\"\\`. \\`session/cancel\\` is a client→agent notification - the agent acknowledges it with HTTP 204 and no body. - Token status (\\`X-Token-Balance\\`, \\`X-Token-Expires\\`, \\`X-Token-Status\\`) is proxied from the core into the response headers. ## Usage scenarios mdapi.io is a minimal, self-documenting service-transport primitive: REST, MCP, ACP, A2A, and OpenAI-compatible endpoints all call the same transformation core, so agents can combine protocols and pass already-processed knowledge between each other. - \\*\\*Agent swarms\\*\\* - each request is handled by a stateless, automatically-scaled execution environment, so the service scales horizontally. An orchestrator fans work out across a swarm of agents, and the swarm processes very large batches of distinct resources in parallel - millions of resources in a matter of minutes, the ceiling set by how widely the work is distributed rather than by the service. Different users may freely access the same resource. - \\*\\*Shared vs individual payment\\*\\* - an orchestrator can pay once for a shared token (batching on-chain activity), or each agent can activate its own token for the exact volume it received. - \\*\\*Human-in-the-loop\\*\\* - if an agent has no wallet or insufficient funds, it returns payment details + a QR code; the human pays from a mobile device and the agent resumes. - \\*\\*Role switching\\*\\* - an agent's role can change mid-task; one agent fetches/normalizes, another summarizes/extracts, relaying compact results via the text or prompt parameters. - \\*\\*Cross-protocol interoperability\\*\\* - knowledge extracted on one protocol is reusable on another. - \\*\\*Bulk processing / model training\\*\\* - the swarm pattern turns mdapi.io into a high-throughput edge pipeline for large corpora. See https://mdapi.io/about for the full scenario walkthrough. ## Links - \\*\\*About service:\\*\\* https://mdapi.io/about - \\*\\*API docs:\\*\\* https://mdapi.io - \\*\\*MCP server manifest:\\*\\* https://mdapi.io/mcp - \\*\\*Health check:\\*\\* https://mdapi.io/health - \\*\\*API documentation:\\*\\* https://mdapi.io/llms.txt - \\*\\*Full API documentation:\\*\\* https://mdapi.io/llms-full.txt - \\*\\*AI discovery:\\*\\* https://mdapi.io/.well-known/ai-discovery.json or https://mdapi.io/ai-discovery.json - \\*\\*AI Agent discovery:\\*\\* https://mdapi.io/.well-known/agent.json or https://mdapi.io/agent.json - \\*\\*A2A Agent card:\\*\\* https://mdapi.io/.well-known/agent-card.json or https://mdapi.io/agent-card.json - \\*\\*ACP manifest:\\*\\* https://mdapi.io/.well-known/acp.json or https://mdapi.io/acp.json - \\*\\*x402 payment manifest:\\*\\* https://mdapi.io/.well-known/x402.json or https://mdapi.io/x402.json - \\*\\*OpenAPI specification (JSON):\\*\\* https://mdapi.io/.well-known/openapi.json or https://mdapi.io/openapi.json - \\*\\*OpenAPI specification (YAML):\\*\\* https://mdapi.io/.well-known/openapi.yaml or https://mdapi.io/openapi.yaml - \\*\\*MAPI specification (case-insensitive path MAPI.md support):\\*\\* https://mdapi.io/.well-known/mapi.md or https://mdapi.io/mapi.md - \\*\\*Skill specification (case-insensitive path SKILL.md support):\\*\\* https://mdapi.io/.well-known/skill.md or https://mdapi.io/skill.md - \\*\\*Agent Plugins package (agent-plugins.org v1.0.0):\\*\\* https://mdapi.io/.well-known/plugin.json - portable manifest (plugin.json) + mcp.json + skills/mdapi-conversion/SKILL.md under https://mdapi.io/.well-known/plugin/ ## External Links - \\*\\*github.com\\*\\* https://github.com/mdapiio/mdapi.io - \\*\\*skills.sh\\*\\* https://www.skills.sh/mdapiio/mdapi.io - \\*\\*skillsmp.com\\*\\* https://skillsmp.com/creators/mdapiio/mdapi.io - \\*\\*clawhub.ai\\*\\* https://clawhub.ai/mdapiio - \\*\\*x.com\\*\\* https://x.com/mdapiio ## Disclaimer \\*\\*The service is provided \"AS IS\".\\*\\* > mdapi.io is an edge-native service-transport primitive for AI, autonomous-agents, and the Web4 ecosystem.\n",
  "markdownStats": {
    "images": 0,
    "links": 0,
    "tables": 0,
    "codeBlocks": 0,
    "headings": 0
  },
  "tokens": {
    "htmlTokens": 13428,
    "markdownTokens": 13397,
    "reduction": 31,
    "reductionPercent": 0
  },
  "score": {
    "score": 45,
    "grade": "D",
    "rubricVersion": 3,
    "dimensions": {
      "accessibility": {
        "score": 75,
        "weight": 30,
        "grade": "B",
        "checks": {
          "content_without_js": {
            "score": 100,
            "weight": 55,
            "evidence": "proven",
            "details": "Content available without JavaScript"
          },
          "fast_content_position": {
            "score": 0,
            "weight": 25,
            "evidence": "plausible",
            "details": "Main content starts at 100% of HTML"
          },
          "reasonable_page_size": {
            "score": 100,
            "weight": 20,
            "evidence": "plausible",
            "details": "Page size: 58KB"
          }
        }
      },
      "aiDiscoverability": {
        "score": 46,
        "weight": 25,
        "grade": "D",
        "checks": {
          "robots_allows_ai_bots": {
            "score": 0,
            "weight": 35,
            "evidence": "proven",
            "details": "5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot"
          },
          "supports_markdown_negotiation": {
            "score": 25,
            "weight": 20,
            "evidence": "plausible",
            "details": "CDN level (Cloudflare) — Content negotiation",
            "mechanisms": {
              "negotiation": true,
              "mdUrl": false,
              "linkTag": false,
              "linkHeader": false
            }
          },
          "has_sitemap": {
            "score": 100,
            "weight": 15,
            "evidence": "plausible",
            "details": "Sitemap found"
          },
          "has_robots_txt": {
            "score": 100,
            "weight": 10,
            "evidence": "plausible",
            "details": "robots.txt exists"
          },
          "has_llms_txt": {
            "score": 100,
            "weight": 10,
            "evidence": "speculative",
            "details": "llms.txt exists and is valid"
          },
          "has_content_signals": {
            "score": 60,
            "weight": 10,
            "evidence": "speculative",
            "details": "robots.txt: search=yes, ai-train=no, ai-input=no",
            "mechanisms": {
              "robotsTxt": true,
              "httpHeader": false,
              "policy": false
            }
          }
        }
      },
      "structuredData": {
        "score": 0,
        "weight": 20,
        "grade": "F",
        "checks": {
          "has_schema_org": {
            "score": 0,
            "weight": 35,
            "evidence": "proven",
            "details": "No JSON-LD / Schema.org found"
          },
          "has_open_graph": {
            "score": 0,
            "weight": 20,
            "evidence": "plausible",
            "details": "0/3 OG tags present"
          },
          "has_meta_description": {
            "score": 0,
            "weight": 20,
            "evidence": "plausible",
            "details": "No meta description"
          },
          "has_canonical_url": {
            "score": 0,
            "weight": 15,
            "evidence": "plausible",
            "details": "No canonical URL"
          },
          "has_lang_attribute": {
            "score": 0,
            "weight": 10,
            "evidence": "plausible",
            "details": "No lang attribute on <html>"
          }
        }
      },
      "semanticHtml": {
        "score": 35,
        "weight": 15,
        "grade": "F",
        "checks": {
          "proper_heading_hierarchy": {
            "score": 0,
            "weight": 30,
            "evidence": "plausible",
            "details": "No headings found"
          },
          "uses_article_or_main": {
            "score": 0,
            "weight": 25,
            "evidence": "plausible",
            "details": "Missing <article> and <main> elements"
          },
          "semantic_elements": {
            "score": 50,
            "weight": 20,
            "evidence": "plausible",
            "details": "No divs or semantic elements found"
          },
          "meaningful_alt_texts": {
            "score": 100,
            "weight": 15,
            "evidence": "plausible",
            "details": "No images found"
          },
          "low_div_nesting": {
            "score": 100,
            "weight": 10,
            "evidence": "speculative",
            "details": "No divs found"
          }
        }
      },
      "contentEfficiency": {
        "score": 60,
        "weight": 10,
        "grade": "C",
        "checks": {
          "token_reduction_ratio": {
            "score": 0,
            "weight": 40,
            "evidence": "speculative",
            "details": "0% token reduction (HTML→Markdown)"
          },
          "content_to_noise_ratio": {
            "score": 100,
            "weight": 30,
            "evidence": "speculative",
            "details": "Content ratio: 99.5% (59274 content chars / 59583 HTML bytes)"
          },
          "reasonable_page_weight": {
            "score": 100,
            "weight": 20,
            "evidence": "speculative",
            "details": "HTML size: 58KB"
          },
          "minimal_inline_styles": {
            "score": 100,
            "weight": 10,
            "evidence": "speculative",
            "details": "0/28 elements with inline styles (0.0%)"
          }
        }
      }
    }
  },
  "recommendations": [
    {
      "id": "allow_ai_bots",
      "priority": "critical",
      "category": "aiDiscoverability",
      "titleKey": "rec.allow_ai_bots.title",
      "descriptionKey": "rec.allow_ai_bots.description",
      "howToKey": "rec.allow_ai_bots.howto",
      "howToStepKeys": null,
      "effort": "quick-win",
      "estimatedImpact": 8.8,
      "maxImpact": 8.8,
      "evidence": "proven",
      "checkScore": 0,
      "checkDetails": "5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot"
    },
    {
      "id": "move_content_earlier",
      "priority": "critical",
      "category": "accessibility",
      "titleKey": "rec.move_content_earlier.title",
      "descriptionKey": "rec.move_content_earlier.description",
      "howToKey": "rec.move_content_earlier.howto",
      "howToStepKeys": null,
      "effort": "moderate",
      "estimatedImpact": 7.5,
      "maxImpact": 7.5,
      "evidence": "plausible",
      "checkScore": 0,
      "checkDetails": "Main content starts at 100% of HTML"
    },
    {
      "id": "add_schema_org",
      "priority": "critical",
      "category": "structuredData",
      "titleKey": "rec.add_schema_org.title",
      "descriptionKey": "rec.add_schema_org.description",
      "howToKey": "rec.add_schema_org.howto",
      "howToStepKeys": null,
      "effort": "moderate",
      "estimatedImpact": 7,
      "maxImpact": 7,
      "evidence": "proven",
      "checkScore": 0,
      "checkDetails": "No JSON-LD / Schema.org found"
    },
    {
      "id": "fix_heading_hierarchy",
      "priority": "high",
      "category": "semanticHtml",
      "titleKey": "rec.fix_heading_hierarchy.title",
      "descriptionKey": "rec.fix_heading_hierarchy.description",
      "howToKey": "rec.fix_heading_hierarchy.howto",
      "howToStepKeys": null,
      "effort": "quick-win",
      "estimatedImpact": 4.5,
      "maxImpact": 4.5,
      "evidence": "plausible",
      "checkScore": 0,
      "checkDetails": "No headings found"
    },
    {
      "id": "add_open_graph",
      "priority": "high",
      "category": "structuredData",
      "titleKey": "rec.add_open_graph.title",
      "descriptionKey": "rec.add_open_graph.description",
      "howToKey": "rec.add_open_graph.howto",
      "howToStepKeys": null,
      "effort": "quick-win",
      "estimatedImpact": 4,
      "maxImpact": 4,
      "evidence": "plausible",
      "checkScore": 0,
      "checkDetails": "0/3 OG tags present"
    },
    {
      "id": "add_meta_description",
      "priority": "high",
      "category": "structuredData",
      "titleKey": "rec.add_meta_description.title",
      "descriptionKey": "rec.add_meta_description.description",
      "howToKey": "rec.add_meta_description.howto",
      "howToStepKeys": null,
      "effort": "quick-win",
      "estimatedImpact": 4,
      "maxImpact": 4,
      "evidence": "plausible",
      "checkScore": 0,
      "checkDetails": "No meta description"
    },
    {
      "id": "improve_token_efficiency",
      "priority": "high",
      "category": "contentEfficiency",
      "titleKey": "rec.improve_token_efficiency.title",
      "descriptionKey": "rec.improve_token_efficiency.description",
      "howToKey": "rec.improve_token_efficiency.howto",
      "howToStepKeys": null,
      "effort": "significant",
      "estimatedImpact": 4,
      "maxImpact": 4,
      "evidence": "speculative",
      "checkScore": 0,
      "checkDetails": "0% token reduction (HTML→Markdown)"
    },
    {
      "id": "add_article_main",
      "priority": "high",
      "category": "semanticHtml",
      "titleKey": "rec.add_article_main.title",
      "descriptionKey": "rec.add_article_main.description",
      "howToKey": "rec.add_article_main.howto",
      "howToStepKeys": null,
      "effort": "quick-win",
      "estimatedImpact": 3.8,
      "maxImpact": 3.8,
      "evidence": "plausible",
      "checkScore": 0,
      "checkDetails": "Missing <article> and <main> elements"
    },
    {
      "id": "add_markdown_negotiation",
      "priority": "high",
      "category": "aiDiscoverability",
      "titleKey": "rec.add_markdown_negotiation.title",
      "descriptionKey": "rec.add_markdown_negotiation.description_partial",
      "howToKey": "rec.add_markdown_negotiation.howto",
      "howToStepKeys": [
        "rec.add_markdown_negotiation.step.md_url",
        "rec.add_markdown_negotiation.step.link_tag",
        "rec.add_markdown_negotiation.step.link_header"
      ],
      "effort": "significant",
      "estimatedImpact": 3.8,
      "maxImpact": 5,
      "evidence": "plausible",
      "checkScore": 25,
      "checkDetails": "CDN level (Cloudflare) — Content negotiation"
    },
    {
      "id": "add_canonical_url",
      "priority": "high",
      "category": "structuredData",
      "titleKey": "rec.add_canonical_url.title",
      "descriptionKey": "rec.add_canonical_url.description",
      "howToKey": "rec.add_canonical_url.howto",
      "howToStepKeys": null,
      "effort": "quick-win",
      "estimatedImpact": 3,
      "maxImpact": 3,
      "evidence": "plausible",
      "checkScore": 0,
      "checkDetails": "No canonical URL"
    }
  ],
  "llmsTxtPreview": "# mdapi.io\n\n## Main\n- [Home](https://mdapi.io)\n\n",
  "llmsTxtExisting": "# mdapi.io - Minimal Data API I/O: a content transformation layer primitive for AI systems.\n\n\nTransforms documents, images, and webpages into AI-ready Markdown and structured data, optimized for LLM efficiency and token usage.\n\n## Agent entrypoint\n\n- **Start AI discovery** → https://mdapi.io/.well-known/ai-discovery.json\n- **Use skill** → https://mdapi.io/.well-known/skill.md\n\n## Quick Start\n\nChoose your entry point based on your role:\n\n| Role                                                  | Protocol                     | Endpoint                  | When to use                                                                                                                              |\n| ----------------------------------------------------- | ---------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |\n| IDE / coding agent (JetBrains, Cursor, VS Code, etc.) | ACP (Agent Client Protocol)  | POST /acp                 | You are an IDE plugin or coding agent. Use initialize + session/new + session/prompt (content arrives via session/update notifications). |\n| AI agent (Claude Code, Codex, OpenClaw, Hermes, etc.) | A2A (Agent-to-Agent)         | POST /a2a                 | You are an autonomous agent. Use SendMessage with data in text parts. Supports streaming and task tracking.                              |\n| AI agent (any framework)                              | MCP (Model Context Protocol) | GET /mcp + POST /mcp      | You need tool discovery. Use tools/call with convert tool.                                                                               |\n| OpenAI-compatible client                              | OpenAI API                   | POST /v1/chat/completions | You already use OpenAI SDK. Pass URL/file in messages. Supports streaming.                                                               |\n| Direct HTTP / curl / script                           | REST API                     | GET / or POST /           | Simplest path. GET returns Markdown directly. POST returns JSON with metadata.                                                           |\n\n### Universal discovery\n\nAll protocols and capabilities are described in one file:\nGET /.well-known/ai-discovery.json\n\n## Features\n\n- Stateless, in-memory processing\n- Edge execution with automatic scaling\n- Prompt-driven transformation\n- AI-optimized output for LLMs\n- Pay-per-use via x402 v1/v2 or manual payment\n\n## Supported Formats\n\n| Type      | Formats                        |\n| --------- | ------------------------------ |\n| Documents | PDF, DOCX, XLSX, XLS, ODT, ODS |\n| Images    | JPEG, JPG, PNG, WebP, SVG      |\n| Text      | HTML, XML, JSON, CSV, TXT      |\n| Webpages  | Any publicly accessible URL    |\n\n## Limits\n\n- Max file size: 50 MB\n- Max URL content: 50 MB\n- Rate limit: 10,000 requests per hour\n- Free tier: 10 requests per day (no token required), within the service’s overall free quota\n- Paid tier: min $0.01 per conversion (USDC on Solana)\n- Token validity: 1 year\n\n## Authentication\n\n- Bearer: Authorization: Bearer TOKEN\n- Header: X-Token-Required: TOKEN\n\n## Token Activation\n\nBefore using a paid token, it must be activated:\n1. Receive 402 with NEW token+memo (pending created)\n2. Send USDC on Solana to wallet with memo from 402\n3. Retry request with EXACT token+memo from 402\n4. After activation: use token only\n\nImportant: The token+memo in 402 response MUST be used - old token or memo will be rejected.\n\n## Method Semantics\n\n| Method | Response Format | Notes                                     |\n| ------ | --------------- | ----------------------------------------- |\n| GET    | Markdown        | Always returns Markdown, including errors |\n| POST   | JSON            | Always returns JSON, including errors     |\n| Error  | Same as method  | GET errors = Markdown, POST errors = JSON |\n\n## GET / (Conversion)\n\nParameters: input, prompt, result, stream, token, memo\n- input: Auto-detected: starts with \"http(s)://\" → URL mode (fetched), starts with \"data:\" → file mode (data URI decoded), otherwise → text mode (processed as-is).\n- ⚠️ Browser URL limit: ~2048 chars. Use POST for long text/prompt values.\n- prompt: Custom LLM instructions\n- result: markdown|prompt|both (default: markdown)\n- stream: Enable streaming (boolean, default false) for SSE response\n- token: Access token\n- memo: Token activation memo\n\n## POST / (JSON body)\n\nReturns JSON with metadata. Accepts JSON body with \"input\" parameter.\nAuto-detection: starts with \"http://\" → URL, starts with \"data:\" → file, otherwise → text.\n\n## stream parameter\n\n- true (boolean): Returns SSE stream with real-time chunks\n- Token info sent as first message: {\"type\":\"token_info\",...}\n- Content sent as OpenAI-compatible chunks: {\"choices\":[{\"delta\":{\"content\":\"...\"},\"finish_reason\":null}]}\n- Final chunk: {\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}]}\n- End marker: data: [DONE]\n- Native framing per protocol: MCP streams notifications/message chunks, ACP streams session/update notification chunks, A2A streams result.task → artifactUpdate → statusUpdate frames (REST/OpenAI use the choices/delta format above)\n\n## result parameter\n\n- markdown: raw converted Markdown (default)\n- prompt: LLM result only (requires prompt)\n- both: markdown + prompt_result\n\n## prompt examples\n\n| Use Case           | Example                                                    |\n| ------------------ | ---------------------------------------------------------- |\n| Summarize          | `?input=https://example.com&prompt=Summarize`              |\n| Extract key points | `?input=Hello World&prompt=Extract key points`             |\n| Convert to JSON    | `?input=https://example.com&prompt=Convert to JSON format` |\n| Analyze content    | `?input=Hello World&prompt=Analyze and explain`            |\n\n## Response (POST)\n\n{\n  \"success\": true,\n  \"markdown\": \"# Converted content...\",\n  \"prompt_result\": \"... (when result=prompt/both)\",\n  \"token_status\": \"free|valid|invalid|expired|exhausted|expired_pending|activated|verification_error|invalid_payment|error|pending\",\n  \"token_balance\": 0.99,\n  \"token_expires\": 1798761600\n}\n\n## Error Codes\n\n| HTTP | code (stable)                                | Description                                      |\n| ---- | -------------------------------------------- | ------------------------------------------------ |\n| 200  | -                                            | Success                                          |\n| 400  | invalid_request / invalid_input              | Bad request                                      |\n| 401  | unauthorized / invalid_token / token_expired | Invalid token                                    |\n| 402  | payment_required                             | Payment required                                 |\n| 403  | blocked                                      | Blocked                                          |\n| 404  | not_found                                    | Not found                                        |\n| 413  | too_large / context_overflow                 | File too large or exceeds the LLM context window |\n| 415  | unsupported_format                           | Format not supported                             |\n| 429  | rate_limited / resource_limit                | Rate limit exceeded                              |\n| 500  | server_error                                 | Internal error                                   |\n| 503  | ai_unavailable                               | AI service unavailable                           |\n| 504  | timeout                                      | Timeout                                          |\n\n## Endpoints\n\n| Method | Path                                                        | Description                                                                 |\n| ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------- |\n| GET    | /about                                                      | About service                                                               |\n| GET    | /                                                           | API docs or conversion (via query parameter: `input`, `prompt`, `result`)   |\n| POST   | /                                                           | Convert content via JSON body (supports `input`, `prompt`, `result` params) |\n| POST   | /v1/chat/completions                                        | OpenAI-compatible endpoint                                                  |\n| GET    | /mcp                                                        | MCP server manifest                                                         |\n| POST   | /mcp                                                        | MCP RPC endpoint (discover, tools, resources, subscriptions)                |\n| POST   | /acp                                                        | ACP RPC endpoint (IDE agents)                                               |\n| POST   | /a2a                                                        | A2A RPC endpoint (agent2agent)                                              |\n| GET    | /health                                                     | Health check                                                                |\n| GET    | /llms.txt                                                   | API documentation                                                           |\n| GET    | /llms-full.txt                                              | Full API documentation                                                      |\n| GET    | /.well-known/ai-discovery.json or /ai-discovery.json        | AI discovery                                                                |\n| GET    | /.well-known/agent.json or /agent.json                      | AI Agent discovery                                                          |\n| GET    | /.well-known/agent-card.json or /agent-card.json            | A2A Agent card                                                              |\n| GET    | /.well-known/acp.json or /acp.json                          | ACP manifest                                                                |\n| GET    | /.well-known/x402.json or /x402.json                        | x402 payment manifest                                                       |\n| GET    | /.well-known/openapi.json or /openapi.json                  | OpenAPI specification (JSON)                                                |\n| GET    | /.well-known/openapi.yaml or /openapi.yaml                  | OpenAPI specification (YAML)                                                |\n| GET    | /.well-known/mapi.md or /mapi.md                            | MAPI specification (case-insensitive path MAPI.md support)                  |\n| GET    | /.well-known/skill.md or /skill.md                          | Skill specification (case-insensitive path SKILL.md support)                |\n| GET    | /.well-known/skills/index.json                              | Legacy skills index                                                         |\n| GET    | /.well-known/agent-skills/index.json                        | Agent Skills discovery (v0.2.0)                                             |\n| GET    | /.well-known/api-catalog                                    | API catalog (linkset+json, RFC draft)                                       |\n| GET    | /.well-known/mcp/server-cards.json                          | MCP server cards index                                                      |\n| GET    | /.well-known/plugin/plugin.json or /.well-known/plugin.json | Agent Plugins v1.0.0 manifest                                               |\n| GET    | /.well-known/plugin/mcp.json                                | Agent Plugins MCP config                                                    |\n| GET    | /.well-known/plugin/skills/mdapi-conversion/SKILL.md        | Agent Plugins conversion skill                                              |\n\n## Links\n\n- **About service:** https://mdapi.io/about\n- **API docs:** https://mdapi.io\n- **MCP server manifest:** https://mdapi.io/mcp\n- **Health check:** https://mdapi.io/health\n- **API documentation:** https://mdapi.io/llms.txt\n- **Full API documentation:** https://mdapi.io/llms-full.txt\n- **AI discovery:** https://mdapi.io/.well-known/ai-discovery.json                                        or https://mdapi.io/ai-discovery.json\n- **AI Agent discovery:** https://mdapi.io/.well-known/agent.json                                         or https://mdapi.io/agent.json\n- **A2A Agent card:** https://mdapi.io/.well-known/agent-card.json                                        or https://mdapi.io/agent-card.json\n- **ACP manifest:** https://mdapi.io/.well-known/acp.json                                                 or https://mdapi.io/acp.json\n- **x402 payment manifest:** https://mdapi.io/.well-known/x402.json                                       or https://mdapi.io/x402.json\n- **OpenAPI specification (JSON):** https://mdapi.io/.well-known/openapi.json                             or https://mdapi.io/openapi.json\n- **OpenAPI specification (YAML):** https://mdapi.io/.well-known/openapi.yaml                             or https://mdapi.io/openapi.yaml\n- **MAPI specification (case-insensitive path MAPI.md support):** https://mdapi.io/.well-known/mapi.md    or https://mdapi.io/mapi.md\n- **Skill specification (case-insensitive path SKILL.md support):** https://mdapi.io/.well-known/skill.md or https://mdapi.io/skill.md\n- **Agent Plugins package (agent-plugins.org v1.0.0):** https://mdapi.io/.well-known/plugin.json - portable manifest (plugin.json) + mcp.json + skills/mdapi-conversion/SKILL.md under https://mdapi.io/.well-known/plugin/\n\n## External Links\n\n- **github.com** https://github.com/mdapiio/mdapi.io\n- **skills.sh** https://www.skills.sh/mdapiio/mdapi.io\n- **skillsmp.com** https://skillsmp.com/creators/mdapiio/mdapi.io\n- **clawhub.ai** https://clawhub.ai/mdapiio\n- **x.com** https://x.com/mdapiio\n\n## Disclaimer\n\n**The service is provided \"AS IS\".**\n\n\n> mdapi.io is an edge-native service-transport primitive for AI, autonomous-agents, and the Web4 ecosystem.",
  "emergingProtocols": {
    "oauthProtectedResource": {
      "exists": false,
      "url": "https://mdapi.io/.well-known/oauth-protected-resource"
    },
    "oauthDiscovery": {
      "exists": false,
      "url": "https://mdapi.io/.well-known/oauth-authorization-server"
    },
    "mcpServerCard": {
      "exists": true,
      "url": "https://mdapi.io/.well-known/mcp/server-card.json",
      "draft": true,
      "name": null,
      "version": "1.0.0",
      "description": "Model Context Protocol server manifest for mdapi.io",
      "transport": null,
      "tools": 1,
      "resources": 15,
      "prompts": null
    },
    "a2aAgentCard": {
      "exists": true,
      "url": "https://mdapi.io/.well-known/agent-card.json",
      "name": "mdapi",
      "version": "1.0.0",
      "description": "Minimal Data API I/O: a content transformation layer primitive for AI systems. Transforms documents, images, and webpages into AI-ready Markdown and structured data, optimized for LLM efficiency and token usage.",
      "capabilities": 3,
      "skills": 7,
      "endpoint": null
    },
    "apiCatalog": {
      "exists": true,
      "url": "https://mdapi.io/.well-known/api-catalog",
      "contentType": "application/linkset+json; charset=utf-8",
      "validMediaType": true,
      "apis": 1
    },
    "agentSkills": {
      "exists": true,
      "url": "https://mdapi.io/.well-known/agent-skills/index.json",
      "draft": true,
      "schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
      "skills": 1,
      "names": [
        "mdapi-conversion"
      ]
    },
    "count": 4,
    "total": 6
  },
  "botAccess": {
    "probed": true,
    "bot": "OAI-SearchBot",
    "controlStatus": 200,
    "botStatus": 200,
    "discriminates": false,
    "refusedAsBot": false,
    "edge": "Cloudflare",
    "verifiable": false,
    "detail": "This origin answers OAI-SearchBot exactly as it answers any other client (200). No edge-level filtering of AI crawlers observed."
  },
  "snippets": [
    {
      "id": "fix_heading_hierarchy",
      "title": "Fix heading hierarchy",
      "description": "Your page has no <h1>. Every page needs exactly one <h1> as the main heading. Add it inside your <main> or <article>.",
      "language": "html",
      "code": "<h1>Your Page Title</h1>",
      "filename": "<main> or <article>"
    },
    {
      "id": "add_open_graph",
      "title": "Add missing Open Graph tags",
      "description": "Open Graph tags control how your page looks when shared on social media and how AI platforms preview your URL in answers.",
      "language": "html",
      "code": "<meta property=\"og:title\" content=\"Page Title\">\n<meta property=\"og:description\" content=\"Page description.\">\n<meta property=\"og:image\" content=\"https://yoursite.com/og-image.jpg\">\n<meta property=\"og:url\" content=\"https://mdapi.io\">\n<meta property=\"og:type\" content=\"website\">",
      "filename": "<head>",
      "stacks": [
        {
          "id": "html",
          "label": "HTML <head>",
          "language": "html",
          "filename": "<head>",
          "code": "<meta property=\"og:title\" content=\"Page Title\">\n<meta property=\"og:description\" content=\"Page description.\">\n<meta property=\"og:image\" content=\"https://yoursite.com/og-image.jpg\">\n<meta property=\"og:url\" content=\"https://mdapi.io\">\n<meta property=\"og:type\" content=\"website\">"
        },
        {
          "id": "wordpress",
          "label": "WordPress",
          "language": "php",
          "filename": "functions.php",
          "code": "<?php\n// Quick Open Graph tags without a plugin (skip if Yoast / Rank Math is active)\nadd_action('wp_head', function () {\n    if (!is_singular()) return;\n    $post = get_queried_object();\n    $title = get_the_title($post);\n    $desc  = get_the_excerpt($post) ?: wp_trim_words(strip_tags($post->post_content), 30);\n    $image = get_the_post_thumbnail_url($post, 'large') ?: 'https://yoursite.com/og-image.jpg';\n    $url   = get_permalink($post);\n    printf('<meta property=\"og:title\" content=\"%s\">' . \"\\n\", esc_attr($title));\n    printf('<meta property=\"og:description\" content=\"%s\">' . \"\\n\", esc_attr($desc));\n    printf('<meta property=\"og:image\" content=\"%s\">' . \"\\n\", esc_url($image));\n    printf('<meta property=\"og:url\" content=\"%s\">' . \"\\n\", esc_url($url));\n    echo '<meta property=\"og:type\" content=\"article\">' . \"\\n\";\n}, 5);"
        },
        {
          "id": "nextjs",
          "label": "Next.js",
          "language": "typescript",
          "filename": "app/page.tsx",
          "code": "// Next.js App Router — Metadata API\nimport type { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n  title: \"Page Title\",\n  description: \"Page description.\",\n  openGraph: {\n    title: \"Page Title\",\n    description: \"Page description.\",\n    url: \"https://mdapi.io\",\n    images: [\"https://yoursite.com/og-image.jpg\"],\n    type: 'website',\n  },\n};"
        }
      ]
    },
    {
      "id": "add_meta_description",
      "title": "Add meta description",
      "description": "A good meta description (50-160 characters) helps AI agents understand your page quickly.",
      "language": "html",
      "code": "<meta name=\"description\" content=\"A brief description of this page.\">",
      "filename": "<head>"
    },
    {
      "id": "add_canonical_url",
      "title": "Add canonical URL",
      "description": "The canonical URL tells AI agents which version of the page is the \"official\" one, avoiding duplicate content issues.",
      "language": "html",
      "code": "<link rel=\"canonical\" href=\"https://mdapi.io\">",
      "filename": "<head>"
    },
    {
      "id": "add_schema_org",
      "title": "Add Schema.org JSON-LD",
      "description": "Structured data helps AI agents understand the type, author, and purpose of your content.",
      "language": "html",
      "code": "<script type=\"application/ld+json\">\n{\n  \"@context\": \"https://schema.org\",\n  \"@type\": \"WebPage\",\n  \"name\": \"Page Title\",\n  \"description\": \"Page description.\",\n  \"url\": \"https://mdapi.io\",\n  \"inLanguage\": \"en\"\n}\n</script>",
      "filename": "<head>"
    },
    {
      "id": "allow_ai_bots",
      "title": "Allow AI bots in robots.txt",
      "description": "Some AI bots are blocked in your robots.txt. Remove or modify these Disallow rules to let AI agents index your content.",
      "language": "txt",
      "code": "# Add to your robots.txt — explicitly allow AI bots:\nUser-agent: GPTBot\nAllow: /\n\nUser-agent: ClaudeBot\nAllow: /\n\nUser-agent: PerplexityBot\nAllow: /",
      "filename": "/robots.txt"
    },
    {
      "id": "add_article_main",
      "title": "Wrap content in <main> and <article>",
      "description": "Semantic HTML landmarks help AI agents identify the main content of your page.",
      "language": "html",
      "code": "<main>\n  <article>\n    <h1>Your Page Title</h1>\n    <p>Your content here...</p>\n  </article>\n</main>",
      "filename": "<body>"
    },
    {
      "id": "add_markdown_negotiation",
      "title": "Support Markdown for Agents",
      "description": "Let AI agents request a clean Markdown version of any page via content negotiation, .md alternate URLs, link tags or Link headers.",
      "language": "html",
      "code": "<!-- Mechanism 3: link tag advertising the .md alternate -->\n<link rel=\"alternate\" type=\"text/markdown\" href=\"/page.md\">",
      "filename": "<head>",
      "stacks": [
        {
          "id": "html",
          "label": "HTML <head>",
          "language": "html",
          "filename": "<head>",
          "code": "<!-- Mechanism 3: link tag advertising the .md alternate -->\n<link rel=\"alternate\" type=\"text/markdown\" href=\"/page.md\">"
        },
        {
          "id": "express",
          "label": "Express",
          "language": "javascript",
          "filename": "server.js",
          "code": "// Mechanisms 1 + 4: content negotiation + Link header\napp.get('/page', (req, res) => {\n  res.setHeader('Vary', 'Accept');\n  res.setHeader('Link', '</page.md>; rel=\"alternate\"; type=\"text/markdown\"');\n  if ((req.headers.accept || '').includes('text/markdown')) {\n    res.type('text/markdown; charset=utf-8');\n    return res.send(renderMarkdown('page'));\n  }\n  res.render('page');\n});"
        },
        {
          "id": "fastify",
          "label": "Fastify",
          "language": "javascript",
          "filename": "server.js",
          "code": "// Mechanisms 1 + 4: content negotiation + Link header\nfastify.get('/page', async (req, reply) => {\n  reply.header('Vary', 'Accept');\n  reply.header('Link', '</page.md>; rel=\"alternate\"; type=\"text/markdown\"');\n  if ((req.headers.accept || '').includes('text/markdown')) {\n    return reply.type('text/markdown; charset=utf-8').send(renderMarkdown('page'));\n  }\n  return reply.view('/page.ejs');\n});"
        },
        {
          "id": "nextjs",
          "label": "Next.js",
          "language": "typescript",
          "filename": "app/page/route.ts",
          "code": "// Next.js App Router — Route Handler returning Markdown\nimport { NextRequest } from 'next/server';\nimport { renderMarkdown } from '@/lib/md';\nexport async function GET(req: NextRequest) {\n  const accept = req.headers.get('accept') || '';\n  if (accept.includes('text/markdown')) {\n    return new Response(await renderMarkdown('page'), {\n      headers: {\n        'Content-Type': 'text/markdown; charset=utf-8',\n        'Vary': 'Accept',\n      },\n    });\n  }\n  // Fall through to the page component\n  return new Response(null, { status: 404 });\n}"
        },
        {
          "id": "wordpress",
          "label": "WordPress",
          "language": "php",
          "filename": "functions.php",
          "code": "<?php\n// Mechanism 1: respond to Accept: text/markdown on the same URL\nadd_action('template_redirect', function () {\n    if (!is_singular()) return;\n    $accept = $_SERVER['HTTP_ACCEPT'] ?? '';\n    if (strpos($accept, 'text/markdown') === false) return;\n    header('Content-Type: text/markdown; charset=utf-8');\n    header('Vary: Accept');\n    $post = get_queried_object();\n    echo \"# \" . get_the_title($post) . \"\\n\\n\";\n    echo wp_strip_all_tags(apply_filters('the_content', $post->post_content));\n    exit;\n});"
        },
        {
          "id": "static",
          "label": "Hugo / Jekyll / Astro",
          "language": "txt",
          "filename": "static/page.md",
          "code": "# Mechanism 2: serve .md alongside .html\n# Hugo: place page.md in /static/ — built unchanged\n# Jekyll: drop page.md in /assets/ — copied as-is\n# Astro: src/pages/page.md.ts that exports a GET returning markdown\n\n# Then advertise with mechanism 3 in <head>:\n#   <link rel=\"alternate\" type=\"text/markdown\" href=\"/page.md\">"
        }
      ]
    }
  ]
}

Nutzen Sie unsere API, um dies programmatisch abzurufen (kommt bald)

Dieses JSON ist für den internen Gebrauch bestimmt — im Gegensatz zu den Markdown- und llms.txt-Dateien soll es nicht auf Ihre Website hochgeladen werden. Speichern Sie es als Ausgangswert, um Ihren Score im Zeitverlauf zu verfolgen, teilen Sie es mit Ihrem Entwicklerteam oder integrieren Sie es in Ihre CI/CD-Pipeline.

Teilen Sie Ihre Ergebnisse

Oder lass dir von einer KI beim Verbessern helfen

Eine zweite Meinung?

Cloudflare betreibt ebenfalls einen kostenlosen Scanner, und er stellt eine andere Frage. Er bewertet, was Ihre Website für Agenten zum Aufrufen veröffentlicht: MCP Server Cards, Agent Skills, API-Kataloge, DNS-Einträge. Wir bewerten, ob ein Agent Ihre Inhalte erreichen, lesen und verstehen kann. Eine Website kann bei dem einen gut und bei dem anderen schlecht abschneiden — rechnen Sie mit zwei verschiedenen Zahlen: Es sind Antworten auf zwei verschiedene Fragen, und beide lohnen sich.

mdapi.io mit Cloudflare scannen

Badge einbetten

Fügen Sie dieses Badge zu Ihrer Website hinzu. Es aktualisiert sich automatisch, wenn sich Ihr KI-Bereitschafts-Score ändert.

AgentReady.md score for mdapi.io
Script Empfohlen
<script src="https://agentready.md/badge.js" data-id="20ca3000-2a3c-4b2d-86c0-6c897f642cde" data-domain="mdapi.io"></script>
Markdown
[![AgentReady.md score for mdapi.io](https://agentready.md/badge/mdapi.io.svg)](https://agentready.md/de/r/mdapi.io)

Demnächst: Vollständige Domain-Analyse

Crawlen Sie Ihre gesamte Domain, generieren Sie llms.txt und überwachen Sie Ihren KI-Bereitschaftswert im Zeitverlauf. Tragen Sie sich in die Warteliste ein.

Sie stehen auf der Liste! Wir benachrichtigen Sie, sobald es verfügbar ist.