Documentation
Everything you need to convert documents with the FileChanger API
Authentication
All API requests require an API key passed in the X-Api-Key header. Create your key from the dashboard.
Request Headers
| Header | Required | Description |
|---|---|---|
X-Api-Key | Yes | Your API key from the dashboard (e.g. fc_abc123...) |
- API keys are prefixed with fc_ and must be kept secret.
- Keys are hashed before storage. If you lose your key, reset it from the dashboard.
- One key per account. Resetting your key invalidates the previous one immediately.
Error Responses
| Status | Error | Description |
|---|---|---|
| 401 | Missing API key | The X-Api-Key header was not provided |
| 401 | Invalid API key | The API key was not found or is malformed |
| 403 | API key disabled | Your API key has been disabled |
| 403 | Quota exhausted | Conversion quota exhausted, upgrade or wait for reset |
| 429 | Rate limit exceeded | Too many requests in the last minute, slow down and retry |
Code Examples
# Pass your API key in the X-Api-Key header
curl https://filechanger.io/api/formats \
-H "X-Api-Key: fc_your_api_key_here"import requests
headers = {"X-Api-Key": "fc_your_api_key_here"}
response = requests.get(
"https://filechanger.io/api/formats",
headers=headers,
)
print(response.json())const response = await fetch("https://filechanger.io/api/formats", {
headers: {
"X-Api-Key": "fc_your_api_key_here",
},
});
const data = await response.json();
console.log(data);HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://filechanger.io/api/formats"))
.header("X-Api-Key", "fc_your_api_key_here")
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());import Network.HTTP.Simple
main :: IO ()
main = do
let request = setRequestHeader "X-Api-Key" ["fc_your_api_key_here"]
$ "GET https://filechanger.io/api/formats"
response <- httpBS request
print (getResponseBody response)Create a Conversion Job
Upload a file and the source/target formats to start a conversion job. Conversion is asynchronous: this returns a job id immediately, then you poll the job and download its output. The same flow handles every format, text or binary, of any size.
Request Headers
| Header | Required | Description |
|---|---|---|
X-Api-Key | Yes | Your API key |
Content-Type | Yes | Must be multipart/form-data |
- Endpoint: POST /api/jobs
- Form fields: "file" (the uploaded file), "from" (source format), "to" (target format)
- Response: JSON with "job_id" (string) and "status" ("pending")
- Next step: poll GET /api/jobs/{job_id} until the status is "done", then download the output
Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | Unsupported input format | The source format is not supported for reading |
| 400 | Unsupported output format | The target format is not supported for writing |
| 400 | Missing form field | The multipart upload must include the 'file', 'from', and 'to' fields |
| 400 | File too large | The uploaded file exceeds the maximum size limit |
| 403 | Quota exhausted | Conversion quota exhausted, upgrade or wait for reset |
| 429 | Rate limit exceeded | Too many requests in the last minute, slow down and retry |
Code Examples
# Upload a Markdown file to convert to DOCX, capturing the job id
curl -X POST "https://filechanger.io/api/jobs" \
-H "X-Api-Key: fc_your_api_key_here" \
-F "file=@document.md" \
-F "from=markdown" \
-F "to=docx"
# Response:
# {"job_id": "42", "status": "pending"}import requests
with open("document.md", "rb") as f:
response = requests.post(
"https://filechanger.io/api/jobs",
headers={"X-Api-Key": "fc_your_api_key_here"},
files={"file": f},
data={"from": "markdown", "to": "docx"},
)
job = response.json()
print("Job id:", job["job_id"])const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("from", "markdown");
formData.append("to", "docx");
const response = await fetch("https://filechanger.io/api/jobs", {
method: "POST",
headers: { "X-Api-Key": "fc_your_api_key_here" },
body: formData,
});
const job = await response.json();
console.log("Job id:", job.job_id);import java.net.http.HttpRequest.BodyPublishers;
import java.nio.file.Path;
String boundary = "----FormBoundary" + System.currentTimeMillis();
byte[] fileBytes = java.nio.file.Files.readAllBytes(Path.of("document.md"));
String body = "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\"document.md\"\r\n"
+ "Content-Type: text/markdown\r\n\r\n"
+ new String(fileBytes) + "\r\n"
+ "--" + boundary + "\r\nContent-Disposition: form-data; name=\"from\"\r\n\r\nmarkdown\r\n"
+ "--" + boundary + "\r\nContent-Disposition: form-data; name=\"to\"\r\n\r\ndocx\r\n"
+ "--" + boundary + "--";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://filechanger.io/api/jobs"))
.header("X-Api-Key", "fc_your_api_key_here")
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());import Network.HTTP.Client
import Network.HTTP.Client.MultipartFormData
main :: IO ()
main = do
manager <- newManager defaultManagerSettings
initialReq <- parseRequest "POST https://filechanger.io/api/jobs"
let req = initialReq
{ requestHeaders = [("X-Api-Key", "fc_your_api_key_here")] }
request <- formDataBody
[ partFileSource "file" "document.md"
, partBS "from" "markdown"
, partBS "to" "docx"
] req
response <- httpLbs request manager
print (responseBody response)Poll and Download
After creating a job, poll its status until it is done, then download the converted output. The output is streamed back with the correct filename and content type, and is deleted from storage right after delivery.
Request Headers
| Header | Required | Description |
|---|---|---|
X-Api-Key | Yes | Your API key |
- Status endpoint: GET /api/jobs/{job_id}
- Status response: JSON with "status" (pending, running, done, or failed), "from", "to", and (once done) "output_size"; "error" appears on failure
- Download endpoint: GET /api/jobs/{job_id}/output
- Download timing: returns 409 until the job is done, then streams the output once (the stored objects are deleted after delivery)
Response Headers
| Header | Description |
|---|---|
Content-Disposition | Attachment filename with the correct extension for the output format (on the output endpoint) |
Content-Type | MIME type of the converted output (on the output endpoint) |
Error Responses
| Status | Error | Description |
|---|---|---|
| 404 | Not found | No job with that id belongs to your account |
| 409 | Not ready | The job has not finished yet; keep polling the status endpoint |
| 500 | Conversion failed | The job's status is "failed"; the error field carries a generic message |
Code Examples
# Poll the job status until it is done
curl "https://filechanger.io/api/jobs/42" \
-H "X-Api-Key: fc_your_api_key_here"
# Response while running: {"status": "running", "from": "markdown", "to": "docx"}
# Response when finished: {"status": "done", "from": "markdown", "to": "docx", "output_size": 1234}
# Download the converted output once the status is "done"
curl "https://filechanger.io/api/jobs/42/output" \
-H "X-Api-Key: fc_your_api_key_here" \
-o document.docximport time
import requests
headers = {"X-Api-Key": "fc_your_api_key_here"}
job_id = "42"
while True:
status = requests.get(
f"https://filechanger.io/api/jobs/{job_id}", headers=headers
).json()
if status["status"] == "done":
break
if status["status"] == "failed":
raise RuntimeError(status.get("error", "conversion failed"))
time.sleep(0.5)
output = requests.get(
f"https://filechanger.io/api/jobs/{job_id}/output", headers=headers
)
with open("document.docx", "wb") as out:
out.write(output.content)const headers = { "X-Api-Key": "fc_your_api_key_here" };
const jobId = "42";
let status;
do {
await new Promise((r) => setTimeout(r, 500));
const res = await fetch("https://filechanger.io/api/jobs/" + jobId, { headers });
status = await res.json();
if (status.status === "failed") throw new Error(status.error);
} while (status.status !== "done");
const output = await fetch(
"https://filechanger.io/api/jobs/" + jobId + "/output", { headers }
);
const blob = await output.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "document.docx";
a.click();import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Path;
String jobId = "42";
String statusUrl = "https://filechanger.io/api/jobs/" + jobId;
String status;
do {
Thread.sleep(500);
HttpRequest poll = HttpRequest.newBuilder()
.uri(URI.create(statusUrl))
.header("X-Api-Key", "fc_your_api_key_here")
.GET().build();
status = client.send(poll, BodyHandlers.ofString()).body();
} while (!status.contains("\"status\":\"done\""));
HttpRequest download = HttpRequest.newBuilder()
.uri(URI.create(statusUrl + "/output"))
.header("X-Api-Key", "fc_your_api_key_here")
.GET().build();
HttpResponse<byte[]> response = client.send(
download, BodyHandlers.ofByteArray());
java.nio.file.Files.write(Path.of("document.docx"), response.body());import Network.HTTP.Simple
import qualified Data.ByteString.Lazy as BL
import Control.Concurrent (threadDelay)
import qualified Data.ByteString.Char8 as B8
main :: IO ()
main = do
let jobId = "42"
key = setRequestHeader "X-Api-Key" ["fc_your_api_key_here"]
pollUntilDone = do
resp <- httpBS (key $ "GET https://filechanger.io/api/jobs/" <> jobId)
if "\"status\":\"done\"" `B8.isInfixOf` getResponseBody resp
then pure ()
else threadDelay 500000 >> pollUntilDone
pollUntilDone
output <- httpLBS (key $ "GET https://filechanger.io/api/jobs/" <> jobId <> "/output")
BL.writeFile "document.docx" (getResponseBody output)Supported Formats
Query the API for the full list of supported input and output formats. Not all formats can be used in both directions. This endpoint is public and needs no API key.
- Endpoint: GET /api/formats
- Text formats: markdown, html, latex, rst, org, textile, mediawiki, dokuwiki, commonmark, gfm, docbook, jira, typst, djot, asciidoc, plain, native, json
- Binary formats: docx, epub, odt, rtf (the same POST /api/jobs flow handles these)
- Audio formats: mp3, wav, flac, ogg, opus (transcode between audio formats)
- Video formats: mp4, mov, avi, mkv, webm, flv, wmv, mpeg, m4v in; mp4, mkv, webm, av1 out
- Format aliases: md (markdown), html5 (html), tex (latex)
Code Examples
curl "https://filechanger.io/api/formats"
# Response:
# {
# "input_formats": ["markdown", "html", "latex", ...],
# "output_formats": ["markdown", "html", "latex", ...]
# }import requests
response = requests.get("https://filechanger.io/api/formats")
formats = response.json()
print("Input formats:", formats["input_formats"])
print("Output formats:", formats["output_formats"])const response = await fetch("https://filechanger.io/api/formats");
const formats = await response.json();
console.log("Input formats:", formats.input_formats);
console.log("Output formats:", formats.output_formats);HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://filechanger.io/api/formats"))
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());import Network.HTTP.Simple
main :: IO ()
main = do
let request = "GET https://filechanger.io/api/formats"
response <- httpBS request
print (getResponseBody response)n8n Integration
Convert files inside your n8n workflows with the FileChanger community node. The node takes a binary input, converts it, and hands the converted file to the next node as binary output.
- Install: in n8n go to Settings > Community Nodes > Install and enter the package name n8n-nodes-filechanger
- Credentials: create a FileChanger API credential with your API key from the dashboard. The Base URL defaults to https://filechanger.io; only change it for a self-hosted instance. The Test button calls GET /api/formats and expects a 200
- Operations: Convert a File (send a binary, get the converted binary back) and List Formats (fetch the supported input and output formats)
- Formats: the From Format and To Format dropdowns are loaded live from /api/formats, so they always match what the API supports
Zapier Integration
Convert files between formats inside your Zaps with the FileChanger Zapier app.
- Connect: add a FileChanger action to a Zap and connect your account with your API key from the dashboard. The Base URL defaults to https://filechanger.io; only change it for a self-hosted instance
- Actions: Convert File (converts a file from one format to another) and List Formats (returns the supported input and output formats)
- Formats: the From Format and To Format dropdowns are populated dynamically from the API
- Verification: Zapier verifies the connection with a GET /api/formats call that must return a 200
Make Integration
Use FileChanger from your Make (formerly Integromat) scenarios with the FileChanger custom app.
- Install: add the FileChanger app to your Make organisation via its install link, then create a connection
- Connection: two fields, Base URL (defaults to https://filechanger.io) and API Key (from the dashboard). Make validates the connection with a GET /api/formats call that must return a 200
- Modules: Convert a File (upload a file with from/to format identifiers, receive the converted file) and List Formats (fetch the supported input and output formats)
- Formats: Make does not offer dropdowns, so From Format and To Format are free-text fields. Run the List Formats module first to discover the identifiers (e.g. markdown, docx, mp3)
- Output: File Data is the converted binary, ready to feed into an upload module; File Name is taken from the response's Content-Disposition header