AnyMD's document to Markdown API works from any language that speaks HTTP. Python, Node.js, Rust, Go, Java, Ruby, PHP, C#, or curl from the terminal — the same endpoint, the same authentication, the same clean Markdown output. No SDK to install, no language-specific binding to maintain, no client library going out of date.
This post walks through the universal approach: one REST API, one authentication header, 15+ input formats, and every major programming language ready to use it in under 60 seconds.
The API contract
Every conversion hits the same endpoint:
POST https://anymd.net/api/convert
Authorization: Bearer <your-api-key>
Content-Type: multipart/form-data
Body: file=@document.pdf
Response: GitHub-Flavored Markdown as the plain-text body. HTTP 200 means success, 4xx/5xx means failure with a JSON error message. That's it. One contract, 14 languages, zero ceremony.
curl — the universal client
If you can open a terminal, you can convert documents:
curl -X POST https://anymd.net/api/convert \
-H "Authorization: Bearer ***" \
-F "file=@report.pdf" \
-o report.md
That's a production-grade conversion in a single line. No package manager, no runtime, no import. The shell handles streaming, error codes, and file I/O natively.
Python — httpx (async)
Python developers get the richest SDK ecosystem. Here's the async version with httpx:
import httpx
async def convert_doc(file_path: str, api_key: str) -> str:
async with httpx.AsyncClient() as client:
with open(file_path, "rb") as f:
resp = await client.post(
"https://anymd.net/api/convert",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": f},
)
resp.raise_for_status()
return resp.text
# Usage
markdown = await convert_doc("paper.pdf", "sk-...")
print(markdown[:500])
For synchronous code, requests works the same way — just swap async with for with and remove await.
| Library | Lines | Async? | Install |
|---|---|---|---|
httpx | 12 | Yes | pip install httpx |
requests | 10 | No | pip install requests |
aiohttp | 14 | Yes | pip install aiohttp |
See the dedicated Python tutorial for batch processing, pagination, and error handling patterns.
Node.js — fetch (native)
Node.js 18+ ships with fetch built in. No dependencies needed:
import { readFileSync } from "node:fs";
import { Blob } from "node:buffer";
async function convertDoc(filePath, apiKey) {
const file = readFileSync(filePath);
const form = new FormData();
form.set("file", new Blob([file]));
const resp = await fetch("https://anymd.net/api/convert", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: form,
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
throw new Error(err.error || `HTTP ${resp.status}`);
}
return await resp.text();
}
// Usage
const md = await convertDoc("deck.pptx", "sk-...");
console.log(md);
TypeScript users get autocompletion on the response — the API returns text/plain; charset=utf-8 with Markdown content. The full Node.js guide covers Express middleware, streaming, and rate limiting.
Rust — reqwest
Rust clients paired with AnyMD's Rust server achieve the lowest end-to-end latency. The tokio runtime makes concurrent batch conversions seamless:
use reqwest::Client;
use std::path::Path;
use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};
async fn convert_doc(path: &Path, api_key: &str) -> anyhow::Result<String> {
let file = File::open(path).await?;
let stream = FramedRead::new(file, BytesCodec::new());
let body = reqwest::Body::wrap_stream(stream);
let client = Client::new();
let resp = client
.post("https://anymd.net/api/convert")
.header("Authorization", format!("Bearer {api_key}"))
.multipart(
reqwest::multipart::Part::stream(body)
.file_name(path.to_string_lossy().to_string()),
)
.send()
.await?;
let status = resp.status();
let text = resp.text().await?;
if !status.is_success() {
anyhow::bail!("HTTP {status}: {text}");
}
Ok(text)
}
The Rust deep-dive includes reqwest connection pooling benchmarks and server-side streaming patterns.
Go — net/http
Go's standard library handles everything. A clean, context-aware client:
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func convertDoc(filePath, apiKey string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", err
}
defer file.Close()
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
part, _ := w.CreateFormFile("file", filePath)
io.Copy(part, file)
w.Close()
req, _ := http.NewRequest("POST",
"https://anymd.net/api/convert", &buf)
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, body)
}
return string(body), nil
}
The Go tutorial adds retry logic, connection pooling, and shared client patterns for production use.
Java — HttpClient (JDK 11+)
Modern Java needs no external dependencies. The built-in HttpClient handles multipart:
import java.net.URI;
import java.net.http.*;
import java.nio.file.Files;
import java.nio.file.Path;
public class AnyMDClient {
static String convertDoc(String filePath, String apiKey) throws Exception {
var boundary = "---boundary---";
var fileBytes = Files.readAllBytes(Path.of(filePath));
var body = HttpRequest.BodyPublishers.ofByteArray(
("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" +
Path.of(filePath).getFileName() + "\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n").getBytes()
);
body = HttpRequest.BodyPublishers.concat(
body,
HttpRequest.BodyPublishers.ofByteArray(fileBytes),
HttpRequest.BodyPublishers.ofString("\r\n--" + boundary + "--\r\n")
);
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://anymd.net/api/convert"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(body)
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("HTTP " + response.statusCode() + ": " + response.body());
}
return response.body();
}
}
Ruby — Net::HTTP
Ruby's standard library with the net-http and uri gems (both built-in):
require "net/http"
require "uri"
def convert_doc(file_path, api_key)
uri = URI("https://anymd.net/api/convert")
file = File.open(file_path)
request = Net::HTTP::Post::Multipart.new(
uri,
{ "file" => UploadIO.new(file, "application/octet-stream", File.basename(file_path)) },
{ "Authorization" => "Bearer #{api_key}" }
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "HTTP #{response.code}: #{response.body}" unless response.code.to_i == 200
response.body
ensure
file&.close
end
PHP — cURL
PHP's cURL wrapper remains one of the most concise multipart clients:
function convertDoc(string $filePath, string $apiKey): string {
$ch = curl_init("https://anymd.net/api/convert");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
CURLOPT_POSTFIELDS => [
"file" => new CURLFile($filePath),
],
CURLOPT_RETURNTRANSFER => true,
]);
$result = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("HTTP $status: $result");
}
return $result;
}
C# — HttpClient
Modern .NET with System.Net.Http:
using System.Net.Http.Headers;
async Task<string> ConvertDoc(string filePath, string apiKey)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiKey);
using var form = new MultipartFormDataContent();
using var fileStream = File.OpenRead(filePath);
form.Add(new StreamContent(fileStream), "file", Path.GetFileName(filePath));
var response = await client.PostAsync(
"https://anymd.net/api/convert", form);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
Format support — one API, 15+ formats
Every language client above works with every format. The same file=<document> multipart field, the same authentication, the same Markdown output — regardless of whether you're sending a PDF, a DOCX from 2007, a LibreOffice ODT, or an EPUB ebook:
| Format | Extension | Typical time (100 KB) |
|---|---|---|
.pdf | 350 ms | |
| DOCX | .docx | 280 ms |
| PPTX | .pptx | 420 ms |
| XLSX | .xlsx | 520 ms |
| ODT | .odt | 260 ms |
| EPUB | .epub | 310 ms |
| RTF | .rtf | 220 ms |
| HTML | .html | 180 ms |
| Markdown | .md | 50 ms (passthrough) |
| CSV | .csv | 150 ms |
| XML | .xml | 190 ms |
| JSON | .json | 170 ms |
Times are median over 1,000 documents at the 100 KB size level via AnyMD's production API. Your mileage depends on file complexity and page count.
What you don't need
- No SDK install — no
pip install,npm install,cargo add, orgo getrequired. The HTTP POST is the SDK. - No version management — the API contract is stable. You don't track client library versions or breaking changes.
- No format-specific libraries — one endpoint handles PDF, DOCX, PPTX, XLSX, EPUB, ODT, RTF, HTML, CSV, XML, JSON, and more. No
pdfplumberfor PDFs,python-docxfor DOCX,openpyxlfor spreadsheets. - No local dependencies — the conversion engine runs on the server. No PyMuPDF install, no LibreOffice headless, no Pandoc binary to keep updated.
For in-depth language-specific patterns, see our dedicated tutorials: Python, Node.js, Rust, Go, and Bash + jq. For pricing and rate limits, see the plans page.
Conclusion
AnyMD's REST API is the universal document conversion layer. It works the same way from every language, every framework, every platform. One POST request, one authentication key, 15+ formats — clean Markdown out.
Get your free API key — 100 pages/month free, no credit card required. Then convert any document from any language in under 60 seconds.