Extract a Resume
The resume type turns a CV into a candidate record: who the person is, where they worked and
studied, and what they can do. Where the invoice schema is mostly scalars, this one is mostly
arrays, which changes how you consume it.
curl -X POST https://cloud.unidoc.io/api/uniai/extract/resume \
-H "X-API-KEY: $UNIDOC_LICENSE_API_KEY" \
-F "[email protected]"The path segment is resume. cv is not a valid type, even though the documents are the same
thing, and /extract/cv returns
{"success":false,"message":"unsupported structured type: cv","errors":null}.
Fields
| Field | Type | Holds |
|---|---|---|
name | string | Full name. |
contact | object | email and phone. |
summary | string | Objective or profile paragraph. |
educations | array | institution, degree, field_of_study, start_date, end_date. |
work_experiences | array | company, position, start_date, end_date, responsibilities. |
skills | array of strings | One entry per skill. |
certifications | array | name, issuing_organization, issue_date, expiration_date. |
languages | array | language and proficiency. |
projects | array | title, description, start_date, end_date, role, technologies. |
references | array | name, relationship, and a nested contact_info. |
additional_info | object | Anything else the document carried. |
Note the plurals: educations and work_experiences, not education and experience.
Every array entry allows extra keys beyond the ones listed, so a resume with an unusual field in its work history will carry it through rather than dropping it.
responsibilities is a single string, not an array, even when the source is a bullet list. The
bullets arrive as one block of text with the model’s own line breaks.
A worked response
{
"name": "Jordan Fields",
"contact": {
"email": "[email protected]",
"phone": "+1 555 0143"
},
"summary": "Backend engineer with eight years on distributed systems.",
"work_experiences": [
{
"company": "Northwind Systems",
"position": "Staff Engineer",
"start_date": "2021-04-01",
"end_date": "2026-02-01",
"responsibilities": "Led the migration off the monolith. Owned the ingest pipeline."
}
],
"educations": [
{
"institution": "University of Leeds",
"degree": "BSc",
"field_of_study": "Computer Science",
"end_date": "2017-06-01"
}
],
"skills": ["Go", "PostgreSQL", "Kubernetes"],
"languages": [
{ "language": "English", "proficiency": "native" }
]
}No certifications, projects, references or additional_info, because that resume had
none. Absent rather than empty arrays, which matters if you are iterating without checking.
Dates are the weak point
Resumes write dates as 2021, Apr 2021, 04/2021, Spring 2021 and present, and the
schema wants a date string. Expect:
- A day-precision value invented for a month-precision source, so
2021-04becomes2021-04-01. - A missing
end_dateon a current role, where the source said “Present”. - A year-only value passed through as written.
If you are computing tenure or sorting a career history, normalize these yourself and treat day precision as untrustworthy. Do not use the dates to decide seniority without a human looking.
Reading it safely
Arrays are absent when empty, so range over them only after checking, or decode into a type where the zero value is a usable empty slice:
type Contact struct {
Email *string `json:"email"`
Phone *string `json:"phone"`
}
type Resume struct {
Name *string `json:"name"`
Contact *Contact `json:"contact"`
WorkExperiences []map[string]any `json:"work_experiences"`
Skills []string `json:"skills"`
}A nil slice ranges zero times in Go, so work_experiences being absent is harmless there. In
languages where indexing a missing key throws, guard it.
Limitations
Three pages maximum, which is a real constraint on resumes; academic CVs with publication lists routinely run longer and will be rejected. Send the first three pages.
Nothing is verified. Employment dates, degrees and certifications come out as the document claims them. This is extraction, not verification, and using it for eligibility or screening decisions without review is a bad idea for both accuracy and fairness reasons.
Resumes are personal data. The document is uploaded and its text is passed to a third-party model provider, which is a processing step you may need a lawful basis and a subprocessor disclosure for. See does UniAI send my documents anywhere?
Two-column layouts and heavy design are where extraction quality drops, since the text comes
out in reading order that may interleave the columns. A scanned resume needs
is_scanned=true; see scanned documents.