Accessful API

PDF to PDF/UA Conversion Service

REST API API Key Webhooks PDF/UA v2.1.1

Overview

The Accessful API converts PDF documents into PDF/UA (Universal Accessibility) format. PDF/UA is the ISO 14289 standard that makes PDFs usable by assistive technology.

Base URL: https://api.accessful.de
API Version: 2.1.0
Authentication: X-API-Key header
Content Types: application/json, multipart/form-data

Key Features

API Workflow

1. Send X-API-Key
2. Upload PDF(s)
3. Process
4. Webhook / Poll
5. Download

Try it out

Convert a PDF to PDF/UA right here in the browser — paste your API key, pick a PDF, and watch the full upload → process → download roundtrip happen against api.accessful.de.

Privacy: Your API key stays in this browser tab. It's sent only to https://api.accessful.de as the X-API-Key header, never to a third party and never to docs.accessful.de. The PDF is processed by Accessful exactly as it would be from your production integration.

PDF/UA Tester

Max 100 MB. PDF only.

Migration v1 → v2

If you integrated against the v1 API (gateway / Keycloak OAuth2), three things change:

What changed v1 (before May 2026) v2 (current)
Base URL https://gateway.accessful.de https://api.accessful.de
Authentication OAuth2 password grant: token from iam.accessful.de/realms/accessful-realm/… using client_id=accessful-api + username + password, then Authorization: Bearer <jwt> Static API key sent as the X-API-Key header. No token round-trip, no realm, no client ID, no username, no password.
Path prefix Same: /api/v1/upload-service/… Unchanged: /api/v1/upload-service/…
Important: The old realm accessful-realm and the OAuth2 client accessful-api have been retired. Existing integrations that still hit gateway.accessful.de or request tokens from the old realm will fail with 401 / 404.

Already-running jobs are not affected — they finish under the old credentials they were started with. Only new requests need the v2 credential.

Authentication

Every request to the Accessful API must include your API key in the X-API-Key header. An API key is a single, long-lived secret issued by your Accessful organisation admin.

Header: X-API-Key: ak_<your_api_key>
Applies to: every endpoint documented below
Rotation: Generate a new key in the Hub portal, switch over, then revoke the old one. Old and new keys can coexist during a rotation window.

Example: smoke-test your key

Use the cheapest authenticated endpoint to verify the key is alive:

curl -i "https://api.accessful.de/api/v1/upload-service/job-status/00000000-0000-0000-0000-000000000000" \ -H "X-API-Key: ak_your_api_key"

You should get 404 Not Found for the made-up case ID. If you get 401 Unauthorized, the key is wrong, revoked, or being sent in the wrong header.

What if my key doesn't work?

API Endpoints

All endpoints expect the X-API-Key header. The Authorization header is no longer required for API-key clients.

Upload PDF Files (multipart)

POST
/api/v1/upload-service/pdf/upload

Upload one or more PDF files for conversion to PDF/UA. Optionally configure a webhook to be notified when each file finishes processing.

Headers

Header Value
X-API-Key Your API key (ak_…)
Content-Type multipart/form-data

Form Parameters

Parameter Type Required Description
files File[] Yes Array of PDF files to upload (max 100 MB per file)
webhookUrl string No Callback URL invoked when each file finishes processing
secret string No* HMAC-SHA256 secret used to sign webhook payloads (*required if webhookUrl is set)
folder-name string No Optional folder name that groups the uploaded PDFs in the Hub portal

Response Codes

200 Upload accepted, files queued for analysis
400 Invalid file type or malformed request
401 API key missing or invalid
429 Upload limit exceeded
500 Internal server error

Success Response (200)

{
  "successfulUploads": [
    "c8c956bb-19c3-451b-826f-5b3129eee4c1",
    "6848df1b-c6cf-4ae4-8ddd-c474cc103d4e"
  ],
  "duplicateFiles": [],
  "message": "Upload completed successfully. Uploaded 2 files. 0 duplicates found.",
  "callbackUrl": "https://your-server.com/callback"
}

Example Request

curl -X POST "https://api.accessful.de/api/v1/upload-service/pdf/upload" \ -H "X-API-Key: ak_your_api_key" \ -F "files=@document1.pdf" \ -F "files=@document2.pdf" \ -F "webhookUrl=https://your-server.com/callback" \ -F "secret=your-hmac-secret" \ -F "folder-name=Marketing"

