API reference · v0.1.0

Four calls, and the file
deletes itself.

Upload, queue a job, follow it, download the result. There are no keys to request and no accounts to create — the same guarantees the web app gets are the ones you get.

Overview

Every response is JSON except a download, which is the file itself. Field names are camelCase going out; request bodies accept either camelCase or snake_case. Timestamps are ISO 8601 in UTC.

# Same origin as the app in production — no separate API host.
https://pdfflow.bondeth.site/api

# No key, no token, no Authorization header. There is no account to attach
# a request to, so there is nothing to authenticate.
curl -s https://pdfflow.bondeth.site/api/health
No authentication
Nothing to send. Rate limits are per client address instead of per key.
Nothing retained
Inputs are deleted when a job ends, results within 30 minutes. There is no listing endpoint.
CORS
Locked to the configured origins. Credentials are never accepted — there are none.

Quickstart

The whole flow, start to finish. Swap in your own host and file, then paste the four commands in order.

# 1 — upload. The response lists the tools that fit what you sent.
curl -s -X POST https://pdfflow.bondeth.site/api/upload \
  -F "files=@quarterly-report.pdf" | jq '.files[0].id, .availableOperations[].key'

# 2 — queue a job against the file id you got back.
curl -s -X POST https://pdfflow.bondeth.site/api/jobs/create \
  -H "Content-Type: application/json" \
  -d '{"operation":"compress","fileIds":["<file-id>"],"options":{"level":"medium"}}' | jq '.id'

# 3 — follow it. The server closes the stream when the job ends.
curl -N https://pdfflow.bondeth.site/api/jobs/<job-id>/events

# 4 — collect the result, before the 30 minutes are up.
curl -OJ https://pdfflow.bondeth.site/api/download/<job-id>

Files

POST/api/upload30 requests / minute

Upload one or more files

Sends files into temporary storage. Each one is written under a generated UUID name — the name you supply is kept only for display and never used on disk. The response also tells you which tools can run on what you just sent, so you never have to guess.

Body — multipart/form-data

filesfilerepeatable, max 25
One form field per file. PDF, JPEG, PNG, WebP, DOCX, XLSX and PPTX are accepted, up to 100 MB each.
curl -s -X POST https://pdfflow.bondeth.site/api/upload \
  -F "files=@quarterly-report.pdf"

Returns 200 — Files stored

files[].iduuid
Pass this to /api/jobs/create.
files[].originalNamestring
Sanitised display name.
files[].sizeinteger
Bytes as stored.
files[].mimeTypestring
Resolved from the content, not the extension.
files[].familystring
One of pdf, image, office.
files[].pageCountintegernullable
Present for PDFs once counted.
files[].expiresAttimestamp
When this file is deleted. 30 minutes out by default.
availableOperationsOperation[]
The subset of the catalog that fits this upload, in the same shape /api/operations returns.
Can fail with 413 file_too_large415 unsupported_file_type422 validation_error429 rate_limited

Tools

GET/api/operations

The tool catalog

Every operation the service knows about, including ones not built yet. Check implemented before offering a tool — an unimplemented key is advertised so clients can show it as coming soon, not so it can be queued.

curl -s https://pdfflow.bondeth.site/api/operations

Returns 200 — The full catalog

keystring
The value you send as operation.
namestring
Human label.
descriptionstring
One line on what it does.
categorystring
organize, optimize, convert, security or edit.
acceptsstring[]
File families it runs on.
multiFileboolean
Whether it takes more than one input.
minFilesinteger
Fewest inputs it needs.
outputExtensionstring
Extension of the result, e.g. .pdf or .zip.
implementedboolean
False means queuing it will fail.
optionsSchemaobject
The shape of options for this tool.

Jobs

POST/api/jobs/create60 requests / minute

Queue a processing job

Hands the job to the worker and returns immediately — the response is the job record at pending, not the finished result. Field names are accepted in both camelCase and snake_case.

Body — application/json

operationstring
A key from /api/operations.
fileIdsuuid[]1–25
Ids from /api/upload. For multi-file tools the array order is the order used.
optionsobjectoptional, max 32 keys
Tool-specific settings, matching that tool’s optionsSchema. Organize Pages accepts a pages array in output order; each item contains a 1-based source page and a rotation of 0, 90, 180 or 270.
curl -s -X POST https://pdfflow.bondeth.site/api/jobs/create \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "compress",
    "fileIds": ["8f14e45f-ea6c-4f2b-b2a1-2c9a6f1d3e77"],
    "options": { "level": "medium" }
  }'

Returns 201 — Job queued

iduuid
Use it to follow, then download.
statusstring
pending, processing, completed, failed or expired.
progressinteger
0–100.
stagestringnullable
What it is doing right now.
expiresAttimestamp
When the result is deleted.
Can fail with 404 not_found409 conflict422 validation_error429 rate_limited
GET/api/jobs/{jobId}/events

Follow a job over SSE

A Server-Sent Events stream, and the way to watch a job without polling. Current state is replayed on connect, so a client that attaches late — or after a job that finished in under a second — still sees the terminal event. The server closes the stream itself once the job completes or fails.

Path parameters

jobIduuid
From /api/jobs/create.
curl -N https://pdfflow.bondeth.site/api/jobs/b1946ac9-2492-4f6e-8b2c-7c1f0d55e3aa/events
Returns 200 — text/event-stream
Can fail with 404 not_found410 expired
GET/api/jobs/{jobId}/status

