Resume Parser API
Upload a PDF or DOCX resume → get parsed JSON. Then render it into any of 10 beautiful templates. Works out of the box with React, React Native, and any HTTP client.
Introduction
The Resume Parser API is a Flask-based REST service that does three things:
1. Parse PDFs
Extract structured data from resume PDFs using AI, returning 26+ fields as JSON.
2. Template Gallery
Browse 10 resume templates via a simple JSON API — ideal for a template picker screen.
3. HTML Rendering
Render parsed resume data into any template and get back full HTML for preview or PDF.
Base URL: http://localhost:5000 (or http://<your-ip>:5000 for mobile access on LAN)
Quick Start
The full workflow in 3 API calls:
Upload a PDF or DOCX
Send the PDF or DOCX file as multipart/form-data. You get back parsed JSON with 26+ fields.
List available templates
Show thumbnails in a grid and let the user pick one.
Render the chosen template
Open this URL in a WebView (React Native) or <iframe> (ReactJS) to show the finished resume.
1. Upload Resume
Upload a PDF or DOCX file and get back structured, parsed resume data.
Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file | File | Yes | PDF or DOCX resume, max 16 MB |
Example
curl -X POST \
-F "file=@resume.pdf" \
http://localhost:5000/api/upload-resumeconst form = new FormData();
form.append("file", pdfFile); // File object from <input type="file">
const res = await fetch("http://localhost:5000/api/upload-resume", {
method: "POST",
body: form
});
const json = await res.json();
// JSON Resume schema response
const uploadId = json.upload_id;
const basics = json.data.basics;
const summary = json.data.basics.summary;
const work = json.data.work;
console.log(basics.name, basics.email);import * as DocumentPicker from "expo-document-picker";
// 1. Pick the PDF
const pick = await DocumentPicker.getDocumentAsync({
type: "application/pdf"
});
if (pick.canceled) return;
const file = pick.assets[0];
// 2. Upload
const formData = new FormData();
formData.append("file", {
uri: file.uri,
name: file.name,
type: "application/pdf"
});
const res = await fetch("http://192.168.1.10:5000/api/upload-resume", {
method: "POST",
body: formData,
headers: { "Content-Type": "multipart/form-data" }
});
const json = await res.json();
// Save upload_id for later template rendering
await AsyncStorage.setItem("upload_id", String(json.upload_id));import axios from "axios";
const form = new FormData();
form.append("file", file);
const { data } = await axios.post(
"http://localhost:5000/api/upload-resume",
form,
{ headers: { "Content-Type": "multipart/form-data" } }
);
console.log(data.upload_id, data.data.basics);Success Response (200) — JSON Resume Schema
basics, work, education, skills, projects, certificates, awards, publications, languages, interests, references, volunteer.{
"status": 200,
"statusText": "OK",
"message": "Resume uploaded and parsed successfully",
"upload_id": 18,
"resume_file": "resume_JOHN-DOE_18_1776874753.pdf",
"schema": "jsonresume",
"data": {
"basics": {
"name": "John Doe",
"label": "Programmer",
"image": "",
"email": "john@gmail.com",
"phone": "(912) 555-4321",
"url": "https://johndoe.com",
"summary": "A summary of John Doe…",
"location": {
"address": "2712 Broadway St",
"postalCode": "CA 94115",
"city": "San Francisco",
"countryCode": "US",
"region": "California"
},
"profiles": [{
"network": "Twitter",
"username": "john",
"url": "https://twitter.com/john"
}]
},
"work": [{
"name": "Company",
"position": "President",
"url": "https://company.com",
"startDate": "2013-01-01",
"endDate": "2014-01-01",
"summary": "Description…",
"highlights": ["Started the company"]
}],
"education": [{
"institution": "University",
"url": "https://institution.com/",
"area": "Software Development",
"studyType": "Bachelor",
"startDate": "2011-01-01",
"endDate": "2013-01-01",
"score": "4.0",
"courses": ["DB1101 - Basic SQL"]
}],
"skills": [{
"name": "Web Development",
"level": "Master",
"keywords": ["HTML", "CSS", "JavaScript"]
}],
"projects": [{
"name": "Project",
"startDate": "2019-01-01",
"endDate": "2021-01-01",
"description": "Description...",
"highlights": ["Won award at AIHacks 2016"],
"url": "https://project.com/"
}],
"certificates": [{
"name": "Certificate",
"date": "2021-11-07",
"issuer": "Company",
"url": "https://certificate.com"
}],
"awards": [{
"title": "Award",
"date": "2014-11-01",
"awarder": "Company",
"summary": "There is no spoon."
}],
"publications": [{
"name": "Publication",
"publisher": "Company",
"releaseDate": "2014-10-01",
"url": "https://publication.com",
"summary": "Description…"
}],
"languages": [{
"language": "English",
"fluency": "Native speaker"
}],
"interests": [{
"name": "Wildlife",
"keywords": ["Ferrets", "Unicorns"]
}],
"references": [{
"name": "Jane Doe",
"reference": "Reference…"
}],
"volunteer": [{
"organization": "Organization",
"position": "Volunteer",
"url": "https://organization.com/",
"startDate": "2012-01-01",
"endDate": "2013-01-01",
"summary": "Description…",
"highlights": ["Awarded 'Volunteer of the Month'"]
}]
},
"ats_scores": {
"overall_score": 75,
"breakdown": {
"contact_info": 80,
"work_experience": 75,
"education": 80,
"skills": 70
},
"feedback": [
"Add more quantitative achievements to your work experience.",
"Consider adding links to your projects."
]
}
}upload_id is at the top level. Save it locally to fetch later via /api/resume/<upload_id> or render templates without re-uploading./api/templates/<id>/html or /render — your data stays in the same shape end-to-end.2. Get Parsed Resume
Retrieve a previously parsed resume by its upload ID. Returns JSON Resume schema. No re-upload needed.
curl http://localhost:5000/api/resume/18Response
Identical JSON Resume shape as /api/upload-resume:
{
"status": 200,
"statusText": "OK",
"message": "Resume fetched successfully",
"upload_id": 18,
"resume_file": "resume_JOHN-DOE_18_1776874753.pdf",
"schema": "jsonresume",
"data": {
"basics": {...},
"work": [...],
"education": [...],
"skills": [...],
"projects": [...],
"certificates": [...],
"awards": [...],
"publications": [...],
"languages": [...],
"interests": [...],
"references": [...],
"volunteer": [...]
},
"ats_scores": {
"overall_score": 75,
"breakdown": {
"contact_info": 80,
"work_experience": 75,
"education": 80,
"skills": 70
},
"feedback": [
"Add more quantitative achievements to your work experience.",
"Consider adding links to your projects."
]
}
}upload_id returns 404, the resume needs to be re-uploaded.3. Parsed Resume Schema
The data object is organized into 8 logical groups matching typical UI sections:
Top-level fields
| Field | Type | Description |
|---|---|---|
upload_id | integer | Save this to fetch data later |
resume_file | string | Server-generated filename |
ats_scores | object | ATS scoring metrics (overall_score, breakdown, feedback) |
data.personal_information | object | Name, contacts, links |
data.professional_summary | string | One-paragraph bio |
data.work_experience | array | Job history |
data.education | array | Degrees and schools |
data.projects | array | Featured projects |
data.hobbies | array | String list |
data.skills | array | Flat skills list |
data.certifications | array | Structured certs (see below) |
data.additional_info | object | Languages, awards, etc. |
personal_information
| Field | Type |
|---|---|
full_name | string |
email | string |
phone | string |
location | string |
linkedin_profile | string |
portfolio_website | string |
work_experience[]
| Field | Type |
|---|---|
job_title | string |
company | string |
location | string |
duration | string (e.g. "Jan 2024 - Present") |
start_date | string |
end_date | string |
responsibilities | array of strings (bullets) |
education[]
| Field | Type |
|---|---|
degree | string |
major | string |
institution | string |
location | string |
start_year | string |
end_year | string |
grade | string |
projects[]
| Field | Type |
|---|---|
name | string |
description | string |
technologies | array of strings |
certifications[]
| Field | Type |
|---|---|
name | string |
issuing_organization | string |
date_obtained | string |
additional_info
| Field | Type | Description |
|---|---|---|
technical_skills | array | Tech-specific skills |
soft_skills | array | Soft skills |
languages | array | Spoken languages |
achievements | array | Personal achievements |
awards | array | Awards received |
github_url | string | GitHub URL |
years_of_experience | number | Calculated total years |
inferred_job_title | string | Most recent title |
4. List Templates
Returns all 10 available resume templates.
Query Parameters (optional)
| Param | Values | Example |
|---|---|---|
category | modern, classic, creative | ?category=creative |
is_premium | true, false | ?is_premium=false |
search | any keyword | ?search=developer |
page | integer (default: 1) | ?page=2 |
limit | integer (default: 10) | ?limit=5 |
Example
curl http://localhost:5000/api/templates?category=modernResponse
{
"status": 200,
"statusText": "OK",
"message": "Templates fetched successfully",
"data": {
"total": 5,
"page": 1,
"limit": 10,
"pages": 1,
"templates": [
{
"id": 1,
"slug": "minimalist-clean",
"name": "The Minimalist",
"description": "Clean, structured layout with emphasis on typography.",
"category": "modern",
"thumbnail": "http://localhost:5000/static/templates/thumb/1.png",
"color_scheme": {
"primary": "#2c3e50",
"secondary": "#f7f9fa",
"text": "#333333",
"accent": "#2c3e50"
},
"font_family": "Inter, sans-serif",
"layout": "two_column",
"is_premium": false,
"template_file": "resume_1_minimalist.html",
"sections": ["summary", "experience", "education", "skills"],
"tags": ["minimal", "corporate", "ats-friendly"],
"render_url": "http://localhost:5000/api/templates/1/render",
"preview_url": "http://localhost:5000/api/templates/1/preview"
}
]
}
}thumbnail URL for the gallery card image and the preview_url when the user taps a template.5. Single Template
Fetch one template by numeric ID (1-10).
curl http://localhost:5000/api/templates/36. Template Categories
Returns all unique categories with template counts.
{
"status": 200,
"data": {
"total": 3,
"categories": [
{ "name": "modern", "count": 5 },
{ "name": "creative", "count": 3 },
{ "name": "classic", "count": 2 }
]
}
}7. Live Preview (Form → HTML)
Most important endpoint for builders. Send form data → get raw rendered HTML back. Perfect for real-time preview in an iframe/WebView while the user types.
Request Body — Option A: Full form data (grouped)
Exactly the same structure as what /api/upload-resume returns:
{
"personal_information": {
"full_name": "Poonam Batham",
"email": "poonam@example.com",
"phone": "+91-9399435171",
"location": "Gwalior, India",
"linkedin_profile": "linkedin.com/in/poonam",
"portfolio_website": "poonam.dev"
},
"professional_summary": "Python Backend Developer with 3 years...",
"work_experience": [
{
"job_title": "Agentic AI Engineer",
"company": "SummitCode",
"location": "Remote",
"duration": "Jan 2026 - Present",
"responsibilities": ["Built AI agents", "Integrated LLMs"]
}
],
"education": [
{
"degree": "B.E.",
"institution": "ITM University",
"location": "Gwalior",
"end_year": "2018"
}
],
"projects": [
{
"name": "Order Management System",
"description": "Backend with Flask + RBAC",
"technologies": ["Flask", "MySQL"]
}
],
"hobbies": ["Reading", "Coding"],
"skills": ["Python", "Django", "Flask"],
"certifications": [
{
"name": "AWS Certified",
"issuing_organization": "Amazon Web Services",
"date_obtained": "2024"
}
]
}Request Body — Option B: From stored upload
{ "upload_id": 18 }Response
Raw HTML string (Content-Type: text/html). Inject directly into an iframe's srcdoc.
Live Preview in ReactJS
import { useState, useEffect } from "react";
const API = "https://pdf-read-k3ow.onrender.com";
function useDebounce(value, delay = 400) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return debounced;
}
export default function ResumeBuilder() {
const [templateId, setTemplateId] = useState(1);
const [formData, setFormData] = useState({
personal_information: { full_name: "", email: "" },
professional_summary: "",
work_experience: [],
education: [],
projects: [],
hobbies: [],
skills: [],
certifications: []
});
// Debounce so we don't hit the API on every keystroke
const debounced = useDebounce(formData, 400);
const [html, setHtml] = useState("");
useEffect(() => {
fetch(`${API}/api/templates/${templateId}/html`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(debounced)
})
.then(r => r.text())
.then(setHtml);
}, [debounced, templateId]);
return (
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Form data={formData} onChange={setFormData} />
<iframe srcDoc={html} style={{ width: "100%", height: "100vh", border: 0 }} />
</div>
);
}Live Preview in React Native
import { WebView } from "react-native-webview";
const [html, setHtml] = useState("");
useEffect(() => {
const timer = setTimeout(async () => {
const res = await fetch(`${API}/api/templates/${id}/html`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData)
});
setHtml(await res.text());
}, 400);
return () => clearTimeout(timer);
}, [formData, id]);
<WebView source={{ html }} style={{ flex: 1 }} />7b. Render Template (JSON wrapped)
Same as /html but returns HTML inside a JSON envelope. Useful when you need the HTML for post-processing (PDF conversion, storage, etc.).
Response
{
"status": 200,
"statusText": "OK",
"message": "Template rendered successfully",
"data": {
"template_id": 3,
"template_name": "Dark Mode Dev",
"html": "<!DOCTYPE html><html>...</html>"
}
}8. Preview Template (GET — for saved uploads)
Returns rendered HTML page directly. Ideal when you have a stored upload_id and want a simple URL for iframe/WebView.
Query Parameters
| Param | Type | Description |
|---|---|---|
upload_id | Integer | Omit for built-in sample data |
Examples
# Sample data (for template thumbnails)
http://localhost:5000/api/templates/1/preview
# Real user data
http://localhost:5000/api/templates/3/preview?upload_id=18React Native WebView
<WebView
source={{ uri: `${API}/api/templates/${templateId}/preview?upload_id=${uploadId}` }}
style={{ flex: 1 }}
/>ReactJS iframe
<iframe
src={`${API}/api/templates/${id}/preview?upload_id=${uploadId}`}
style={{ width: "100%", height: "100vh", border: 0 }}
/>• POST /html — live preview while user types (form → HTML)
• POST /render — get HTML as a string (for PDF conversion)
• GET /preview — display a saved resume (upload_id already exists)
9. Available Templates
Browse all 20 templates available out of the box. Templates 11–18 also support a profile photo.
| ID | Name | Category | Layout | Photo | Premium |
|---|---|---|---|---|---|
| 1 | The Minimalist | modern | two_column | — | Free |
| 2 | The Creative | creative | sidebar_left | — | Free |
| 3 | Dark Mode Dev | modern | two_column | — | Free |
| 4 | The Executive | classic | two_column | — | Free |
| 5 | Modern Split | modern | split_header | — | Free |
| 6 | Gradient Glow | modern | two_column | — | Free |
| 7 | Infographic | creative | sidebar_left | — | Free |
| 8 | Academic Scholar | classic | single_column | — | Free |
| 9 | Portfolio Card | creative | sidebar_left | — | Free |
| 10 | Marketing Dynamic | modern | two_column | — | Free |
| 11 | Photo Classic | classic | single_column | Yes | Free |
| 12 | Photo Modern | modern | two_column | Yes | Free |
| 13 | Photo Designer | creative | sidebar_left | Yes | Free |
| 14 | Photo Executive | classic | two_column | Yes | Free |
| 15 | Photo Minimal | modern | single_column | Yes | Free |
| 16 | Photo Corporate | classic | single_column | Yes | Free |
| 17 | Photo Creative | creative | sidebar_left | Yes | Free |
| 18 | Photo Elegant | classic | two_column | Yes | Free |
| 19 | Compact Pro | modern | single_column | — | Free |
| 20 | Timeline | creative | single_column | — | Free |
10. Error Codes
All errors follow the same envelope shape so clients can handle them uniformly.
{
"status": 400,
"statusText": "Bad Request",
"message": "Human-readable message",
"data": {
"data": {
"resume_file": null,
"resume_analysed": false,
"parsed_data": {
"status": "error",
"message": "...",
"code": "ERROR_CODE",
"data": null
}
}
}
}| HTTP | Code | Meaning |
|---|---|---|
| 400 | NO_FILE_FIELD | Missing file field |
| 400 | EMPTY_FILENAME | Empty filename |
| 404 | NOT_FOUND | Upload or template not found |
| 413 | FILE_TOO_LARGE | File exceeds 16 MB |
| 415 | INVALID_FILE_TYPE | Unsupported file type (only PDF and DOCX accepted) |
| 422 | EMPTY_TEXT | Couldn't extract text (scanned/corrupt PDF/DOCX or empty file) |
| 422 | AI_PARSE_FAILED | AI returned non-JSON response |
| 500 | SAVE_FAILED | Server couldn't save the file |
11. Client Examples — Complete Flow
ReactJS — Full Upload + Template Gallery
import { useState, useEffect } from "react";
const API = "http://localhost:5000";
export default function ResumeApp() {
const [templates, setTemplates] = useState([]);
const [uploadId, setUploadId] = useState(null);
const [selected, setSelected] = useState(null);
useEffect(() => {
fetch(`${API}/api/templates`)
.then(r => r.json())
.then(j => setTemplates(j.data.templates));
}, []);
const handleUpload = async (e) => {
const file = e.target.files[0];
const form = new FormData();
form.append("file", file);
form.append("user_id", "2");
const res = await fetch(`${API}/api/upload-resume`, {
method: "POST",
body: form
});
const j = await res.json();
// Extract upload_id from resume_file filename pattern
const match = j.data.data.resume_file.match(/_(\d+)_\d+\.pdf$/);
setUploadId(match ? match[1] : null);
};
return (
<div>
<input type="file" accept=".pdf" onChange={handleUpload} />
<div style={{display:"grid",gridTemplateColumns:"repeat(3,1fr)",gap:16}}>
{templates.map(t => (
<div key={t.id} onClick={() => setSelected(t.id)} style={{cursor:"pointer"}}>
<img src={t.thumbnail} alt={t.name} style={{width:"100%"}} />
<h4>{t.name}</h4>
</div>
))}
</div>
{selected && uploadId && (
<iframe
src={`${API}/api/templates/${selected}/preview?upload_id=${uploadId}`}
style={{width:"100%",height:"800px",border:0}}
/>
)}
</div>
);
}React Native — Full Flow
import React, { useState, useEffect } from "react";
import { View, FlatList, Image, TouchableOpacity, Text } from "react-native";
import { WebView } from "react-native-webview";
import * as DocumentPicker from "expo-document-picker";
const API = "http://192.168.1.10:5000";
export default function ResumeScreen() {
const [templates, setTemplates] = useState([]);
const [uploadId, setUploadId] = useState(null);
const [selected, setSelected] = useState(null);
useEffect(() => {
fetch(`${API}/api/templates`)
.then(r => r.json())
.then(j => setTemplates(j.data.templates));
}, []);
const pickAndUpload = async () => {
const res = await DocumentPicker.getDocumentAsync({ type: "application/pdf" });
if (res.canceled) return;
const file = res.assets[0];
const form = new FormData();
form.append("file", { uri: file.uri, name: file.name, type: "application/pdf" });
form.append("user_id", "2");
const r = await fetch(`${API}/api/upload-resume`, {
method: "POST",
body: form,
headers: { "Content-Type": "multipart/form-data" }
});
const j = await r.json();
const m = j.data.data.resume_file.match(/_(\d+)_\d+\.pdf$/);
setUploadId(m ? m[1] : null);
};
if (selected && uploadId) {
return (
<WebView
source={{ uri: `${API}/api/templates/${selected}/preview?upload_id=${uploadId}` }}
style={{ flex: 1 }}
/>
);
}
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={pickAndUpload}>
<Text>Upload Resume</Text>
</TouchableOpacity>
<FlatList
data={templates}
numColumns={2}
keyExtractor={t => String(t.id)}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => setSelected(item.id)}>
<Image source={{ uri: item.thumbnail }} style={{ width: 150, height: 200 }} />
<Text>{item.name}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}12. API Cheatsheet
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /api/upload-resume | Upload PDF or DOCX → parsed JSON + upload_id |
| GET | /api/resume/<upload_id> | Retrieve stored parsed resume |
| GET | /api/templates | List all 20 templates |
| GET | /api/templates/<id> | Single template details |
| GET | /api/templates/categories | Category counts |
| POST | /api/templates/<id>/html | Live preview (form → raw HTML) |
| POST | /api/templates/<id>/render | Render template (HTML inside JSON) |
| GET | /api/templates/<id>/preview | Preview saved resume (by upload_id) |