Affinda Resume Parser Open console

Self-hosted

The parser also ships as a Docker image. It runs the same models as the cloud API and answers the same path with the same JSON, so moving between them is a change of base URL and authentication.

Run it when documents must not leave your network, when you want a fixed cost rather than a per-parse one, or when you need the parser next to something that has no internet access. It verifies its own license offline: no telemetry, no call home.

Start it

docker run -d --name affinda-parser \
  -e AFFINDA_LICENSE_TOKEN='<your-license-token>' \
  -p 8080:8080 \
  affinda/resume-parser

The image is public, so there is no registry login. affinda/resume-parser is the current CPU release; affinda/resume-parser:latest-gpu is the GPU build.

Then parse, with no Authorization header:

curl -X POST http://localhost:8080/v1/parse \
  -F "file=@resume.pdf"

For durable state, mount a volume. The container counts its own parses in /var/lib/affinda, and a container started with --rm and no volume loses that count when it is removed:

docker run -d --name affinda-parser \
  -v affinda-state:/var/lib/affinda \
  -e AFFINDA_LICENSE_TOKEN='<your-license-token>' \
  -p 8080:8080 \
  affinda/resume-parser

Getting a license

Without a license the container runs in evaluation mode: 1,000 successful parses, then 402 entitlement_required. That is the same allowance a new cloud account gets, so you can compare the two on the same documents.

A free trial license raises the total to 11,000 parses and lasts a year. Get one at https://resume-parser.affinda.com/self-hosted/get-your-key, which sends you to the console for your region. You need a verified email address, and one license is issued per organization: ask twice and you get the same license back.

Two ways to apply it:

  • AFFINDA_LICENSE_TOKEN=<token> at start, as above. Better, set AFFINDA_LICENSE_TOKEN_FILE=/run/secrets/affinda-license and mount the token as a file. When both are set the file wins. Neither is printed in the logs.
  • Without a restart, on a container that is already running:
docker exec affinda-parser affinda-license apply '<your-license-token>'

A token that is invalid, expired or unreadable stops the container at startup rather than quietly falling back. Leaving the variable unset is what keeps it in evaluation mode.

Each container counts independently, in its own state volume. Do not point two running replicas at the same volume.

Running on a GPU

CPU is the simple default and is fine at low and moderate volume: a 16-core host measures about 3,900 parses/hour, and 8 GB of RAM is recommended.

For sustained high volume, run affinda/resume-parser:latest-gpu on a GPU host. It is not just faster, it is cheaper per document. The box we certify against is one NVIDIA L4, which on AWS is g6.2xlarge. On our certification mix (89% text PDF, 10% DOCX, 1% scanned) one such box sustained about 171,000 parses/hour with zero rejected requests, which at the US East on-demand price we measured against (about $0.98/hour) works out to roughly 175,000 resumes per dollar. That is more than an order of magnitude cheaper per document than the same volume on CPU hosts, so if you are chasing the lowest cost at high volume, GPU is the answer.

Two caveats before you size a fleet from those numbers. The scanned fraction dominates throughput: the same box measured about 81,000 parses/hour when 10% of the documents were scans. And cloud prices move. Treat throughput and cost as roughly ±15%, run your own document mix past one box first, and scale out with identical boxes rather than one bigger one.

What is different from the cloud

Cloud Container
Base URL https://resume-parser.affinda.com http://localhost:8080
Parse path /v1/resumes/parse /v1/parse, and /v1/resumes/parse as an alias
Authentication Authorization: Bearer <key> None. The license sits in the container.
Billing One credit per successful parse Licensed. Credits are not involved.
Exhausted allowance 402 no_credits 402 entitlement_required
Request size cap 20 MB 50 MB by default
Response headers X-Credits-Remaining X-Affinda-License, X-Affinda-Parses-Remaining

The response body is the same in both. The cloud returns the container's bytes unchanged, which is why The parse response applies equally to a self-hosted deployment.

What the container adds

It has its own API surface that the cloud does not expose:

Endpoint What it answers
GET /health, GET /ready Liveness and readiness, for your orchestrator.
GET /version The build, the model id and the inference backend.
GET /formats Which formats this container accepts, as it is configured right now.
GET /stats Throughput and queue counters.
GET /license License tier, expiry and parses remaining.

It also accepts query parameters on the parse path that the cloud endpoint does not forward, including filename, confidence, debug=timings and diagnostic=1. Use GET /formats and these parameters when you are debugging a document: they answer questions about your own deployment that the cloud endpoint cannot.

The container's own reference lives in the image, alongside its changelog and troubleshooting notes.

Configuration worth knowing

Variable Default What it does
AFFINDA_PORT 8080 Listen port.
AFFINDA_LICENSE_TOKEN, AFFINDA_LICENSE_TOKEN_FILE unset The license. The file form wins.
AFFINDA_WORKERS sized from the host How many parses run at once.
AFFINDA_OCR on OCR for scans and images. With it off, images are rejected with 415 unsupported_format.
AFFINDA_STRICT_INTAKE unset Set to 1 for a stricter posture that also rejects HTML, RTF and legacy .doc. Unset is the recommended default.

Everything else has a working default. Size the host by parse volume rather than by document size, and see Running on a GPU once volume is the thing you are sizing for.