Resume Parser API
Upload a PDF → get parsed JSON. Render into 62 beautiful templates. AI-powered ATS scoring. No signup required.
Introduction
The Resume Parser API is a Next.js App Router REST service that does four things:
1. Parse PDFs
Extract structured data from resume PDFs using AI, returning the standard JSON Resume schema.
2. Template Gallery
Browse 62 resume templates via a simple JSON API — ideal for a template picker screen.
3. HTML Rendering
Render any JSON Resume into any template and get back raw HTML for live preview or PDF export.
4. ATS Scoring
Rule-based scoring plus AI-generated feedback covering keywords, action verbs, weak areas and more.
Base URL: https://resume.codekrafters.co.in (or your deployed origin). From the browser, prefer relative paths like /api/templates.
Schema: Every endpoint that accepts or returns resume data uses the standardized JSON Resume shape (basics, work, education, skills, etc.).
Quick Start
The full workflow in 4 API calls:
Upload a PDF
Send the PDF as multipart/form-data. You get back parsed JSON Resume + ATS scores + upload_id.
List available templates
Show thumbnails in a grid and let the user pick one of 62 designs.
Render the chosen template
Pipe the parsed JSON (or upload_id) back in and stream HTML into an iframe's srcDoc.
Get ATS feedback
Score the resume with rule-based + AI feedback to drive your ATS UI.
1. Upload Resume
Upload a PDF file and get back structured, parsed resume data in JSON Resume format plus ATS scoring.
Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file | File | Yes | PDF resume, max 16 MB |
Code Examples
cURL
curl -X POST \ -F "file=@resume.pdf" \ https://resume.codekrafters.co.in/api/upload-resume
JavaScript (fetch)
const form = new FormData();
form.append("file", pdfFile); // File from <input type="file">
const res = await fetch("/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);React Native
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:3000/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));Axios
import axios from "axios";
const form = new FormData();
form.append("file", file);
const { data } = await axios.post(
"/api/upload-resume",
form,
{ headers: { "Content-Type": "multipart/form-data" } }
);
console.log(data.upload_id, data.data.basics, data.ats_scores);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 to 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 plus ATS scores. No re-upload needed.
cURL
curl https://resume.codekrafters.co.in/api/resume/18
JavaScript
const res = await fetch(`/api/resume/${uploadId}`);
const { data, ats_scores } = await res.json();Response (200)
{
"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 upload record was wiped (e.g. ephemeral container restart). Re-upload the PDF.3. Get as JSON Resume
Identical payload to /api/resume/<id>, but explicitly tagged with schema: "jsonresume" and a spec_url reference. Use this when you want clients to know the response conforms to the public spec.
Response (200)
{
"status": 200,
"statusText": "OK",
"message": "JSON Resume fetched successfully",
"schema": "jsonresume",
"spec_url": "https://jsonresume.org/schema",
"upload_id": 18,
"data": { "basics": {...}, "work": [...], "education": [...], "skills": [...] }
}cURL
curl https://resume.codekrafters.co.in/api/jsonresume/18
4. Response Schema
The data object follows the JSON Resume spec. Top-level keys:
Top-level fields
| Field | Type | Description |
|---|---|---|
upload_id | integer | Save this to fetch data later |
resume_file | string | Server-generated filename |
schema | string | Always "jsonresume" |
data | object | JSON Resume payload (see below) |
ats_scores | object | Overall score + breakdown + feedback |
basics
| Field | Type | Description |
|---|---|---|
name | string | Full name |
label | string | Headline / current title |
image | string | Optional photo URL |
email | string | Primary email |
phone | string | Primary phone |
url | string | Portfolio / personal site |
summary | string | One-paragraph bio |
location | object | address, city, region, postalCode, countryCode |
profiles[] | array | network, username, url |
work[]
| Field | Type |
|---|---|
name | string (company) |
position | string |
url | string |
startDate | string (YYYY-MM-DD) |
endDate | string |
summary | string |
highlights[] | array of strings (bullets) |
education[]
| Field | Type |
|---|---|
institution | string |
url | string |
area | string (field of study) |
studyType | string (Bachelor, Master, ...) |
startDate | string |
endDate | string |
score | string (GPA) |
courses[] | array of strings |
skills[]
| Field | Type |
|---|---|
name | string (skill group) |
level | string (Beginner ... Master) |
keywords[] | array of strings |
projects[]
| Field | Type |
|---|---|
name | string |
description | string |
highlights[] | array of strings |
url | string |
startDate | string |
endDate | string |
certificates[]
| Field | Type |
|---|---|
name | string |
date | string |
issuer | string |
url | string |
awards[]
| Field | Type |
|---|---|
title | string |
date | string |
awarder | string |
summary | string |
publications[]
| Field | Type |
|---|---|
name | string |
publisher | string |
releaseDate | string |
url | string |
summary | string |
languages[]
| Field | Type |
|---|---|
language | string |
fluency | string |
interests[]
| Field | Type |
|---|---|
name | string |
keywords[] | array of strings |
references[]
| Field | Type |
|---|---|
name | string |
reference | string |
volunteer[]
| Field | Type |
|---|---|
organization | string |
position | string |
url | string |
startDate | string |
endDate | string |
summary | string |
highlights[] | array of strings |
ats_scores
| Field | Type | Description |
|---|---|---|
overall_score | number (0-100) | Composite ATS score |
breakdown | object | Per-section sub-scores |
feedback[] | array of strings | Human-readable tips |
5. List Templates
Returns all 62 available resume templates. When neither page nor limit is provided, the full registry is returned in one shot.
Query Parameters (optional)
| Param | Values | Example |
|---|---|---|
category | modern, classic, creative | ?category=creative |
is_premium | true, false | ?is_premium=false |
search | any keyword (name, tag, description) | ?search=developer |
page | integer (default: 1) | ?page=2 |
limit | integer (default: 10) | ?limit=5 |
cURL
curl "https://resume.codekrafters.co.in/api/templates?category=modern&limit=5"
Response
{
"status": 200,
"statusText": "OK",
"message": "Templates fetched successfully",
"data": {
"total": 62,
"page": 1,
"limit": 10,
"pages": 7,
"templates": [
{
"id": 1,
"slug": "minimalist-clean",
"name": "The Minimalist",
"description": "Clean, structured layout with emphasis on typography.",
"category": "modern",
"thumbnail": "https://resume.codekrafters.co.in/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": "https://resume.codekrafters.co.in/api/templates/1/render",
"preview_url": "https://resume.codekrafters.co.in/api/templates/1/preview"
}
]
}
}thumbnail URL for the gallery card image and the preview_url when the user taps a template.6. Single Template
Fetch one template by numeric ID (1-62).
cURL
curl https://resume.codekrafters.co.in/api/templates/3
Response (200)
{
"status": 200,
"data": {
"id": 3,
"slug": "dark-mode-dev",
"name": "Dark Mode Dev",
"category": "modern",
"layout": "two_column",
"is_premium": false,
"tags": ["developer", "dark", "tech", "engineering"],
"render_url": "https://resume.codekrafters.co.in/api/templates/3/render",
"preview_url": "https://resume.codekrafters.co.in/api/templates/3/preview"
}
}7. Template Categories
Returns all unique categories with template counts.
cURL
curl https://resume.codekrafters.co.in/api/templates/categories
Response
{
"status": 200,
"data": {
"total": 3,
"categories": [
{ "name": "modern", "count": 28 },
{ "name": "classic", "count": 19 },
{ "name": "creative", "count": 15 }
]
}
}8. Live Preview (Form → HTML)
The main builder endpoint. Send a JSON Resume payload → get raw rendered HTML back. Perfect for real-time preview in an iframe's srcDoc or a React Native WebView while the user types.
{ "upload_id": 18 } to render a previously parsed resume from the server.Request Body — Option A: Full JSON Resume
{
"basics": {
"name": "Poonam Batham",
"email": "poonam@example.com",
"phone": "+91-9399435171",
"label": "Python Backend Developer",
"summary": "Python Backend Developer with 3 years..."
},
"work": [
{
"name": "SummitCode",
"position": "Agentic AI Engineer",
"startDate": "2026-01-01",
"endDate": "",
"summary": "",
"highlights": ["Built AI agents", "Integrated LLMs"]
}
],
"education": [
{
"institution": "ITM University",
"studyType": "B.E.",
"area": "Computer Science",
"endDate": "2018-06-01"
}
],
"projects": [
{
"name": "Order Management System",
"description": "Backend with Flask + RBAC",
"highlights": ["Flask", "MySQL"]
}
],
"skills": [
{ "name": "Backend", "keywords": ["Python", "Django", "Flask"] }
],
"certificates": [
{
"name": "AWS Certified",
"issuer": "Amazon Web Services",
"date": "2024-08-01"
}
]
}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 React
"use client";
import { useState, useEffect } from "react";
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 [resume, setResume] = useState({
basics: { name: "", email: "" },
work: [],
education: [],
projects: [],
skills: [],
certificates: [],
});
const debounced = useDebounce(resume, 400);
const [html, setHtml] = useState("");
useEffect(() => {
fetch(`/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={resume} onChange={setResume} />
<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(resume),
});
setHtml(await res.text());
}, 400);
return () => clearTimeout(timer);
}, [resume, id]);
<WebView source={{ html }} style={{ flex: 1 }} />9. 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.).
Request Body
JSON Resume payload, or { "upload_id": N }.
Response
{
"status": 200,
"statusText": "OK",
"message": "Template rendered successfully",
"data": {
"template_id": 3,
"template_name": "Dark Mode Dev",
"html": "<!DOCTYPE html><html>...</html>"
}
}cURL
curl -X POST https://resume.codekrafters.co.in/api/templates/3/render \
-H "Content-Type: application/json" \
-d '{"upload_id": 18}'JavaScript
const res = await fetch(`/api/templates/${id}/render`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(resume),
});
const { data } = await res.json();
document.getElementById("preview").srcdoc = data.html;10. Preview Template (GET)
Returns rendered HTML directly (Content-Type: text/html). Ideal when you have a stored upload_id and want a simple URL for iframe/WebView. Omit upload_id for built-in sample data — useful for generating template thumbnails.
Query Parameters
| Param | Type | Description |
|---|---|---|
upload_id | integer | Omit for built-in sample data |
Examples
# Sample data (for template thumbnails) https://resume.codekrafters.co.in/api/templates/1/preview # Real user data https://resume.codekrafters.co.in/api/templates/3/preview?upload_id=18
React iframe
<iframe
src={`/api/templates/${id}/preview?upload_id=${uploadId}`}
style={{ width: "100%", height: "100vh", border: 0 }}
/>React Native WebView
<WebView
source={{ uri: `${API}/api/templates/${templateId}/preview?upload_id=${uploadId}` }}
style={{ flex: 1 }}
/>- POST /html — live preview while user types (JSON Resume → raw HTML).
- POST /render — get HTML as a string (for PDF conversion, storage).
- GET /preview — display a saved resume (upload_id already exists).
11. Available Templates
All 62 templates available out of the box. Templates with Photo support a profile photo via basics.image.
| 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 |
| 21 | Tech Sleek | modern | two_column | — | Free |
| 22 | Data Scientist | modern | single_column | — | Free |
| 23 | Cyber Shield | creative | single_column | — | Free |
| 24 | Game Developer | modern | sidebar_left | Yes | Free |
| 25 | Cloud Engineer | modern | single_column | — | Free |
| 26 | Finance Pro | classic | two_column | — | Free |
| 27 | Consultant Elite | classic | sidebar_left | — | Free |
| 28 | Sales Power | modern | single_column | — | Free |
| 29 | Startup Founder | modern | single_column | — | Free |
| 30 | Project Manager | modern | single_column | — | Free |
| 31 | Healthcare | modern | sidebar_left | Yes | Free |
| 32 | Pharma Professional | classic | single_column | — | Free |
| 33 | Lab Scientist | classic | two_column | — | Free |
| 34 | Veterinary Care | modern | single_column | Yes | Free |
| 35 | Dental Pro | modern | sidebar_right | Yes | Free |
| 36 | Fashion Editorial | classic | single_column | Yes | Free |
| 37 | Photographer | classic | single_column | Yes | Free |
| 38 | Music Artist | modern | single_column | — | Free |
| 39 | Journalist | classic | two_column | — | Free |
| 40 | Architect | modern | sidebar_left | — | Free |
| 41 | Construction Manager | modern | single_column | — | Free |
| 42 | Hospitality | classic | single_column | — | Free |
| 43 | Chef Culinary | classic | sidebar_right | Yes | Free |
| 44 | Fitness Trainer | modern | single_column | Yes | Free |
| 45 | Aviation Pilot | modern | single_column | — | Free |
| 46 | Educator | classic | sidebar_left | Yes | Free |
| 47 | Legal Brief | classic | single_column | — | Free |
| 48 | NGO / Humanitarian | modern | single_column | — | Free |
| 49 | Real Estate Agent | modern | sidebar_left | Yes | Free |
| 50 | Universal Pro | classic | two_column | — | Free |
| 51 | Pearson Engineer | modern | single_column | — | Free |
| 52 | Paulsen Strategist | modern | single_column | — | Free |
| 53 | Pearson HR | modern | two_column | Yes | Free |
| 54 | Ross Project | modern | single_column | Yes | Free |
| 55 | Elegant | classic | single_column | — | Free |
| 56 | Creative Sidebar | modern | sidebar_left | Yes | Free |
| 57 | Executive | classic | single_column | Yes | Free |
| 58 | Classic | classic | single_column | — | Free |
| 59 | Standard | modern | single_column | Yes | Free |
| 60 | Luminary | classic | single_column | Yes | Free |
| 61 | Simple | modern | single_column | — | Free |
| 62 | Zenith | modern | two_column | Yes | Free |
12. ATS Analyze
Score a resume with both rule-based + AI feedback. Submit a JSON Resume payload directly, or reference a previously uploaded resume by upload_id.
Request Body (one of the two)
| Name | Type | Required | Description |
|---|---|---|---|
resume | object | * | JSON Resume object to analyze. |
upload_id | integer | * | Reference a stored upload instead of inlining. |
cURL — inline resume
curl -X POST https://resume.codekrafters.co.in/api/ats-analyze \
-H "Content-Type: application/json" \
-d '{"resume": {"basics": {"name": "Jane Doe", "email": "jane@example.com"}, "work": [], "education": []}}'cURL — by upload_id
curl -X POST https://resume.codekrafters.co.in/api/ats-analyze \
-H "Content-Type: application/json" \
-d '{"upload_id": 18}'JavaScript
const res = await fetch("/api/ats-analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ resume }),
});
const { rule_based, ai_analysis } = await res.json();Response (200)
{
"status": 200,
"statusText": "OK",
"rule_based": {
"overall_score": 82,
"breakdown": {
"contact_info": 100,
"work_experience": 80,
"education": 90,
"skills": 70
},
"feedback": [
"Strong contact section.",
"Add metrics to work bullets."
],
"stats": {
"word_count": 412,
"bullet_count": 18,
"quantified_bullets": 7,
"action_verb_bullets": 14
}
},
"ai_analysis": {
"missing_sections": ["projects"],
"weak_areas": [
"Summary is too long",
"Skills section lacks proficiency levels"
],
"keyword_suggestions": ["TypeScript", "CI/CD", "Kubernetes"],
"action_verb_upgrades": [
{ "from": "Worked on", "to": "Engineered" },
{ "from": "Helped with", "to": "Led" }
],
"quantification_tips": [
"Quantify the impact of your design system rebuild (e.g., reduced bundle size by X%)"
],
"summary_rewrite": "Senior frontend engineer with 8+ years building production React apps...",
"strengths": [
"Quantified impact in last role",
"Clear progression"
],
"ats_risk_flags": [
"References line still present",
"Two-column layout may confuse some ATS parsers"
],
"overall_recommendation": "Cut the summary, add metrics to 3 more bullets, and remove the references line.",
"inferred_target_role": "Senior Frontend Engineer",
"ai_powered": true
}
}13. AI Feedback Structure
The ai_analysis object surfaces structured fields you can render directly into UI cards:
| Field | Type | Description |
|---|---|---|
missing_sections | string[] | Sections the resume is missing |
weak_areas | string[] | Specific weaknesses with explanations |
keyword_suggestions | string[] | Keywords to add for the target role |
action_verb_upgrades | {from, to}[] | Suggested replacements for weak verbs |
quantification_tips | string[] | Hints for adding numbers/metrics |
summary_rewrite | string | Suggested rewritten professional summary |
strengths | string[] | What the resume does well |
ats_risk_flags | string[] | Layout / formatting risks for ATS parsers |
overall_recommendation | string | One-paragraph plan of action |
inferred_target_role | string | Role the AI inferred from the content |
ai_powered | boolean | false if the AI provider was unavailable and fallback rules were used |
ai_powered is false and only rule_based scores will be populated with meaningful data. Always check ai_powered before rendering AI cards.14. Error Codes
All errors follow the same envelope shape so clients can handle them uniformly.
{
"status": 400,
"statusText": "Bad Request",
"message": "Human-readable message",
"error_code": "ERROR_CODE",
"data": null
}| HTTP | Code | Meaning |
|---|---|---|
| 400 | NO_FILE_FIELD | Missing file field on upload |
| 400 | EMPTY_FILENAME | Uploaded file has 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 accepted) |
| 422 | EMPTY_TEXT | Couldn't extract text (scanned or corrupt PDF) |
| 422 | AI_PARSE_FAILED | AI returned non-JSON response |
| 500 | SAVE_FAILED | Server couldn't save the file |
Defensive client handling
async function safeFetch(url, init) {
const res = await fetch(url, init);
if (!res.ok) {
let body;
try { body = await res.json(); } catch { body = {}; }
const code = body.error_code || res.status;
const message = body.message || res.statusText;
throw new Error(`[${code}] ${message}`);
}
return res.json();
}15. Client Examples — Complete Flow
Full pipeline: upload PDF → get parsed data → render with template → fetch ATS feedback.
React (Web) — Full Builder + ATS
"use client";
import { useState, useEffect } from "react";
const API = ""; // same-origin
export default function ResumeApp() {
const [templates, setTemplates] = useState([]);
const [uploadId, setUploadId] = useState(null);
const [parsed, setParsed] = useState(null);
const [selected, setSelected] = useState(1);
const [html, setHtml] = useState("");
const [ats, setAts] = useState(null);
// 1) Load templates on mount
useEffect(() => {
fetch(`${API}/api/templates`)
.then((r) => r.json())
.then((j) => setTemplates(j.data.templates));
}, []);
// 2) Handle PDF upload
const handleUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
const form = new FormData();
form.append("file", file);
const res = await fetch(`${API}/api/upload-resume`, {
method: "POST",
body: form,
});
const j = await res.json();
if (j.status !== 200) {
alert(j.message);
return;
}
setUploadId(j.upload_id);
setParsed(j.data);
setAts(j.ats_scores); // initial rule-based scores
};
// 3) Re-render whenever template or data changes
useEffect(() => {
if (!parsed) return;
fetch(`${API}/api/templates/${selected}/html`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(parsed),
})
.then((r) => r.text())
.then(setHtml);
}, [selected, parsed]);
// 4) Get full AI feedback on demand
const runAtsAnalysis = async () => {
const res = await fetch(`${API}/api/ats-analyze`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ upload_id: uploadId }),
});
setAts(await res.json());
};
return (
<div style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 16 }}>
<aside>
<input type="file" accept=".pdf" onChange={handleUpload} />
<button onClick={runAtsAnalysis} disabled={!uploadId}>
Run ATS Analysis
</button>
<h3>Pick a template</h3>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8 }}>
{templates.map((t) => (
<button key={t.id} onClick={() => setSelected(t.id)}>
<img src={t.thumbnail} alt={t.name} style={{ width: "100%" }} />
<div>{t.name}</div>
</button>
))}
</div>
{ats?.rule_based && (
<div>
<h4>ATS score: {ats.rule_based.overall_score}/100</h4>
<ul>
{ats.rule_based.feedback.map((f, i) => <li key={i}>{f}</li>)}
</ul>
</div>
)}
</aside>
<iframe srcDoc={html} style={{ width: "100%", height: "100vh", border: 0 }} />
</div>
);
}React Native — Full Flow
import React, { useState, useEffect } from "react";
import { View, FlatList, Image, TouchableOpacity, Text, ScrollView } from "react-native";
import { WebView } from "react-native-webview";
import * as DocumentPicker from "expo-document-picker";
const API = "http://192.168.1.10:3000";
export default function ResumeScreen() {
const [templates, setTemplates] = useState([]);
const [uploadId, setUploadId] = useState(null);
const [parsed, setParsed] = useState(null);
const [selected, setSelected] = useState(null);
const [ats, setAts] = useState(null);
// 1) Load templates
useEffect(() => {
fetch(`${API}/api/templates`)
.then((r) => r.json())
.then((j) => setTemplates(j.data.templates));
}, []);
// 2) Pick + upload PDF
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" });
const r = await fetch(`${API}/api/upload-resume`, {
method: "POST",
body: form,
headers: { "Content-Type": "multipart/form-data" },
});
const j = await r.json();
setUploadId(j.upload_id);
setParsed(j.data);
setAts(j.ats_scores);
};
// 3) Run full AI ATS analysis
const runAts = async () => {
const r = await fetch(`${API}/api/ats-analyze`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ upload_id: uploadId }),
});
setAts(await r.json());
};
// 4) Render template in a WebView
if (selected && uploadId) {
return (
<WebView
source={{ uri: `${API}/api/templates/${selected}/preview?upload_id=${uploadId}` }}
style={{ flex: 1 }}
/>
);
}
return (
<ScrollView style={{ flex: 1 }}>
<TouchableOpacity onPress={pickAndUpload}>
<Text>Upload Resume PDF</Text>
</TouchableOpacity>
<TouchableOpacity onPress={runAts} disabled={!uploadId}>
<Text>Run ATS Analysis</Text>
</TouchableOpacity>
{ats?.rule_based && (
<View>
<Text>Score: {ats.rule_based.overall_score}/100</Text>
</View>
)}
<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>
)}
/>
</ScrollView>
);
}One-shot pipeline (Node / browser)
// File in → parsed JSON + rendered HTML + ATS feedback out
async function runFullPipeline(file, templateId = 1) {
// 1. Upload
const form = new FormData();
form.append("file", file);
const upload = await fetch("/api/upload-resume", { method: "POST", body: form }).then(r => r.json());
// 2. Render + ATS in parallel
const [html, ats] = await Promise.all([
fetch(`/api/templates/${templateId}/html`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(upload.data),
}).then((r) => r.text()),
fetch("/api/ats-analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ upload_id: upload.upload_id }),
}).then((r) => r.json()),
]);
return { upload_id: upload.upload_id, data: upload.data, html, ats };
}16. API Cheatsheet
| Method | Path | Description |
|---|---|---|
| POST | /api/upload-resume | Upload PDF → parsed JSON Resume + ATS scores + upload_id |
| GET | /api/resume/{upload_id} | Retrieve stored parsed resume |
| GET | /api/jsonresume/{upload_id} | Same as above, with explicit JSON Resume envelope |
| GET | /api/schema | JSON Resume schema reference |
| GET | /api/templates | List all 62 templates (filter + paginate) |
| GET | /api/templates/{id} | Single template details |
| GET | /api/templates/categories | Category counts |
| POST | /api/templates/{id}/html | Live preview (JSON Resume → raw HTML) |
| POST | /api/templates/{id}/render | Render template (HTML inside JSON envelope) |
| GET | /api/templates/{id}/preview | Preview saved resume (by upload_id) |
| POST | /api/ats-analyze | Rule-based + AI ATS feedback |