Skip to content

HTTP API ​

The self-hosted image serves the web interface at / and this API. Everything runs on your server; nothing leaves it. If you have not started the container yet, that is one command:

bash
docker run -d -p 8080:8080 ghcr.io/alam00000/bentopdf-kura:latest

Your first request ​

Send the PDF as the raw request body; the converted PDF comes back as the response body.

bash
curl -o out.pdf --data-binary @in.pdf 'http://localhost:8080/api/convert?level=2b'

See what happened without opening the file:

bash
curl -s -o out.pdf -D - --data-binary @in.pdf \
  'http://localhost:8080/api/convert?level=2b' | grep -i '^x-kura'
x-kura-level: 2b
x-kura-engine: BentoPDF Kura Engine 1.1.0
x-kura-changes: 3

--data-binary matters: plain -d would mangle the bytes. A JPEG works as input too; it becomes a one-page document first.

POST /api/convert ​

Converts the document to the target and returns the result.

Query parameters ​

The names match the npm package options; every one maps to a CLI flag.

ParameterMeaning
levelrequired: 1b, 1a, 2b, 2u, 2a, 3b, 3u, 3a, 4, 4f, 4e, x1a, x3, x4, x6, e1, vt1, vt3
ua=trueadd PDF/UA on top of the level
langdocument language tag, for example en-US
allowVisualRisk=trueaccept repairs that may change appearance instead of rejecting
rasterizePages=truerasterize every page
rasterDpiresolution for rasterized pages, 24 to 1200
outlineFonts=trueconvert text to outlines
imageMaxPpidownsample images above this resolution
outputCondition, outputConditionInfo, registrythe PDF/X output intent
vtRecordsPDF/VT record boundaries, for example 1-3,4-6
embedSource=true, embedSourceNameattach the original file to the result
attachXmlName, facturxProfilee-invoice attachment name and profile
profilea bundled preflight profile to apply, by name from /api/profiles
report=jsonreturn the JSON report with the PDF inside instead of raw bytes

Body ​

Either the document itself as the raw body, or multipart/form-data with these fields:

FieldMeaning
filethe document (required)
profilea preflight profile in JSON or XML, when it is not one of the bundled ones
xmlan e-invoice XML to attach
destProfilean ICC profile for the output intent
defaultRgb, defaultCmyk, defaultGrayICC profiles for uncalibrated colour

Any query parameter may also be sent as a text field.

Headers ​

HeaderDirectionMeaning
X-Passwordrequestpassword for encrypted input
Authorization: Bearer <token>requestrequired when the server has KURA_API_TOKEN set (X-Api-Token works too)
X-Kura-Levelresponsethe level the file was converted to
X-Kura-Engineresponseengine name and version
X-Kura-Changesresponsehow many changes were made

With the report ​

report=json returns the same report the CLI prints, with the PDF base64-encoded in pdf:

bash
curl -s -F file=@in.pdf 'http://localhost:8080/api/convert?level=2b&ua=true&report=json' \
  | jq '{level, changes: (.issues | length), first: .issues[0]}'
json
{"level":"2b","changes":3,"first":{"code":"OUTPUT_INTENT_ADDED","detail":"sRGB IEC61966-2.1","fixed":true}}

Examples ​

An e-invoice, attaching the XML and producing PDF/A-3:

bash
curl -o invoice-a3.pdf -F file=@invoice.pdf -F xml=@invoice.xml \
  'http://localhost:8080/api/convert?level=3b'

Apply a bundled preflight profile's fixes:

bash
curl -o fixed.pdf --data-binary @in.pdf \
  'http://localhost:8080/api/convert?level=x4&profile=press/book-text-pages-check-and-fix'

Apply your own profile:

bash
curl -o fixed.pdf -F file=@in.pdf -F profile=@my-rules.json \
  'http://localhost:8080/api/convert?level=2b'

POST /api/check ​

Checks without changing anything and returns the report. The level may also be a check-only flavour (x4p, x5g, x5n, x5pg, x6n, x6p, vt2).

bash
curl -s --data-binary @in.pdf 'http://localhost:8080/api/check?level=2b'
json
{"ok":true,"level":"2b","engine":"BentoPDF Kura Engine 1.1.0","mode":"check","compliant":false,"findings":2,"issues":[...]}

Add analyze=true for the document analysis lines, or profile=<name> (or a profile field) to run a preflight profile; the findings arrive in analysis:

bash
curl -s -F file=@in.pdf 'http://localhost:8080/api/check?level=x4&profile=press/book-text-pages-check' \
  | jq '.analysis[] | select(.code=="PROFILE_HIT") | .detail'

The HTTP status is 200 whether or not the file conforms; read compliant and findings.

GET /api/profiles ​

Lists the bundled preflight profiles as [{id, name, description}]. The id (for example press/book-text-pages-check) is what profile= takes.

POST /api/verify-password ​

Send the encrypted PDF as the body with X-Password; returns {"ok":true,"valid":true} or {"ok":true,"valid":false} without converting. true means /api/convert will accept the same password.

bash
curl -s --data-binary @locked.pdf -H 'X-Password: hunter2' http://localhost:8080/api/verify-password

GET /healthz ​

json
{"ok":true,"version":"BentoPDF Kura Engine (kura) 1.1.0","active":0,"queued":0}

active is jobs currently running, queued is jobs waiting for a slot. Wire it into your load balancer or uptime monitoring as is.

Errors ​

Every error is a JSON body with ok: false, an errorCode and a plain-language error:

StatusCodeMeaningWhat to do
400BAD_LEVELunknown levelthe response lists valid ones
400BAD_REQUESTan option is malformed, or no document was sentfix the request
400BAD_PROFILEno bundled profile with that namesee /api/profiles
400NOT_A_PDFthe body is neither a PDF nor a JPEGsend the file with --data-binary
401UNAUTHORIZEDtoken requiredsend Authorization: Bearer <token>
413TOO_LARGEbody exceeds the upload limitraise KURA_MAX_UPLOAD_MB
422the engine's codethe document was rejected; error says why and suggestedLevel names a level that would accept itsee Rejection codes
429BUSYqueue fullretry with backoff, or raise KURA_QUEUE
504TIMEOUTthe job exceeded the time limitraise KURA_TIMEOUT_MS

PASSWORD_REQUIRED is a 422 like any other rejection: send X-Password.

Limits and tuning ​

Concurrency, queue depth, upload size and the time limit are environment variables on the container. Defaults: 2 parallel jobs, a queue of 8, 500 MB uploads, 10 minutes per job. See Self-hosting.

Dual-licensed under AGPL-3.0 and Commercial License.