Upload PDF Files by URL

POST
/api/v1/upload-service/pdf/upload-by-url-list

Upload PDFs by URL instead of uploading bytes. Accessful downloads each URL and queues it for conversion. The request is accepted asynchronously.

Headers

Header Value
X-API-Key Your API key (ak_…)
Content-Type application/json

JSON Body

Field Type Required Description
files FileEntry[] Yes Array of { url, filename } entries
callbackUrl string No Callback URL for processing notifications
hmacSignature string No* HMAC-SHA256 secret used to sign webhook payloads (*required if callbackUrl is set)

FileEntry Object

Property Type Required Description
url string Yes Publicly reachable URL of the PDF
filename string Yes Filename ending with .pdf (alphanumeric, underscore, hyphen)

Response Codes

202 Upload accepted for processing
400 Invalid URL format or filename
401 API key missing or invalid
429 Upload limit exceeded
500 Internal server error

Success Response (202)

{
  "accepted": [
    {
      "uri": "https://example.com/document1.pdf",
      "jobId": "c8c956bb-19c3-451b-826f-5b3129eee4c1"
    },
    {
      "uri": "https://example.com/document2.pdf",
      "jobId": "6848df1b-c6cf-4ae4-8ddd-c474cc103d4e"
    }
  ],
  "failures": {},
  "callbackResult": "Callback sent successfully"
}

Example Request

curl -X POST "https://api.accessful.de/api/v1/upload-service/pdf/upload-by-url-list" \ -H "X-API-Key: ak_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "files": [ { "url": "https://example.com/document1.pdf", "filename": "document1.pdf" }, { "url": "https://example.com/document2.pdf", "filename": "document2.pdf" } ], "callbackUrl": "https://your-server.com/callback", "hmacSignature": "your-hmac-secret" }'

Get Job Status

GET
/api/v1/upload-service/job-status/{caseId}

Retrieve the current processing status of a PDF conversion job, plus the latest accessibility score.

Path Parameters

Parameter Type Description
caseId UUID Unique identifier for the conversion job (returned by the upload endpoints)

Job Status Values

  • queued — Job is waiting to be processed
  • running — Job is currently being processed
  • completed — Job completed successfully
  • failed — Job failed during processing
  • canceled — Job was canceled

Example Request

curl -X GET "https://api.accessful.de/api/v1/upload-service/job-status/c8c956bb-19c3-451b-826f-5b3129eee4c1" \ -H "X-API-Key: ak_your_api_key"

Example Response

{
  "jobStatus": "completed",
  "score": 95
}

Download Converted PDF

GET
/api/v1/upload-service/download/{caseId}

Download the converted PDF/UA file for a completed job. The response is the raw PDF bytes (Content-Type: application/pdf). The pdf-version response header contains the iteration number that was downloaded.

Example Request

curl -X GET "https://api.accessful.de/api/v1/upload-service/download/c8c956bb-19c3-451b-826f-5b3129eee4c1" \ -H "X-API-Key: ak_your_api_key" \ -o converted-document.pdf

Delete Case

DELETE
/api/v1/upload-service/delete/{caseId}

Delete a case and its associated files. Only the case owner (the user who owns the API key that uploaded it) can delete it.

Example Request

curl -X DELETE "https://api.accessful.de/api/v1/upload-service/delete/c8c956bb-19c3-451b-826f-5b3129eee4c1" \ -H "X-API-Key: ak_your_api_key"

Webhooks

Webhooks deliver real-time notifications when a PDF finishes processing. When you supply webhookUrl (multipart upload) or callbackUrl (URL upload) along with a secret, Accessful POSTs the result to your endpoint and signs the payload with HMAC-SHA256.

Security: Every webhook is signed with HMAC-SHA256 using the secret you provided. Always verify the signature before processing the payload — otherwise an attacker who guesses your callback URL can spoof job-completion events.

Webhook Request

POST
https://your-server.com/callback/{caseId}

Headers

Header Description
Content-Type application/json
X-Signature Base64-encoded HMAC-SHA256 of the raw request body, using the secret you supplied at upload time

Payload

{
  "jobStatus": "completed",
  "caseId": "c8c956bb-19c3-451b-826f-5b3129eee4c1",
  "fileName": "document.pdf"
}

HMAC Verification

Compute the HMAC-SHA256 of the raw request body using your secret, base64-encode it, and compare it to the X-Signature header using a constant-time comparison.