Poll a job

The small shape, for clients that cannot hold a stream open. Prefer the SSE endpoint where you can; this exists as the fallback, and it is what the web app itself falls back to.

Path parameters

jobIduuid
From /api/jobs/create.
curl -s https://pdfflow.bondeth.site/api/jobs/b1946ac9-2492-4f6e-8b2c-7c1f0d55e3aa/status

Returns 200 — Current status

iduuid
The job.
statusstring
pending, processing, completed, failed or expired.
progressinteger
0–100.
stagestringnullable
Current step.
errorMessagestringnullable
Set when status is failed.
Can fail with 404 not_found410 expired
GET/api/jobs/{jobId}

Read the full job record

Everything the status endpoint returns, plus filenames, output size and timestamps. Useful once a job is done and you want to describe the result.

Path parameters

jobIduuid
From /api/jobs/create.
curl -s https://pdfflow.bondeth.site/api/jobs/b1946ac9-2492-4f6e-8b2c-7c1f0d55e3aa

Returns 200 — The job record

operationstring
The tool that ran.
inputFilenamestringnullable
Display name of the input.
outputFilenamestringnullable
Name the download is served under.
outputSizeintegernullable
Bytes of the result.
createdAttimestamp
When it was queued.
completedAttimestampnullable
When it finished.
expiresAttimestamp
When the result is deleted.
Can fail with 404 not_found410 expired

Result

GET/api/download/{jobId}

Download the result

Streams the finished file. There is no listing and no index — a job id is the only way to reach a result, and the link stops working the moment the file is swept.

Path parameters

jobIduuid
A job whose status is completed.
# -OJ keeps the filename the server sends
curl -OJ https://pdfflow.bondeth.site/api/download/b1946ac9-2492-4f6e-8b2c-7c1f0d55e3aa

Returns 200 — The file, as a binary body

Content-Dispositionheader
attachment, with both the ASCII and RFC 5987 forms of the filename.
Content-Typeheader
application/pdf, application/zip, image/png or image/jpeg.
Cache-Controlheader
Always no-store.
Can fail with 404 not_found409 conflict — still processing, or the job failed410 expired

System

GET/api/health

Liveness and dependencies

Returns 200 while the database and Redis both answer, and 503 with status "degraded" when either does not. Safe to point a load balancer at.

curl -s https://pdfflow.bondeth.site/api/health

Returns 200 / 503 — ok, or degraded

statusstring
ok or degraded.
versionstring
API version.
databasestring
up or down.
redisstring
up or down.
GET/api/config

Client-visible limits

The limits the server is actually running with. Read these at startup instead of hardcoding them — they are deployment settings, not constants.

curl -s https://pdfflow.bondeth.site/api/config

Returns 200 — Current limits

maxUploadBytesinteger
Largest accepted file.
maxFilesPerJobinteger
Most files in one upload or job.
fileTtlMinutesinteger
How long anything survives.

Job lifecycle

A job moves through one of five statuses. Three of them are terminal: once you see one, stop asking.

pending
Queued, not picked up yet.
processing
A worker has it.
completed terminal
The result is ready to download.
failed terminal
Read errorMessage for the reason.
expired terminal
The retention window closed and the file is gone.

Stages, in order

  1. 0%

    queued

    Accepted, waiting for a worker.

  2. 10–30%

    preparing

    Inputs opened and validated.

  3. 30–80%

    merging / splitting / optimizing

    The operation itself. Which one depends on the tool.

  4. 80%

    writing

    The result is being written out.

  5. 90–100%

    finishing

    Result recorded, inputs deleted.

SSE event names

job_created
Sent once, when the job record exists.
job_progress
Sent on every progress change. Also the replayed opening frame.
job_completed
Terminal. The server closes the stream after this.
job_failed
Terminal. errorMessage carries the safe reason.

Errors

Every failure uses the same envelope. Match on code, not on the message — messages are written for people and will change. requestId also comes back as the X-Request-Id header, and quoting it is how a failure gets traced in the logs.

{
  "error": {
    "code": "expired",
    "message": "This result has expired and was deleted.",
    "requestId": "e69ea9ba8619"
  }
}
413file_too_large
A file was over maxUploadBytes.
415unsupported_file_type
The extension is not allowed, or the bytes do not match it.
422validation_error
Something in the request was not usable.
422invalid_request
The body did not match the schema at all.
404not_found
No such file, job or result.
409conflict
The job is not in a state that allows this — usually still processing.
410expired
It existed, and has since been deleted.
429rate_limited
Slow down and retry.
500internal_error
Something broke. The cause is logged, never returned.

Limits

These are the defaults. They are deployment settings, so read /api/config at startup rather than hardcoding them.

Largest file
100 MB
per file, not per request
Files per request
25
upload and job alike
Retention
30 minutes
inputs go as soon as the job ends
Uploads
30 / min
per client address
Job creation
60 / min
per client address

Accepted file types

The extension has to be on this list and the file's leading bytes have to match it. A .pdf that is really a ZIP is rejected with unsupported_file_type.

pdf
.pdf
image
.jpg, .jpeg, .png, .webp
office
.docx, .xlsx, .pptx

Prefer to poke at it live?

The generated schema and an interactive console are both still served straight off the API.

Nothing to sign up for

Bring a file.
Leave with a file.

Everything in between happens on our server and then stops existing.

© 2026 PDFFlow — no accounts, no tracking, no permanent storage.

API reference