已验证的 AgentReady.md 证书
签发于 sig: 97d565df0ca4e493 验证 →

已分析URL

https://mdapi.io

测量于23小时前

分析另一个URL

AI-Ready评分

较差

/ 100

Token节省量

HTML Token 13.428
Markdown Token 13.397
节省 0%

评分详情

可访问性 75/100
AI可发现性 46/100
结构化数据 0/100
语义化HTML 35/100
内容效率 60/100

新兴协议

已检测到 4/6

AI代理查找的well-known端点。检测到意味着代理可以自动发现并连接到您的服务。

  • 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

分数不会自己保持不变。 监控功能尚未上线——加入名单,上线时通知你。

您已加入名单!服务上线时我们会通知您。

我们测到的 5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot

您的robots.txt阻止了部分AI机器人(GPTBot、ClaudeBot、PerplexityBot)。这会阻止AI代理索引您的内容。

如何实施

更新robots.txt以允许AI机器人。为GPTBot、ClaudeBot和PerplexityBot添加明确的Allow规则。

粘贴到编码智能体中让它完成修复

我们测到的 Main content starts at 100% of HTML

主要内容出现在HTML文档的后部。AI代理可能对较早出现的内容赋予更高权重。

如何实施

重构HTML,使<main>或<article>内容出现在侧边栏和补充内容之前。

粘贴到编码智能体中让它完成修复

我们测到的 No JSON-LD / Schema.org found

未找到Schema.org结构化数据。JSON-LD帮助AI代理从页面中提取基于事实的结构化信息。

如何实施

添加包含Schema.org标记的<script type="application/ld+json">块。使用适当的类型:博客文章用Article,产品页面用Product,公司页面用Organization。

粘贴到编码智能体中让它完成修复

我们测到的 No headings found

您的标题结构存在问题(跳级或多个h1标签)。清晰的层级帮助AI代理理解内容组织。

如何实施

确保每页只有一个<h1>,标题按顺序排列:h1 > h2 > h3。不要跳级(例如从h1直接到h3)。

粘贴到编码智能体中让它完成修复

我们测到的 0/3 OG tags present

Open Graph标签缺失或不完整。OG标签帮助AI代理(和社交平台)理解页面的标题、描述和图片。

如何实施

在页面<head>中添加og:title、og:description和og:image meta标签。

粘贴到编码智能体中让它完成修复

我们测到的 No meta description

未找到meta描述。此标签为AI代理和搜索引擎提供页面的简洁摘要。

如何实施

添加包含150-160字符页面内容描述的<meta name="description" content="...">标签。

粘贴到编码智能体中让它完成修复

我们测到的 0% token reduction (HTML→Markdown)

HTML转Markdown的Token减少比率较低。您的页面相对于内容有过多的标记。

如何实施

删除不必要的标记、内联样式和表现性元素。保持以内容为中心的HTML,使其能干净地转换为Markdown。

粘贴到编码智能体中让它完成修复

我们测到的 Missing <article> and <main> elements

您的页面未使用<article>或<main>元素。这些语义容器帮助AI代理识别主要内容区域,忽略导航、侧边栏和页脚。

如何实施

在页面主要内容周围添加<main>元素,并对博客文章或产品描述等独立内容块使用<article>。

粘贴到编码智能体中让它完成修复

我们测到的 CDN level (Cloudflare) — Content negotiation

你的站点只实现了 Markdown for Agents 的一部分——上面的“我们测了什么”列出了你已经具备的机制。补上其余部分,可以让更多智能体直接请求 markdown 而不是解析 HTML,把 token 用量降低约 80%。

如何实施

  • 在第二个网址上提供 Markdown 孪生页,例如 /page.md 与 /page 并存。
  • 在 HTML head 中加入 <link rel="alternate" type="text/markdown" href="…">。
  • 加一个指向 Markdown 版本的 Link 响应头:Link: <…>; rel="alternate"; type="text/markdown"。
粘贴到编码智能体中让它完成修复

我们测到的 No canonical URL

未找到规范URL。它帮助AI代理识别页面的首选版本并避免重复内容。

如何实施

添加指向页面规范URL的<link rel="canonical" href="...">标签。

粘贴到编码智能体中让它完成修复
Markdown Token: 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.

将此文件上传到服务器的/index.md,以便AI代理可以访问页面的干净版本。您也可以配置Accept: text/markdown内容协商以自动提供。

我们的建议

下载llms.txt
# mdapi.io

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

完整llms.txt需要全域分析(即将推出)

将此文件上传到域名根目录的https://mdapi.io/llms.txt。ChatGPT、Claude和Perplexity等AI代理会检查此文件以了解您的网站结构。

该网站已有llms.txt文件。

格式有效
# 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.

可访问性

无需JavaScript即可获取内容 (100/100)

Content available without JavaScript

内容在HTML中位置靠前 (0/100)

Main content starts at 100% of HTML

合理的页面大小 (100/100)

Page size: 58KB

AI可发现性

robots.txt允许AI机器人 (0/100)

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

Markdown for Agents支持 (25/100) Cloudflare CDN
✓ Accept: text/markdown ✗ .md URL ✗ <link> tag ✗ Link header
有sitemap.xml (100/100)

Sitemap found

有robots.txt文件 (100/100)

robots.txt exists

有llms.txt文件 (100/100)

llms.txt exists and is valid

有Content-Signal(robots.txt或HTTP标头) (60/100)
✓ robots.txt ✗ HTTP header ✗ Policy

结构化数据

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

No JSON-LD / Schema.org found

有Open Graph标签 (0/100)

0/3 OG tags present

有meta描述 (0/100)

No meta description

有规范URL (0/100)

No canonical URL

有lang属性 (0/100)

No lang attribute on <html>

语义化HTML

正确的标题层级 (0/100)

No headings found

使用article或main元素 (0/100)

Missing <article> and <main> elements

使用语义化HTML元素 (50/100)

No divs or semantic elements found

有意义的图片alt属性 (100/100)

No images found

较低的div嵌套深度 (100/100)

No divs found

内容效率

良好的Token减少比率 (0/100)

0% token reduction (HTML→Markdown)

良好的内容与噪声比 (100/100)

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

合理的页面重量 (100/100)

HTML size: 58KB

最少的内联样式 (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\">"
        }
      ]
    }
  ]
}

使用我们的API以编程方式获取此内容(即将推出)

此JSON供内部使用 — 与Markdown和llms.txt文件不同,它不适合上传到您的网站。将其保存为基准值以跟踪评分变化,与开发团队共享,或集成到CI/CD流水线中。

想听听第二种意见?

Cloudflare 也提供免费扫描工具,但它问的是另一个问题。他们评分的是你的站点为智能体调用而发布的东西:MCP server card、Agent Skills、API 目录、DNS 记录。我们评分的是智能体能否访问、读取并理解你的内容。一个站点可能在其中一项表现很好、另一项很差,所以看到两个不同的分数很正常:它们是两个不同问题的答案,都值得了解。

用 Cloudflare 扫描 mdapi.io

嵌入您的徽章

将此徽章添加到您的网站。当您的 AI 就绪评分发生变化时,它会自动更新。

AgentReady.md score for mdapi.io
Script 推荐
<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/zh/r/mdapi.io)

即将推出:全域分析

爬取您的整个域名,生成llms.txt,并随时间监控您的AI就绪度评分。加入等候名单以获取通知。

您已加入名单!服务上线时我们会通知您。