URL analisado
https://mdapi.io
Medido há 23 horas
Pontuação AI-Ready
Fraco
de 100
Poupança de tokens
Desdobramento da pontuação
Protocolos emergentes
4 de 6 detetadosEndpoints well-known que os agentes de IA procuram. Detetado significa que um agente pode descobrir e conectar-se automaticamente ao seu serviço.
-
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
Isto não fica quieto. A monitorização ainda não existe: junte-se à lista e avisamos quando estiver.
Está na lista! Notificá-lo-emos quando estiver disponível.
O que medimos 5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot
O seu robots.txt bloqueia alguns bots de IA (GPTBot, ClaudeBot, PerplexityBot). Isto impede os agentes de IA de indexar o seu conteúdo.
Como implementar
Atualize o seu robots.txt para permitir bots de IA. Adicione regras Allow explícitas para GPTBot, ClaudeBot e PerplexityBot.
O que medimos Main content starts at 100% of HTML
O conteúdo principal aparece tarde no documento HTML. Os agentes de IA podem dar mais peso a conteúdo que aparece mais cedo.
Como implementar
Reestruture o seu HTML para que o conteúdo <main> ou <article> apareça antes das barras laterais e conteúdo suplementar.
O que medimos No JSON-LD / Schema.org found
Não foram encontrados dados estruturados Schema.org. JSON-LD ajuda os agentes de IA a extrair informação factual e estruturada das suas páginas.
Como implementar
Adicione um bloco <script type="application/ld+json"> com marcação Schema.org. Use tipos apropriados: Article para publicações de blog, Product para páginas de produtos, Organization para a página da sua empresa.
O que medimos No headings found
A sua estrutura de títulos tem problemas (níveis saltados ou múltiplos h1). Uma hierarquia limpa ajuda os agentes de IA a compreender a organização do conteúdo.
Como implementar
Certifique-se de que tem exatamente um <h1> por página e os títulos seguem uma ordem sequencial: h1 > h2 > h3. Não salte níveis (ex.: h1 diretamente para h3).
O que medimos 0/3 OG tags present
Tags Open Graph em falta ou incompletas. As tags OG ajudam os agentes de IA (e plataformas sociais) a compreender o título, descrição e imagem da sua página.
Como implementar
Adicione meta tags og:title, og:description e og:image ao <head> da sua página.
O que medimos No meta description
Nenhuma meta descrição encontrada. Esta tag fornece um resumo conciso da página para agentes de IA e motores de busca.
Como implementar
Adicione uma tag <meta name="description" content="..."> com uma descrição de 150-160 caracteres do conteúdo da página.
O que medimos 0% token reduction (HTML→Markdown)
A conversão HTML-para-Markdown mostra um baixo rácio de redução de tokens. A sua página tem markup excessivo em relação ao conteúdo.
Como implementar
Remova markup desnecessário, estilos inline e elementos de apresentação. Mantenha HTML focado no conteúdo que converta limpo para Markdown.
O que medimos Missing <article> and <main> elements
A sua página não utiliza elementos <article> ou <main>. Estes contentores semânticos ajudam os agentes de IA a identificar a área de conteúdo principal e ignorar navegação, barras laterais e rodapés.
Como implementar
Adicione um elemento <main> à volta do conteúdo principal da sua página e use <article> para blocos de conteúdo autónomos como publicações de blog ou descrições de produtos.
O que medimos CDN level (Cloudflare) — Content negotiation
O teu site só suporta parte do Markdown para Agentes — em «o que medimos», acima, estão os mecanismos que já tens. Acrescentar os restantes permite que mais agentes peçam markdown em vez de analisar HTML, reduzindo os tokens em ~80%.
Como implementar
- Serve um gémeo Markdown num segundo URL, por ex. /pagina.md ao lado de /pagina.
- Acrescenta <link rel="alternate" type="text/markdown" href="…"> no head do HTML.
- Acrescenta um cabeçalho HTTP Link a apontar para a versão Markdown: Link: <…>; rel="alternate"; type="text/markdown".
O que medimos No canonical URL
Nenhum URL canónico encontrado. Isto ajuda os agentes de IA a identificar a versão preferida de uma página e evitar conteúdo duplicado.
Como implementar
Adicione uma tag <link rel="canonical" href="..."> apontando para o URL canónico da página.
\# 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.
Carregue este ficheiro como /index.md no seu servidor para que os agentes de IA possam aceder a uma versão limpa da sua página. Também pode configurar a negociação de conteúdo Accept: text/markdown para o servir automaticamente.
A nossa recomendação
# mdapi.io ## Main - [Home](https://mdapi.io)
O llms.txt completo requer análise de todo o domínio (em breve)
Carregue este ficheiro como https://mdapi.io/llms.txt na raiz do seu domínio. Agentes de IA como ChatGPT, Claude e Perplexity verificam este ficheiro para compreender a estrutura do seu site.
Este site já possui um ficheiro llms.txt.
Formato válido# 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.
Acessibilidade
Content available without JavaScript
Main content starts at 100% of HTML
Page size: 58KB
Descobribilidade IA
5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot
Sitemap found
robots.txt exists
llms.txt exists and is valid
Dados Estruturados
No JSON-LD / Schema.org found
0/3 OG tags present
No meta description
No canonical URL
No lang attribute on <html>
HTML Semântico
No headings found
Missing <article> and <main> elements
No divs or semantic elements found
No images found
No divs found
Eficiência de conteúdo
0% token reduction (HTML→Markdown)
Content ratio: 99.5% (59274 content chars / 59583 HTML bytes)
HTML size: 58KB
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\">"
}
]
}
]
}
Use a nossa API para obter isto programaticamente (em breve)
Este JSON é para uso interno — ao contrário dos ficheiros Markdown e llms.txt, não se destina a ser carregado no seu site. Guarde-o como referência para acompanhar a sua pontuação ao longo do tempo, partilhe-o com a sua equipa de desenvolvimento ou integre-o no seu pipeline CI/CD.
Partilhe os seus resultados
Ou peça a uma IA que o ajude a melhorar
Quer uma segunda opinião?
A Cloudflare também tem um scanner gratuito, e faz uma pergunta diferente. Eles pontuam o que o seu site publica para os agentes invocarem: MCP server cards, Agent Skills, catálogos de API, registos DNS. Nós pontuamos se um agente consegue chegar ao seu conteúdo, lê-lo e compreendê-lo. Um site pode ir bem num e mal no outro, por isso conte com dois números diferentes: são respostas a duas perguntas diferentes, e vale a pena conhecer ambas.
Analisar mdapi.io com a CloudflareIncorpore o seu badge
Adicione este badge ao seu site. Atualiza automaticamente quando a sua pontuação de prontidão para IA mudar.
<script src="https://agentready.md/badge.js" data-id="20ca3000-2a3c-4b2d-86c0-6c897f642cde" data-domain="mdapi.io"></script>
[](https://agentready.md/pt/r/mdapi.io)
Em breve: Análise completa de domínio
Rastreie todo o seu domínio, gere llms.txt e monitorize a sua pontuação de prontidão para IA ao longo do tempo. Inscreva-se na lista de espera.
Está na lista! Notificá-lo-emos quando estiver disponível.