API reference
The stateless resume parsing API served on resume-parser.affinda.com.
Post a file, get the parsed resume back. Nothing is stored: the API keeps no copy of your file and no record of the result, so there is nothing to poll, list or delete afterwards. One request is one parse, and one successful parse costs one credit no matter how many pages the document has.
The same API is available as a self-hosted container, on the same /v1/resumes/parse path.
Moving between the two is a change of base URL and authentication. Where the two differ, the
parameter or response below says so.
Base URLs
Your account, its credits and its API keys live in one region. Use the host for that region.
| Region | Base URL |
|---|---|
| Asia Pacific and global | https://resume-parser.affinda.com |
| United States | https://resume-parser.us1.affinda.com |
| Europe | https://resume-parser.eu1.affinda.com |
Authentication
An API key created on the console's API keys screen, sent as
Authorization: Bearer aff_.... A key is shown in full exactly once, when it is
created, and you may hold three at a time.
Keys belong to a user and to the region they were created in. A key made on
resume-parser.eu1.affinda.com does not authenticate against the Asia Pacific host, and
the reverse is equally true. Sign in on the host for your region and create the key
there.
Do not call this endpoint from browser code. The key would be visible to anyone who
opened the page, and it also works against app.affinda.com.
Endpoints
Parse a resume
POST/v1/resumes/parse
Parses one resume and returns the extracted fields as JSON.
Send the file either as a multipart/form-data body with the file in a field named
file, or as the raw bytes of the file as the whole request body. The raw form is what
the self-hosted container accepts, so accepting it here too means moving between the two
is a change of base URL and authentication and nothing else. With a raw body, name the
file in a Content-Disposition: attachment; filename="cv.pdf" header; without one the
parser is told the file is called resume.
There is no JSON or base64 intake. A file up to 20 MB is accepted; anything larger is rejected before the parser is called and costs no credit.
A raw body needs a content type that is not application/x-www-form-urlencoded. That one
is parsed as a form, which carries no file, and is answered 400 missing_file.
The path has no trailing slash. POST /v1/resumes/parse/ is not an alias and is not
redirected.
Request body
Accepts application/octet-stream, application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, multipart/form-data, text/plain.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
organization | query | optional | The identifier of the organization to charge for the parse. Optional when your account
has exactly one Resume Parser organization, which is the usual case; required when it
has more than one, so that the charge is never guessed. An identifier belonging to an
organization you are not in, or to one of your organizations on the main Affinda
platform rather than the Resume Parser, is answered |
language | query | optional | Only the literal |
Content-Disposition | header | optional | Names the file when the request body is the raw bytes of the document, as
|
Responses
| Status | Meaning |
|---|---|
200 | The parsed resume. |
400 | The request did not carry a usable file, or names no organization when it had to.
|
401 | The API key is missing, malformed, unknown, expired, or blocked by your organization's IP allowlist. Also returned when a key from another region is used. |
402 | The organization has no parse credits left, or its credits have expired. The parser is not called and nothing is charged. Top up in the console's Billing screen. Note this is a 402 on this API specifically. The same condition is a 403 on the main Affinda platform API. |
403 | The caller was recognised but not allowed. In practice this is an account that requires multi-factor authentication and has none set up; finish setting up MFA in the console. |
404 | The |
405 | Only POST is implemented on this path. |
413 | The file is larger than the 20 MB limit. The size is checked against the declared
|
415 | The document is not a format the parser reads. Intake is lenient: if a file might hold text, it is attempted. PDF, DOCX, ODT, XLSX, ODS,
legacy |
422 | The file was read but no resume could be extracted from it. A file with no text signal
and a password-protected document both land here. Remove the password and retry in the
second case. A zero-byte upload never reaches the parser and is answered
|
429 | Too many requests. The limits are 6,000 parses per minute per organization and 6,000 per
minute per API key, and the parser itself sheds load when its queue is full. Wait
|
500 | Something failed unexpectedly. The detail is recorded on our side, not returned. |
502 | The parser service could not be reached. The request is retried once on a fresh connection before this is returned, because a parse is idempotent and a dropped connection means it never arrived. No credit is charged. |
503 | The parser instance is shutting down. Retry. |
504 | The parse did not finish in time. The cloud waits 30 seconds for the parser, and the
parser applies its own 28 second deadline, so in practice the parser's |
Parse a resume (alias)
POST/api/v1/resumes/parse
The same endpoint as POST /v1/resumes/parse, for callers whose HTTP client expects an
/api prefix. It is an alias and not the canonical path: prefer /v1/resumes/parse,
which is also the path the self-hosted container serves.
Request body
Accepts application/octet-stream, multipart/form-data.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
organization | query | optional | The identifier of the organization to charge for the parse. Optional when your account
has exactly one Resume Parser organization, which is the usual case; required when it
has more than one, so that the charge is never guessed. An identifier belonging to an
organization you are not in, or to one of your organizations on the main Affinda
platform rather than the Resume Parser, is answered |
language | query | optional | Only the literal |
Content-Disposition | header | optional | Names the file when the request body is the raw bytes of the document, as
|
Responses
| Status | Meaning |
|---|---|
200 | The parsed resume. |
400 | The request did not carry a usable file, or names no organization when it had to.
|
401 | The API key is missing, malformed, unknown, expired, or blocked by your organization's IP allowlist. Also returned when a key from another region is used. |
402 | The organization has no parse credits left, or its credits have expired. The parser is not called and nothing is charged. Top up in the console's Billing screen. Note this is a 402 on this API specifically. The same condition is a 403 on the main Affinda platform API. |
The parsed resume
A successful parse answers with two keys, data and meta. These are the members of data.
Schema v1 is sparse. Null members and objects emptied by trimming are dropped before the
response is sent, so a key you do not see was not found rather than found empty. Arrays
are always present, including when empty, and data and meta are the only two keys
guaranteed at the top level.
Certifications are lifted out of education rather than nested inside it: an education
record whose level is a course, certificate or conference is re-emitted as a
certifications entry.
rawText, redactedText and headshot are large. Ask for them only if you use them.
| Field | Type | Notes |
|---|---|---|
person required | object | Name parts, date of birth, nationalities and location. |
contact required | object | Emails, phone numbers and websites. |
summary | string | |
objective | string | |
workExperience required | array of object | |
education required | array of object | |
certifications required | array of object | |
skills required | array of object | |
languages required | array of object | |
projects required | array of object | |
publications required | array of object | |
referees required | array of object | |
achievements required | array of string | |
associations required | array of string | |
interests required | array of string | |
patents required | array of string | |
availability | string | |
workAuthorization | string | |
willingToRelocate | boolean | |
expectedSalary | string | |
preferredWorkLocations required | array of Location | |
employmentMetrics required | object | Totals derived from the work history, including tenure figures and employment gaps. |
headshot | string | Base64 image, when one was found. |
rawText | string | |
redactedText | string |
Document metadata
meta describes the parse rather than the person. meta.schemaVersion is the string 1.0, meta.timing.totalMs is the server-measured wall time, and meta.document carries the classification and quality readings below.
How well text came out of this document. Populated by default.
| Field | Type | Notes |
|---|---|---|
band required | one of high, medium, low | |
score required | number |
Present only when parsing continued past a quality gate that would otherwise have stopped it.
| Field | Type | Notes |
|---|---|---|
code required | one of degenerate_ocr, low_text_quality, ocr_truncated | |
message required | string |
Present when the document went through OCR. OCR reads the first three pages only, and
truncated says whether more existed. Text documents contribute fields from their first
twenty pages.
| Field | Type | Notes |
|---|---|---|
routed required | boolean | |
sourcePageCount required | integer | |
processedPageCount required | integer | |
pageLimit required | integer | |
truncated required | boolean | |
pixelLimit required | integer | |
downscaledPageCount required | integer | |
downscaledAttemptCount required | integer |
Errors
error is a message safe to show a person, and code is the stable identifier to branch
on. retryAfterSeconds is present when waiting is the right response, and the
Retry-After header carries the same number.
| Field | Type | Notes |
|---|---|---|
error required | string | |
code required | one of missing_file, organization_required, invalid_request, unauthorized, no_credits, forbidden, organization_not_found, method_not_allowed, file_too_large, payload_too_large, unsupported_format, password_protected_document, missing_file_part, unparseable_document, rate_limited, queue_full, queue_timeout, ocr_queue_full, internal_error, internal, parser_unavailable, shutting_down, parser_timeout, deadline | |
retryAfterSeconds | integer |