Bulk resume parsing: how to run a CV backlog
Published
Bulk resume parsing: how to run a backlog of CVs without blowing the bill
Bulk resume parsing is almost never a bulk endpoint. Every major parsing API in this category, ours included, takes one document per request, so "parse 40,000 CVs" is a loop you write, a concurrency number you pick, and a retry policy you own. Get those three right and a backlog of 40,000 finishes overnight for a predictable amount of money. Get them wrong and you pay twice for the same documents.
This article is for the developer who has an archive to ingest: a job board importing a candidate database, an applicant tracking system onboarding a customer, a matching prototype that needs 10,000 profiles before it can be evaluated. It covers what the vendors actually allow, what a failed call costs you, how to size the run before you start it, and where a per-parse API is cheaper than a credit plan for this specific shape of work.
We build ResumeJSON, one of the APIs in the comparison, so read this as written by an interested party. Every claim about somebody else's product below was read off that vendor's own pages in September 2026, and their pages are theirs to change.
The first thing to check: does the vendor let you go parallel?
This is the question that decides your wall-clock time, and it is answered in documentation rather than on the pricing page. It is worth reading before you write the loop, because the answer is sometimes no.
Textkernel's developer FAQ, as of September 2026, is explicit about it:
Routine parsing of batches of less than 100,000 documents MUST always be done serially (one-at-a-time).
Read that as a real constraint on a migration plan. At one document at a time, a run's duration is the number of documents multiplied by the per-document latency, with no lever to pull. If the per-document time is two seconds, 40,000 documents is about 22 hours of continuous serial calling. If it is ten seconds, the same backlog is five days.
The other half of that policy is what happens when you ignore it. Textkernel's platform documentation states that a request refused with HTTP 429 for exceeding the concurrency allowance in their acceptable use policy is still charged, and that resubmitting it is the caller's job. So a loop that opens twenty connections to go faster can spend real credits on refusals and finish later than the serial loop it replaced.
What a failed call costs
Most bulk runs are sized on the happy path, and the failures are what break the estimate. Three kinds of failure cost money, and they are worth separating before you start:
- Refusals you caused. A file that is not a CV, a corrupt upload, an unsupported type. Some vendors charge these, some do not. Ours does, because the gateway meters every call it forwards to us whatever we answer, and our API reference says so rather than leaving you to discover it.
- Refusals the vendor caused. A rate-limit rejection, a queue overflow, a temporary upstream failure. These are the expensive ones, because the natural reaction is a retry, and a retry of a charged refusal doubles the charge.
- Silent partials. A parse that returns a document with half the employment history missing because the file was a scan and OCR was never enabled. This costs nothing extra on the invoice and a great deal later, which is why it belongs in the plan.
The practical rule: make your loop idempotent on your side. Keep a table with one row per source document, a status column, and the request id of the call that produced it. Re-running the job then skips what already succeeded, and a retry is something you decide rather than something a catch block does for you. Every parsing API in this category returns a request id you can store; ours puts it in the response body and in an X-Request-Id header.
Sizing the run before you start it
A bulk job has four numbers. Work them out on paper first, because three of them are cheap to change before the run and expensive to change during it.
| Number | How to get it | Why it bites |
|---|---|---|
| Documents | Count the archive, not the rows | Duplicates and non-CV attachments are routinely 10% to 30% of an old archive |
| Per-document time | Parse 50 real files from the archive and take the median | A vendor's advertised latency is measured on their own sample, and scanned files are slower than text ones |
| Concurrency allowed | The vendor's acceptable use policy, not the pricing page | Exceeding it can be charged, and the limit is often 1 |
| Price of one document | Base price plus every add-on you will actually switch on | A credit is not a parse when normalization and geocoding are billed on top |
The fourth one is where credit plans and per-parse plans diverge. On a credit platform, a parse is a base cost and then a stack of add-ons. Textkernel's published transaction-cost table, as of September 2026, prices a resume or job parse at 1 credit, then adds 0.2 credits for job title normalization, 0.1 for skills normalization, 1 for geocoding with default options (0.1 if you supply your own provider key), 0.05 for resume OCR, and 0.1 for its LLM parser. Switch on two normalizations and default geocoding and the same document is 2.3 credits rather than 1, so a 40,000-document run is 92,000 credits.
That arithmetic is not a criticism of credits. It is a reason to do it before the run rather than after the invoice.
What the bulk run costs on each shape of plan
| Credit plan (Textkernel) | Per-parse API (ResumeJSON) | |
|---|---|---|
| Buying model | Credits purchased in advance, monthly plans from 500 to 25,000 credits, annual up to 100,000 | Pay per use, or a monthly quota with priced overage |
| Entry price | Professional, from $99 a month | $0 pay-per-use at $0.05 a parse |
| Intro offer | Accelerator, 5,000 credits for $200 on a first purchase | None |
| Larger volume | Enterprise plan by quote, higher rate limits included | $99 a month covers 5,000 parses, then $0.018 each |
| Add-ons | Normalization, geocoding, OCR and LLM parsing billed on top | None offered, so one parse is one unit |
| Concurrency | Serial required for batches under 100,000 documents | No documented serial requirement |
| Scans | OCR is a priced add-on | Accepted at the same price |
Both shapes have a run size where they win. A credit plan wins when you need what the credits also buy: normalized skills and job titles against a taxonomy, matching, job posting parsing, a support relationship with a quote behind it. A per-parse API wins when the backlog is the whole job and the output is the fields the CV plainly states.
Who should stay on a credit platform for their bulk run:
- You parse in many languages and need a published list of them. Textkernel lists resume parsing in 29 languages by name. We publish no equivalent list.
- Your run has to produce normalized job titles or skills mapped to a taxonomy such as O*NET or ISCO. That is a product, and it is not one we sell.
- Your contract fixes where documents are processed. Textkernel lists availability in the US, EU and APAC and documents a self-hosted installation. We host in one place and offer no region choice.
- The run is the start of an enterprise relationship rather than a one-off import. A quote, higher rate limits and an account manager are the Enterprise tier's whole point.
If one of those is load-bearing, stop reading here: the cheaper per-document number below is the wrong number for you.
Running the backlog against a per-parse API
For the narrower job, the loop is short and the bill is easy to predict. A practical shape, in any language:
- Enumerate the archive into a queue table with a status column. Never drive the loop straight off a directory listing, because a crash then restarts from zero.
- Filter before you pay. Drop zero-byte files, duplicates by content hash, and anything over the size limit. On our API that limit is 20 MB per upload, which covers a photographed CV at flagship phone resolution.
- Pick a concurrency and hold it. Start at 4 and watch the error rate before raising it. Faster is worth nothing if a fraction of the calls come back refused and charged.
- Watch the quota headers rather than the clock. Our gateway returns the plan quota, the remaining count and the seconds until the window resets on every response. A loop that reads
x-ratelimit-requests-remainingknows it is about to run out an hour before it does. - Treat a 429 as a stop, not a retry. On our listing a 429 means the monthly quota is spent. Retrying spells the same answer more expensively. Wait for the reset or move up a plan.
- Record failures with their code.
unreadable_file,not_a_resumeandtoo_largeare your archive telling you something true about itself, and they are usually worth fixing in the queue rather than in the loop.
A worked example at our published prices: a 40,000 CV backlog on the $99 plan uses the 5,000 included parses and then 35,000 at $0.018, which is $630 plus the $99, so $729 for the run. On pay-per-use at $0.05 the same backlog is $2,000. The plan is worth taking for a single large import and cancelling afterwards, and it is worth saying plainly that a job this size is where the monthly plan beats our own pay-per-use rate by a wide margin.
The mistakes that cost the most
- Running the whole archive before parsing fifty documents from it. A sample tells you the real median time, the real failure rate and whether the files are scans. It costs a few dollars and it is the only way to size the run honestly.
- Retrying blindly. A retry policy that does not distinguish a definitive refusal from a transient one turns one charged failure into five.
- Ignoring the acceptable use policy. Concurrency limits are not advisory when exceeding them is billable.
- Enabling add-ons you will not use. Normalization and geocoding are useful products and they are priced per document. Switching them on for a migration you will not query that way multiplies the run's cost by two.
- Parsing the same archive twice. Without a status table you will, usually a week later, after somebody asks for one more field.
Where to go next
If you are choosing the API rather than sizing the run, our CV parser API comparison covers what each option returns and what it charges. The plan-level detail behind the credit column above is in Textkernel pricing. If the archive is full of scans, OCR resume parser covers what changes when the CV is an image. And if you want to try one document before you write the loop, the free parser takes a file and returns the JSON with no signup.