Java

public boolean verifyHmac(String payload, String signature, String secret) {
    try {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] rawHmac = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
        String expected = Base64.getEncoder().encodeToString(rawHmac);
        return MessageDigest.isEqual(expected.getBytes(), signature.getBytes());
    } catch (Exception e) {
        return false;
    }
}

Node.js

const crypto = require('crypto');

function verifyHmac(payload, signature, secret) {
    const hmac = crypto.createHmac('sha256', secret);
    hmac.update(payload);
    const expected = hmac.digest('base64');
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Complete Examples

Quick Start with cURL

# Set your API key once (replace with the value your admin gave you)
API_KEY="ak_your_api_key"
BASE="https://api.accessful.de"

# 1. Upload a PDF
RESPONSE=$(curl -s -X POST "$BASE/api/v1/upload-service/pdf/upload" \
  -H "X-API-Key: $API_KEY" \
  -F "files=@document.pdf" \
  -F "webhookUrl=https://your-server.com/callback" \
  -F "secret=your-hmac-secret")

# 2. Extract the case ID
CASE_ID=$(echo "$RESPONSE" | jq -r '.successfulUploads[0]')

# 3. (Optional) Poll for completion — prefer the webhook for production
curl -s -X GET "$BASE/api/v1/upload-service/job-status/$CASE_ID" \
  -H "X-API-Key: $API_KEY"

# 4. Download the converted file
curl -X GET "$BASE/api/v1/upload-service/download/$CASE_ID" \
  -H "X-API-Key: $API_KEY" \
  -o converted-document.pdf

JavaScript / Node.js

const FormData = require('form-data');
const fs = require('fs');
const axios = require('axios');

class AccessfulClient {
    constructor(baseUrl, apiKey) {
        this.baseUrl = baseUrl;
        this.headers = { 'X-API-Key': apiKey };
    }

    async uploadPdf(filePath, webhookUrl = null, secret = null) {
        const form = new FormData();
        form.append('files', fs.createReadStream(filePath));
        if (webhookUrl) form.append('webhookUrl', webhookUrl);
        if (secret)     form.append('secret', secret);

        const response = await axios.post(
            `${this.baseUrl}/api/v1/upload-service/pdf/upload`,
            form,
            { headers: { ...form.getHeaders(), ...this.headers } }
        );
        return response.data;
    }

    async getJobStatus(caseId) {
        const response = await axios.get(
            `${this.baseUrl}/api/v1/upload-service/job-status/${caseId}`,
            { headers: this.headers }
        );
        return response.data;
    }

    async downloadPdf(caseId, outputPath) {
        const response = await axios.get(
            `${this.baseUrl}/api/v1/upload-service/download/${caseId}`,
            { headers: this.headers, responseType: 'stream' }
        );
        const writer = fs.createWriteStream(outputPath);
        response.data.pipe(writer);
        return new Promise((resolve, reject) => {
            writer.on('finish', resolve);
            writer.on('error', reject);
        });
    }
}

// Usage
const client = new AccessfulClient('https://api.accessful.de', 'ak_your_api_key');

(async () => {
    const upload = await client.uploadPdf('./document.pdf');
    const caseId = upload.successfulUploads[0];
    console.log('Case ID:', caseId);

    let status = { jobStatus: 'queued' };
    while (status.jobStatus !== 'completed' && status.jobStatus !== 'failed') {
        await new Promise(r => setTimeout(r, 5000));
        status = await client.getJobStatus(caseId);
        console.log('Status:', status);
    }

    if (status.jobStatus === 'completed') {
        await client.downloadPdf(caseId, './converted-document.pdf');
        console.log('Download complete.');
    }
})();

Spring Boot — minimal configuration

# application.properties
accessful.api.base-url=https://api.accessful.de
accessful.api.key=ak_your_api_key
callback.hmac.secret=your-hmac-secret
callback.url=https://your-server.com/api/callback/

Inject the key as a header on every outgoing request — no token-refresh logic required:

@Bean
public RestClient accessfulRestClient(
        @Value("${accessful.api.base-url}") String baseUrl,
        @Value("${accessful.api.key}")      String apiKey) {
    return RestClient.builder()
            .baseUrl(baseUrl)
            .defaultHeader("X-API-Key", apiKey)
            .build();
}

Rate Limits & Best Practices

Rate Limits

Best Practices