ChatGPT resume parser: build it yourself, or use an API
Published
ChatGPT resume parser: building one yourself, and when an API is less work
Yes, you can build a ChatGPT resume parser, and for a prototype it takes an afternoon. Extract the text from the CV, send it to the OpenAI API with a JSON schema describing the fields you want, and read back an object. The model call is the easy part. The work that decides whether it holds up in production is everything around the call: getting text out of PDFs and scans, keeping dates in one format, stopping the model from filling gaps with guesses, and handling the call that fails.
This article is for the developer building a job board, an applicant tracking system or a candidate-matching tool who is weighing "just use ChatGPT" against buying a resume parser. It walks through the do-it-yourself build, the code, what the tokens cost, and the places it tends to break. Then it covers when a dedicated parsing API is less code to own.
We build ResumeJSON, a resume parser API, so read this as written by an interested party. Every statement about OpenAI's products below was read off OpenAI's own pages in September 2026, and those pages are theirs to change.
Pasting a CV into ChatGPT versus calling the API
There are two different things people mean by a ChatGPT resume parser, and only one of them is something you can ship.
- Pasting a resume into the ChatGPT app and asking for JSON works for one document. It is fine for checking what a model makes of a CV. It is not an integration: there is no endpoint, no schema enforcement, and nothing you can call from your upload handler.
- Calling the OpenAI API from your own code is the real build. Your server receives the upload, turns it into text, sends that text with a schema, and stores the result.
Everything below is about the second one.
The ChatGPT resume parser build, step by step
A working pipeline has five parts. Only one of them is the model.
- Text extraction. The API takes text (and images), so a PDF or DOCX has to be turned into text first. A text-layer PDF goes through a library such as pdfplumber or pdf.js. A DOCX goes through a DOCX reader. A scan or a phone photo of a page has no text layer at all and needs OCR or an image input, which is a separate path with its own failure modes. Our OCR resume parser article covers that case.
- A schema. You define the object you want back: basics, a list of roles, education, skills.
- The call, with the schema attached so the reply is constrained to it.
- Normalisation. The model returns what the CV says. "Summer 2019", "06/19" and "June 2019" all have to become one sortable format if you want to order roles by date.
- Validation and retries. A timeout, a rate limit or a refusal has to end in a state your application can show, not an empty candidate record.
Structured Outputs is what makes the schema hold
The part that changed this build from fragile to reasonable is OpenAI's Structured Outputs feature. OpenAI's own guide describes it this way, as of September 2026:
Structured Outputs is a feature that ensures the model will always generate responses that adhere to your supplied JSON Schema, so you don't need to worry about the model omitting a required key, or hallucinating an invalid enum value.
The same guide notes that the Python and JavaScript libraries let you define the schema with pydantic.BaseModel and z.object respectively, and that safety-based refusals are programmatically detectable. So you no longer have to parse JSON out of prose or retry on a missing brace.
Read that promise carefully, though: it guarantees the SHAPE, not the values. A schema that requires start_date as a string gets a string every time. It does not stop the string being "Summer 2019", and it does not stop the model inventing a date for a role that had none, because a required field has to be filled with something.
The code
A minimal Python version, using the pattern OpenAI's guide shows:
from openai import OpenAI
from pydantic import BaseModel
class Role(BaseModel):
company: str | None
title: str | None
start_date: str | None # ask for YYYY-MM in the instructions
end_date: str | None
is_current: bool
class Resume(BaseModel):
full_name: str | None
email: str | None
phone: str | None
work: list[Role]
skills: list[str]
client = OpenAI()
def parse_resume(cv_text: str) -> Resume:
response = client.responses.parse(
model="<the model you choose>",
input=[
{"role": "system", "content": (
"Extract the resume into the schema. "
"Dates as YYYY-MM or YYYY. "
"If the CV does not state a value, return null. Never guess."
)},
{"role": "user", "content": cv_text},
],
text_format=Resume,
)
return response.output_parsedThree details in that snippet do most of the work:
- Every field is nullable. If a field is required and non-null, the model must put something in it, and "something" for a CV with no phone number is an invented phone number.
- The instructions say "never guess". That reduces invention. It does not remove it, so a production build still checks values against the source text where it matters.
is_currentis its own field. Without it, a current job and a job whose end date was simply left off look the same in your data.
What the snippet leaves out is the rest of the pipeline: the PDF and DOCX extraction in front of it, a date normaliser after it, and retry logic around it. Those are usually several times the size of the model call.
What a ChatGPT resume parser costs per CV
OpenAI prices the API by token. As of September 2026 its pricing page lists three GPT-6 models at standard rates:
| Model | Input, per 1M tokens | Cached input, per 1M tokens | Output, per 1M tokens |
|---|---|---|---|
| GPT-6 Astra | $10.00 | $1.00 | $50.00 |
| GPT-6 Sol | $2.00 | $0.20 | $10.00 |
| GPT-6 Luna | $0.10 | $0.01 | $0.50 |
The same page offers batch processing at 50% off and data residency at 10% more.
A two-page CV plus your instructions is a few thousand input tokens, and the JSON back is usually under a thousand. On the cheapest model that is a fraction of a cent per CV; on the most capable one it is a few cents. Your own token counts are what to budget from, and OpenAI's pricing page is the source for the rates.
The token bill is rarely the expensive part, though. The real cost of the build is the engineering time on extraction, normalisation and error handling, and the maintenance of that code every time a CV arrives in a shape you had not seen.
Where a do-it-yourself ChatGPT resume parser breaks
These are the problems that show up after the prototype works on your own CV.
- Scans and photos. A PDF that is a picture of a page has no text for a text extractor to find. You need an OCR step or an image input, and a separate test set for it.
- Two-column layouts. A text extractor can interleave the left and right columns line by line, so the model receives "Python Senior Engineer React Acme Corp" and has to untangle it.
- Dates. "Present", "Now", "current", "2019 to date", ranges with no month. Each needs one rule, applied the same way every time.
- Invention. A model asked for
total_years_experiencewill compute one even from a CV with gaps and missing dates. Decide in advance which fields you trust as computed and which must be read. - Latency. Your upload handler now waits on text extraction plus a model call. Measure it on real CVs before deciding to parse inline instead of in a queue.
- Failures. A timeout or a refusal must not become a blank candidate profile that looks like a real one. Store the failure as a failure.
None of these are reasons not to build it. They are the list of what you will be maintaining if you do.
When to keep ChatGPT for resume parsing
Stay with your own OpenAI build if any of these is true:
- You need fields no resume parser returns. Custom scoring against a job description, a summary in your own house style, a classification specific to your market. That is a model task, and you should own the prompt.
- CVs are one small part of a larger LLM pipeline you already run, with extraction, retries and monitoring in place. Adding one more schema is cheap.
- Volume is tiny and a person reviews every result. A recruiter checking ten CVs a week does not need an integration.
- You want full control over which model runs and where the data is processed. With the API you choose the model and the processing region yourself.
When a resume parser API is less work
A dedicated parser is the better fit when what you want is the standard resume object, reliably, from whatever file a candidate uploads, without owning the pipeline around the model.
That is the job ResumeJSON does. You send a PDF, a DOCX, plain text, or a photo or scan of the page, and get back typed JSON: contact details, every role with dates as YYYY-MM or YYYY, is_current: true on a role with no end date, education, skills, certifications and languages. A field the CV does not state comes back null rather than guessed. Median parse time is 2.2 seconds, measured against the live endpoint, and every response carries an X-Parse-Ms header so you can see it yourself.
| Your own ChatGPT build | ResumeJSON | |
|---|---|---|
| File types | Whatever extraction you write | PDF, DOCX, text, scan or photo |
| Schema | Yours to design and version | Fixed, documented typed resume |
| Date format | Yours to normalise | YYYY-MM or YYYY, always |
| Missing values | Depends on your schema and prompt | null, never guessed |
| Custom fields | Anything you can prompt for | The standard resume fields only |
| Cost | Tokens plus your engineering time | Per parse, from a free tier |
| Maintenance | You own it | We own it |
Pricing is published in full on the docs, billed through RapidAPI: a free Basic plan capped at 100 parses a month, Pro at $0 a month and $0.05 a parse, Ultra at $29 a month for 1,000 parses then $0.045 each, and Mega at $99 a month for 5,000 parses then $0.018 each.
The trade is control against code. Your own build can return anything you can describe. A parser returns one well-defined object and nothing else, and in exchange you write none of the pipeline.
How to switch from a ChatGPT parser to an API
If you already have an OpenAI build and want to try a parser alongside it, keep it cheap to compare:
- Pick 50 real CVs, including your worst: scans, two-column layouts, CVs in a second language.
- Run both on the same files and diff the fields you actually use: names, roles, dates, skills.
- Map the output onto your schema. If your own schema was modelled on JSON Resume, most fields line up one to one.
- Keep your model call for the custom part. A common end state is a parser for the standard fields and one small model call for the scoring or summary only you need.
You can try a CV without signing up on the free resume parser page, and our CV parser API comparison covers the other hosted options if you want to compare more than two.
The short answer
A ChatGPT resume parser is a good build when the output you need is custom, or when you already run an LLM pipeline and CVs are one more document type. For the standard resume object from any uploaded file, the model call is the small part, and a parser API removes the extraction, normalisation and failure handling you would otherwise own. Try both on your worst fifty CVs and let the diff decide.