Resume Parser
Programming language

Parse Resumes with Python

Extract structured resume data in Python with a single API call

Parse PDF resumes with Python using the Resume Parser REST API. Setup with the requests library, rate-limit friendly batch processing, error handling, and structured JSON output.

Setup steps

  1. 1

    Get your API key

    Sign up at https://api.resumeparser.cc/signup with your email. You get a magic link, and you create an API key (rp_live_...) in the dashboard. Keep the key on your server and never expose it in client-side code.

  2. 2

    Install the requests library

    Run `pip install requests`. It handles the multipart/form-data encoding needed for file uploads.

  3. 3

    Send a resume to the API

    POST the PDF to https://api.resumeparser.cc/v1/parse as multipart field "file" with the header `Authorization: Bearer rp_live_...`. PDF only, up to 5 MB. Parsing is synchronous and can take up to 60 seconds, so set your HTTP client timeout to at least 60 seconds.

  4. 4

    Use the JSON response

    The response is `{ data, usage }`. data holds the parsed fields (firstName, lastName, emailAddress, phoneNumber, linkedInLink, workExperiences, educations, fluentLanguages, languages). usage holds billedAs, freeRemaining, and creditBalance.

Code examples

Parse a single resume (backend)python
import os
import requests

API_URL = "https://api.resumeparser.cc/v1/parse"
API_KEY = os.environ["RESUME_PARSER_API_KEY"]  # rp_live_...
MAX_BYTES = 5 * 1024 * 1024  # 5 MB


def parse_resume(file_path: str) -> dict:
    if os.path.getsize(file_path) > MAX_BYTES:
        raise ValueError("file exceeds 5 MB")

    with open(file_path, "rb") as f:
        response = requests.post(
            API_URL,
            headers={"Authorization": f"Bearer {API_KEY}"},
            files={"file": (os.path.basename(file_path), f, "application/pdf")},
            timeout=60,
        )

    body = response.json()
    if not response.ok:
        error = body["error"]
        raise RuntimeError(f"{response.status_code} {error['code']}: {error['message']}")

    return body


result = parse_resume("resume.pdf")
data = result["data"]

# Every field is optional: it is left out when the CV does not have it
print(data.get("firstName"), data.get("lastName"))
print(data.get("emailAddress"))

for experience in data.get("workExperiences", []):
    print(experience["title"], "at", experience["organization"])
    print(experience["startMonth"], experience["startYear"], "ongoing:", experience["isOngoing"])

for education in data.get("educations", []):
    print(education["degree"], "at", education["school"])

print(result["usage"])  # billedAs, freeRemaining, creditBalance
Batch parse a folder within the rate limitpython
import os
import time
import json
from pathlib import Path

import requests

API_URL = "https://api.resumeparser.cc/v1/parse"
API_KEY = os.environ["RESUME_PARSER_API_KEY"]

# 30 requests per minute per key: one request every 2 seconds, one at a time
SECONDS_BETWEEN_REQUESTS = 2

output_dir = Path("parsed")
output_dir.mkdir(exist_ok=True)

for pdf_path in sorted(Path("resumes").glob("*.pdf")):
    with open(pdf_path, "rb") as f:
        response = requests.post(
            API_URL,
            headers={"Authorization": f"Bearer {API_KEY}"},
            files={"file": (pdf_path.name, f, "application/pdf")},
            timeout=60,
        )

    body = response.json()
    if response.ok:
        (output_dir / f"{pdf_path.stem}.json").write_text(json.dumps(body["data"], indent=2))
        print(pdf_path.name, "ok, free remaining:", body["usage"]["freeRemaining"])
    else:
        print(pdf_path.name, "failed:", body["error"]["code"])

    time.sleep(SECONDS_BETWEEN_REQUESTS)
Retry on 429 and 503python
import os
import time

import requests

API_URL = "https://api.resumeparser.cc/v1/parse"
API_KEY = os.environ["RESUME_PARSER_API_KEY"]

# 429 rate_limited and 503 busy are worth retrying.
# 400 invalid_file, 401 invalid_api_key, 402 insufficient_credits and
# 413 file_too_large will fail again with the same request.
RETRYABLE_STATUSES = {429, 503}


def parse_resume_with_retry(file_path: str, max_attempts: int = 4) -> dict:
    for attempt in range(1, max_attempts + 1):
        with open(file_path, "rb") as f:
            response = requests.post(
                API_URL,
                headers={"Authorization": f"Bearer {API_KEY}"},
                files={"file": (os.path.basename(file_path), f, "application/pdf")},
                timeout=60,
            )

        if response.ok:
            return response.json()

        error = response.json()["error"]
        if response.status_code not in RETRYABLE_STATUSES or attempt == max_attempts:
            raise RuntimeError(f"{response.status_code} {error['code']}: {error['message']}")

        time.sleep(10 * attempt)

    raise RuntimeError("unreachable")

Integration benefits

Only the requests library

No SDK to install. Any Python code that can send a multipart POST with a header can call the API.

One fixed JSON schema

Every response uses the same documented fields. Every field is optional, so check for missing values.

Works in batch jobs

Each request is independent. Stay within 30 requests per minute per key and retry 429 and 503 responses.

Failed parses are not charged

50 free parses per month, then prepaid credits. A request that ends in an error does not use a parse.

Questions

Frequently asked questions

Any Python that can make HTTPS requests. The examples use Python 3 with the requests library.

Send one request every 2 seconds per key, or use a small token bucket. If you get 429 rate_limited, wait for the Retry-After header (60 seconds) before retrying. If you get 503 busy, the API is at capacity; wait a few seconds and retry.

Yes. Use httpx or aiohttp with the same request, and keep an asyncio.Semaphore or a rate limiter so you stay within 30 requests per minute per key.

The API returns 400 invalid_file, or 422 parse_failed if parsing fails. Failed parses are not charged. Scanned image-only PDFs do not work because there is no OCR; the PDF needs a text layer.

Every parse response includes usage (billedAs, freeRemaining, creditBalance). You can also call GET https://api.resumeparser.cc/v1/usage with the same Authorization header.

Start parsing resumes with Python

Sign up with your email, create an API key, and use the 50 free parses per month.

Get API key