Affinda Resume Parser Open console

Quickstart

One request, two ways to run it. The cloud API and the self-hosted container expose the same path and return the same JSON. Only the base URL and the authentication differ.

1. Get an API key

Sign in to the console on the host for your region and open the API keys screen at /keys. On the Asia Pacific host that is https://resume-parser.affinda.com/keys.

New accounts start with a 14-day trial of 1,000 free documents. A key is shown in full exactly once, when you create it, so copy it then. See Authentication for the details.

2. Send a resume

Post the file to /v1/resumes/parse with your key in the Authorization header. Replace YOUR_API_KEY with the key you just created.

curl

curl -X POST https://resume-parser.affinda.com/v1/resumes/parse \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@resume.pdf"

Python

import requests

url = "https://resume-parser.affinda.com/v1/resumes/parse"
headers = {"Authorization": "Bearer YOUR_API_KEY"}

with open("resume.pdf", "rb") as f:
    response = requests.post(url, headers=headers, files={"file": f})

response.raise_for_status()
resume = response.json()["data"]
name = resume["person"]["name"]
print(name.get("given"), name.get("family"))

TypeScript

const form = new FormData();
form.append("file", file, "resume.pdf");

const response = await fetch("https://resume-parser.affinda.com/v1/resumes/parse", {
    method: "POST",
    headers: { Authorization: "Bearer YOUR_API_KEY" },
    body: form,
});

if (!response.ok) throw new Error(await response.text());

const { data, meta } = await response.json();
console.log(data.person.name, meta.document.classification);

If a multipart form is inconvenient, send the file's bytes as the whole request body instead. Name the file in a Content-Disposition header and give the body a content type, because a body sent as application/x-www-form-urlencoded carries no file and is answered 400 missing_file.

curl -X POST https://resume-parser.affinda.com/v1/resumes/parse \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/pdf" \
  -H 'Content-Disposition: attachment; filename="resume.pdf"' \
  --data-binary @resume.pdf

There is no JSON or base64 intake. Post the file, not a wrapper around it.

3. Read the response

You get { "data": ..., "meta": ... } back. data holds the person and contact block, work history with date precision, education, certifications, skills and languages. meta holds meta.document.classification, which says whether the parser believes this was a resume at all, and meta.document.extractionQuality, which says how cleanly the text came out.

{
  "data": {
    "person": { "name": { "given": "Alex", "family": "Rivera" } },
    "contact": { "emails": ["alex.rivera@example.com"] },
    "workExperience": [
      { "jobTitle": "Senior Backend Engineer", "organization": "Northwind Data" }
    ]
  },
  "meta": {
    "schemaVersion": "1.0",
    "document": {
      "classification": { "label": "resume", "confidence": 0.683 },
      "extractionQuality": { "band": "high", "score": 0.9384 }
    }
  }
}

That is the real shape, cut down hard. The parse response walks through the whole thing.

Two habits worth forming now:

  • Read fields defensively. A field the parser did not find is left out rather than returned as null, so use .get() in Python and optional chaining in TypeScript.
  • Read X-Credits-Remaining. The response header carries your balance after the parse was charged. One successful parse costs one credit whatever the page count, and a failed parse costs nothing.

Nothing is stored: there is no document to fetch afterwards.

The same request, self-hosted

Point the same code at your container and drop the Authorization header. The container is licensed at the container, so its requests carry no bearer token.

docker run -d --name affinda-parser \
  -e AFFINDA_LICENSE_TOKEN='<your-license-token>' \
  -p 8080:8080 \
  affinda/resume-parser
curl -X POST http://localhost:8080/v1/resumes/parse \
  -F "file=@resume.pdf"

The container answers both /v1/resumes/parse and its own shorter /v1/parse, so the only edits are the base URL and the missing header. Self-hosted covers the license, the remaining differences and what the container adds.

Next