Check a PDF for accessibility
Checks one PDF (up to 25 MB and 500 pages) against the machine-checkable criteria of PDF/UA. Send the file as multipart/form-data in the field file, or JSON with url or upload_id. Answers with 202 right away; ?wait=true (or a number of seconds, at most 30) waits for the result. The result counts the criteria met and lists the topics with problems, with steps to fix them. It is not a statement that the file meets legal requirements: a manual review is always needed. Costs at least 1 credit.
https://api.switchfile.io/v1/accessibility/checksExample
curl "https://api.switchfile.io/v1/accessibility/checks?wait=true" \
-H "Authorization: Bearer $SWITCHFILE_API_KEY" \
-F file=@annual-report.pdf \
-F report_pdf=enimport { readFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("annual-report.pdf")]), "annual-report.pdf");
form.append("report_pdf", "en");
const res = await fetch("https://api.switchfile.io/v1/accessibility/checks?wait=true", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SWITCHFILE_API_KEY}`,
},
body: form,
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
console.log(await res.json());<?php
$ch = curl_init('https://api.switchfile.io/v1/accessibility/checks?wait=true');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => new CURLFile('annual-report.pdf'),
'report_pdf' => 'en',
],
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SWITCHFILE_API_KEY'),
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) {
throw new RuntimeException("HTTP $status: $response");
}
print_r(json_decode($response, true));import os
import requests
res = requests.post(
"https://api.switchfile.io/v1/accessibility/checks?wait=true",
headers={"Authorization": f"Bearer {os.environ['SWITCHFILE_API_KEY']}"},
files={"file": open("annual-report.pdf", "rb")},
data={"report_pdf": "en"},
)
res.raise_for_status()
print(res.json())Parameters
Query parameters
waitstring | numbertrue or a number of seconds: wait until the check is done, at most 30 seconds
one of "true" Ā· example: true
Headers
Idempotency-Keystring1 to 255 visible ASCII characters, for example a UUID. Send the request again with the same key after a timeout: nothing is created twice. Kept for 24 hours.
example: 6f1c2b8e-1d8e-4c55-9f0e-3f3f0b0a8f21
X-End-UserstringService keys only: an ID for the visitor you act for (1 to 128 characters: letters, digits and . _ : @ -). Quotas and rate limits then count per visitor.
X-End-User-IpstringRequired with X-End-User: the IP address of the visitor.
Request body
filenamestringFile name shown in the report
1 to 255 characters
profilestringPDF/UA part to check against. auto (default) picks it from the file and falls back to PDF/UA-1
one of "auto"
report_pdfstringAlso create the report as an accessible PDF in this language
one of "en"
upload_idstring (uuid)ID from POST /v1/uploads
urlstringPublic URL of the PDF
at most 4096 characters Ā· matches ^https?://
filefilerequiredThe PDF, up to 25 MB and 500 pages
filenamestringFile name shown in the report
profilestringPDF/UA part to check against
one of "auto", "ua1", "ua2"
report_pdfstringAlso create the report as a PDF in this language
one of "en"
Responses
200Done within wait
created_atstringrequiredISO 8601 timestamp, for example 2026-11-06T18:00:00+00:00
creditsstring (integer) | integerrequiredCredits used, set once the check is done
errorobject | nullrequirederror.codestringrequirederror.messagestringrequiredexpires_atstring | nullrequiredInput and report are deleted at this time
finished_atstring | nullrequiredISO 8601 timestamp, for example 2026-11-06T18:00:00+00:00
idstring (uuid)requiredinputobjectrequiredinput.filenamestring | nullrequiredinput.sizestring (integer) | integerrequiredlinksobjectrequiredlinks.selfstringrequiredobjectstringrequiredone of "accessibility_check"
profilestringrequiredone of "auto", "ua1", "ua2"
report_pdfstring | nullrequiredAlso create the report as an accessible PDF in this language
one of "en"
resultobject | nullrequiredresult.completebooleanrequiredfalse if the time limit was reached; the counts cover only the checked part
result.criteriaobjectrequiredresult.criteria.appliedstring (integer) | integerrequiredMachine-checkable criteria that apply to this file
at least 0
result.criteria.failedstring (integer) | integerrequiredat least 0
result.criteria.metstring (integer) | integerrequiredat least 0
result.manual_review_neededbooleanrequiredAlways true: software cannot check everything. A person who knows the content has to review it
one of true
result.pagesstring (integer) | integerrequiredat least 0
result.profilestringrequiredone of "ua1", "ua2"
result.report_json_urlstringrequiredSigned URL of the full report as JSON
result.report_pdf_urlstring | nullrequiredSigned URL of the report as PDF (with report_pdf)
result.summarystringrequiredX of Y machine-checkable criteria met; manual review needed
result.topicsAccessibilityTopic[]requiredTopics with failed criteria, most failed checks first
result.topics[].explanationstringrequiredWhat the problem means for people who use assistive technology
result.topics[].failed_checksstring (integer) | integerrequiredNumber of failed checks in this topic
at least 0
result.topics[].fixobjectrequiredresult.topics[].fix.acrobatstring[]requiredSteps in Adobe Acrobat Pro
result.topics[].fix.indesignstring[]requiredSteps in Adobe InDesign
result.topics[].fix.wordstring[]requiredSteps in Microsoft Word
result.topics[].idstringrequiredStable topic ID, for example alt-text
result.topics[].pagesstring (integer) | integer[]requiredAffected pages, at most 100
result.topics[].titlestringrequiredstatusstringrequiredone of "queued", "processing", "finished", "failed"
200 application/json{ "id": "8c3e5a71-2f4b-4d6a-9e0c-7b1d3f5a2e48", "object": "accessibility_check", "status": "finished", "input": { "filename": "annual-report.pdf", "size": 482113 }, "profile": "auto", "report_pdf": "en", "result": { "summary": "38 of 41 machine-checkable criteria met; manual review needed", "profile": "ua1", "complete": true, "pages": 12, "criteria": { "applied": 41, "met": 38, "failed": 3 }, "manual_review_needed": true, "topics": [ { "id": "alt-text", "title": "Figure or formula has no alternative text", "explanation": "A Figure or Formula tag has neither alternative text (/Alt) nor replacement text (/ActualText). Screen readers then announce only that there is a graphic, or skip it, so blind users miss information that exists only in the image, such as the message of a chart or the content of a formula.", "fix": { "word": [ "Right-click the picture and choose View Alt Text (Office 2019 to 2024: Edit Alt Text)." ], "indesign": [ "Select the image and choose Object > Object Export Options > Alt Text." ], "acrobat": [ "Choose All tools > Prepare for accessibility > Add alternate text." ] }, "failed_checks": 5, "pages": [ 1, 2, 7 ] } ], "report_json_url": "https://api.switchfile.io/files/2a6f8e1c-4b3d-4e7a-9c5f-8d1b0e2a4c67/report.json?expires=1762538401&signature=3f9c2a7d41e8b6c05d1f9e2a7b4c8d3e6f0a1b2c3d4e5f60718293a4b5c6d7e8", "report_pdf_url": "https://api.switchfile.io/files/2a6f8e1c-4b3d-4e7a-9c5f-8d1b0e2a4c67/report.pdf?expires=1762538401&signature=3f9c2a7d41e8b6c05d1f9e2a7b4c8d3e6f0a1b2c3d4e5f60718293a4b5c6d7e8" }, "error": null, "credits": 1, "created_at": "2026-11-06T18:00:00+00:00", "finished_at": "2026-11-06T18:00:02+00:00", "expires_at": "2026-11-07T18:00:02+00:00", "links": { "self": "https://api.switchfile.io/v1/accessibility/checks/8c3e5a71-2f4b-4d6a-9e0c-7b1d3f5a2e48" } }202Created, still running. Poll links.self with ?wait=true
created_atstringrequiredISO 8601 timestamp, for example 2026-11-06T18:00:00+00:00
creditsstring (integer) | integerrequiredCredits used, set once the check is done
errorobject | nullrequirederror.codestringrequirederror.messagestringrequiredexpires_atstring | nullrequiredInput and report are deleted at this time
finished_atstring | nullrequiredISO 8601 timestamp, for example 2026-11-06T18:00:00+00:00
idstring (uuid)requiredinputobjectrequiredinput.filenamestring | nullrequiredinput.sizestring (integer) | integerrequiredlinksobjectrequiredlinks.selfstringrequiredobjectstringrequiredone of "accessibility_check"
profilestringrequiredone of "auto", "ua1", "ua2"
report_pdfstring | nullrequiredAlso create the report as an accessible PDF in this language
one of "en"
resultobject | nullrequiredresult.completebooleanrequiredfalse if the time limit was reached; the counts cover only the checked part
result.criteriaobjectrequiredresult.criteria.appliedstring (integer) | integerrequiredMachine-checkable criteria that apply to this file
at least 0
result.criteria.failedstring (integer) | integerrequiredat least 0
result.criteria.metstring (integer) | integerrequiredat least 0
result.manual_review_neededbooleanrequiredAlways true: software cannot check everything. A person who knows the content has to review it
one of true
result.pagesstring (integer) | integerrequiredat least 0
result.profilestringrequiredone of "ua1", "ua2"
result.report_json_urlstringrequiredSigned URL of the full report as JSON
result.report_pdf_urlstring | nullrequiredSigned URL of the report as PDF (with report_pdf)
result.summarystringrequiredX of Y machine-checkable criteria met; manual review needed
result.topicsAccessibilityTopic[]requiredTopics with failed criteria, most failed checks first
result.topics[].explanationstringrequiredWhat the problem means for people who use assistive technology
result.topics[].failed_checksstring (integer) | integerrequiredNumber of failed checks in this topic
at least 0
result.topics[].fixobjectrequiredresult.topics[].fix.acrobatstring[]requiredSteps in Adobe Acrobat Pro
result.topics[].fix.indesignstring[]requiredSteps in Adobe InDesign
result.topics[].fix.wordstring[]requiredSteps in Microsoft Word
result.topics[].idstringrequiredStable topic ID, for example alt-text
result.topics[].pagesstring (integer) | integer[]requiredAffected pages, at most 100
result.topics[].titlestringrequiredstatusstringrequiredone of "queued", "processing", "finished", "failed"
202 application/json{ "id": "8c3e5a71-2f4b-4d6a-9e0c-7b1d3f5a2e48", "object": "accessibility_check", "status": "queued", "input": { "filename": "annual-report.pdf", "size": 482113 }, "profile": "auto", "report_pdf": "en", "result": null, "error": null, "credits": null, "created_at": "2026-11-06T18:00:00+00:00", "finished_at": null, "expires_at": null, "links": { "self": "https://api.switchfile.io/v1/accessibility/checks/8c3e5a71-2f4b-4d6a-9e0c-7b1d3f5a2e48" } }401The API key is missing or not valid
UNAUTHENTICATED401 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-unauthenticated", "title": "Unauthenticated", "status": 401, "code": "UNAUTHENTICATED", "detail": "Missing or invalid API key." }402Today's free quota is used up and there are not enough credits
CREDITS_EXCEEDED402 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-credits-exceeded", "title": "Credits exceeded", "status": 402, "code": "CREDITS_EXCEEDED", "detail": "Today's free quota is used up and there are not enough credits left." }403X-End-User was sent with a key that is not a service key, or the account converts without storage (no_store, STORAGE_DISABLED)
END_USER_NOT_ALLOWED403 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-end-user-not-allowed", "title": "End user not allowed", "status": 403, "code": "END_USER_NOT_ALLOWED", "detail": "The X-End-User header is only allowed with a service key." }404upload_id was not found
NOT_FOUND404 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-not-found", "title": "Not found", "status": 404, "code": "NOT_FOUND", "detail": "Upload not found." }409No file has been uploaded for this upload_id yet (UPLOAD_INCOMPLETE), or the request with this Idempotency-Key is still running
UPLOAD_INCOMPLETE409 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-upload-incomplete", "title": "Upload incomplete", "status": 409, "code": "UPLOAD_INCOMPLETE", "detail": "No file has been uploaded for this upload_id yet." }413The request or the file is too large
FILE_TOO_LARGE413 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-file-too-large", "title": "File too large", "status": 413, "code": "FILE_TOO_LARGE", "detail": "The file is larger than 25 MB, the limit for accessibility checks." }415Send JSON or multipart/form-data
UNSUPPORTED_MEDIA_TYPE415 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-unsupported-media-type", "title": "Unsupported media type", "status": 415, "code": "UNSUPPORTED_MEDIA_TYPE", "detail": "Send JSON or multipart/form-data." }422Invalid data or a bad URL
INVALID_DATA422 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-invalid-data", "title": "Invalid data", "status": 422, "code": "INVALID_DATA", "detail": "The request contains invalid data.", "errors": { "source": [ "Set exactly one of url and upload_id, or send the file as multipart/form-data." ] } }429Too many requests or a daily quota is used up. See Retry-After
RATE_LIMIT_EXCEEDED429 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-rate-limit-exceeded", "title": "Rate limit exceeded", "status": 429, "code": "RATE_LIMIT_EXCEEDED", "detail": "Too many requests. Try again in 12 seconds." }503Accessibility checks are switched off on this server
ENGINE_UNAVAILABLE503 application/problem+json{ "type": "https://switchfile.io/docs/api/v1#errors-engine-unavailable", "title": "Engine unavailable", "status": 503, "code": "ENGINE_UNAVAILABLE", "detail": "PDF accessibility checks are not available right now. Try again later." }