Skip to content
Extract a Resume

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

FieldTypeHolds
namestringFull name.
contactobjectemail and phone.
summarystringObjective or profile paragraph.
educationsarrayinstitution, degree, field_of_study, start_date, end_date.
work_experiencesarraycompany, position, start_date, end_date, responsibilities.
skillsarray of stringsOne entry per skill.
certificationsarrayname, issuing_organization, issue_date, expiration_date.
languagesarraylanguage and proficiency.
projectsarraytitle, description, start_date, end_date, role, technologies.
referencesarrayname, relationship, and a nested contact_info.
additional_infoobjectAnything 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-04 becomes 2021-04-01.
  • A missing end_date on 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.

Last updated on