URL analizzato
https://mdapi.io
Misurato 23 ore fa
Punteggio AI-Ready
Scarso
su 100
Risparmio di token
Dettaglio del punteggio
Protocolli emergenti
4 di 6 rilevatiEndpoint well-known cercati dagli agenti IA. Rilevato significa che un agente può scoprire e connettersi automaticamente al tuo servizio.
-
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
Non resta fermo da solo. Il monitoraggio non esiste ancora: iscriviti e ti avvisiamo quando c'è.
Sei nella lista! Ti avviseremo quando sarà disponibile.
Cosa abbiamo misurato 5/5 AI search bots blocked: OAI-SearchBot, Claude-SearchBot, PerplexityBot, DuckAssistBot, Amazonbot
Il tuo robots.txt blocca alcuni bot IA (GPTBot, ClaudeBot, PerplexityBot). Questo impedisce agli agenti IA di indicizzare i tuoi contenuti.
Come implementare
Aggiorna il tuo robots.txt per consentire i bot IA. Aggiungi regole Allow esplicite per GPTBot, ClaudeBot e PerplexityBot.
Cosa abbiamo misurato Main content starts at 100% of HTML
Il contenuto principale appare tardi nel documento HTML. Gli agenti IA potrebbero dare più peso al contenuto che appare prima.
Come implementare
Ristruttura il tuo HTML in modo che il contenuto <main> o <article> appaia prima delle barre laterali e del contenuto supplementare.
Cosa abbiamo misurato No JSON-LD / Schema.org found
Nessun dato strutturato Schema.org trovato. JSON-LD aiuta gli agenti IA a estrarre informazioni fattuali e strutturate dalle tue pagine.
Come implementare
Aggiungi un blocco <script type="application/ld+json"> con markup Schema.org. Usa tipi appropriati: Article per post del blog, Product per pagine prodotto, Organization per la pagina aziendale.
Cosa abbiamo misurato No headings found
La struttura delle intestazioni presenta problemi (livelli saltati o tag h1 multipli). Una gerarchia pulita aiuta gli agenti IA a comprendere l'organizzazione dei contenuti.
Come implementare
Assicurati di avere esattamente un <h1> per pagina e che le intestazioni seguano un ordine sequenziale: h1 > h2 > h3. Non saltare livelli (es. da h1 direttamente a h3).
Cosa abbiamo misurato 0/3 OG tags present
Tag Open Graph mancanti o incompleti. I tag OG aiutano gli agenti IA (e le piattaforme social) a comprendere titolo, descrizione e immagine della tua pagina.
Come implementare
Aggiungi i meta tag og:title, og:description e og:image nel <head> della tua pagina.
Cosa abbiamo misurato No meta description
Nessuna meta descrizione trovata. Questo tag fornisce un riassunto conciso della pagina per agenti IA e motori di ricerca.
Come implementare
Aggiungi un tag <meta name="description" content="..."> con una descrizione di 150-160 caratteri del contenuto della pagina.
Cosa abbiamo misurato 0% token reduction (HTML→Markdown)
La conversione HTML-a-Markdown mostra un basso rapporto di riduzione token. La tua pagina ha markup eccessivo rispetto al contenuto.
Come implementare
Rimuovi markup non necessario, stili inline ed elementi presentazionali. Mantieni HTML focalizzato sul contenuto che si converta in modo pulito in Markdown.
Cosa abbiamo misurato Missing <article> and <main> elements
La tua pagina non utilizza elementi <article> o <main>. Questi contenitori semantici aiutano gli agenti IA a identificare l'area del contenuto principale e ignorare navigazione, barre laterali e piè di pagina.
Come implementare
Aggiungi un elemento <main> attorno al contenuto principale della tua pagina e usa <article> per blocchi di contenuto autonomi come post del blog o descrizioni di prodotti.
Cosa abbiamo misurato CDN level (Cloudflare) — Content negotiation
Il tuo sito supporta Markdown per Agenti solo in parte: in «cosa abbiamo misurato», sopra, ci sono i meccanismi che hai già. Aggiungere gli altri permette a più agenti di chiedere markdown invece di analizzare HTML, riducendo i token del ~80%.
Come implementare
- Servi un gemello Markdown su una seconda URL, ad es. /pagina.md accanto a /pagina.
- Aggiungi <link rel="alternate" type="text/markdown" href="…"> nell'head HTML.
- Aggiungi un header HTTP Link che punti alla versione Markdown: Link: <…>; rel="alternate"; type="text/markdown".
Cosa abbiamo misurato No canonical URL
Nessun URL canonico trovato. Questo aiuta gli agenti IA a identificare la versione preferita di una pagina ed evitare contenuti duplicati.
Come implementare
Aggiungi un tag <link rel="canonical" href="..."> che punti all'URL canonico della pagina.
\# 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.
Carica questo file come /index.md sul tuo server affinché gli agenti IA possano accedere a una versione pulita della tua pagina. Puoi anche configurare la negoziazione dei contenuti Accept: text/markdown per servirlo automaticamente.
La nostra raccomandazione
# mdapi.io ## Main - [Home](https://mdapi.io)
Il llms.txt completo richiede un'analisi dell'intero dominio (prossimamente)
Carica questo file come https://mdapi.io/llms.txt nella radice del tuo dominio. Agenti IA come ChatGPT, Claude e Perplexity controllano questo file per comprendere la struttura del tuo sito.
Questo sito ha già un file llms.txt.
Formato valido# 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.
Accessibilità
Content available without JavaScript
Main content starts at 100% of HTML
Page size: 58KB
Scopribilità 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
Dati Strutturati
No JSON-LD / Schema.org found
0/3 OG tags present
No meta description
No canonical URL
No lang attribute on <html>
HTML Semantico
No headings found
Missing <article> and <main> elements
No divs or semantic elements found
No images found
No divs found
Efficienza dei contenuti
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\">"
}
]
}
]
}
Usa la nostra API per ottenere questo programmaticamente (prossimamente)
Questo JSON è per uso interno — a differenza dei file Markdown e llms.txt, non è destinato ad essere caricato sul tuo sito. Salvalo come riferimento per monitorare il tuo punteggio nel tempo, condividilo con il tuo team di sviluppo o integralo nella tua pipeline CI/CD.
Condividi i tuoi risultati
Oppure chiedi a un'IA di aiutarti a migliorarlo
Vuoi un secondo parere?
Anche Cloudflare ha uno scanner gratuito, e pone una domanda diversa. Loro valutano ciò che il tuo sito pubblica perché gli agenti lo invochino: MCP server card, Agent Skills, cataloghi di API, record DNS. Noi valutiamo se un agente riesce a raggiungere i tuoi contenuti, leggerli e capirli. Un sito può andare bene in uno e male nell'altro, quindi aspettati due numeri diversi: sono risposte a due domande diverse, ed entrambe vale la pena conoscerle.
Analizza mdapi.io con CloudflareIncorpora il tuo badge
Aggiungi questo badge al tuo sito. Si aggiorna automaticamente quando cambia il tuo punteggio di prontezza IA.
<script src="https://agentready.md/badge.js" data-id="20ca3000-2a3c-4b2d-86c0-6c897f642cde" data-domain="mdapi.io"></script>
[](https://agentready.md/it/r/mdapi.io)
Prossimamente: Analisi completa del dominio
Scansiona l'intero dominio, genera llms.txt e monitora il tuo punteggio di prontezza IA nel tempo. Iscriviti alla lista d'attesa.
Sei nella lista! Ti avviseremo quando sarà disponibile.