Resume Parser API

Upload a PDF → get parsed JSON. Render into 62 beautiful templates. AI-powered ATS scoring. No signup required.

Next.jsAI-Powered62 TemplatesCORS EnabledREST APIJSON ResumeFree

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:

1

Upload a PDF

POST/api/upload-resume

Send the PDF as multipart/form-data. You get back parsed JSON Resume + ATS scores + upload_id.

2

List available templates

GET/api/templates

Show thumbnails in a grid and let the user pick one of 62 designs.

3

Render the chosen template

POST/api/templates/{id}/html

Pipe the parsed JSON (or upload_id) back in and stream HTML into an iframe's srcDoc.

4

Get ATS feedback

POST/api/ats-analyze

Score the resume with rule-based + AI feedback to drive your ATS UI.

1. Upload Resume

POST/api/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

FieldTypeRequiredDescription
fileFileYesPDF 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

JSON Resume SchemaThe response uses the standardized JSON Resume shape: 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."
    ]
  }
}
Save the upload_idupload_id is at the top level. Save it locally to fetch later via /api/resume/<upload_id> or to render templates without re-uploading.
BidirectionalThe same JSON Resume payload can be sent back to /api/templates/<id>/html or /render — your data stays in the same shape end-to-end.

2. Get Parsed Resume

GET/api/resume/{upload_id}

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."
    ]
  }
}
Persistence noteIf upload_id returns 404, the upload record was wiped (e.g. ephemeral container restart). Re-upload the PDF.

3. Get as JSON Resume

GET/api/jsonresume/{upload_id}

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

FieldTypeDescription
upload_idintegerSave this to fetch data later
resume_filestringServer-generated filename
schemastringAlways "jsonresume"
dataobjectJSON Resume payload (see below)
ats_scoresobjectOverall score + breakdown + feedback

basics

FieldTypeDescription
namestringFull name
labelstringHeadline / current title
imagestringOptional photo URL
emailstringPrimary email
phonestringPrimary phone
urlstringPortfolio / personal site
summarystringOne-paragraph bio
locationobjectaddress, city, region, postalCode, countryCode
profiles[]arraynetwork, username, url

work[]

FieldType
namestring (company)
positionstring
urlstring
startDatestring (YYYY-MM-DD)
endDatestring
summarystring
highlights[]array of strings (bullets)

education[]

FieldType
institutionstring
urlstring
areastring (field of study)
studyTypestring (Bachelor, Master, ...)
startDatestring
endDatestring
scorestring (GPA)
courses[]array of strings

skills[]

FieldType
namestring (skill group)
levelstring (Beginner ... Master)
keywords[]array of strings

projects[]

FieldType
namestring
descriptionstring
highlights[]array of strings
urlstring
startDatestring
endDatestring

certificates[]

FieldType
namestring
datestring
issuerstring
urlstring

awards[]

FieldType
titlestring
datestring
awarderstring
summarystring

publications[]

FieldType
namestring
publisherstring
releaseDatestring
urlstring
summarystring

languages[]

FieldType
languagestring
fluencystring

interests[]

FieldType
namestring
keywords[]array of strings

references[]

FieldType
namestring
referencestring

volunteer[]

FieldType
organizationstring
positionstring
urlstring
startDatestring
endDatestring
summarystring
highlights[]array of strings

ats_scores

FieldTypeDescription
overall_scorenumber (0-100)Composite ATS score
breakdownobjectPer-section sub-scores
feedback[]array of stringsHuman-readable tips

5. List Templates

GET/api/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)

ParamValuesExample
categorymodern, classic, creative?category=creative
is_premiumtrue, false?is_premium=false
searchany keyword (name, tag, description)?search=developer
pageinteger (default: 1)?page=2
limitinteger (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"
      }
    ]
  }
}
Rendering a Template GalleryUse the thumbnail URL for the gallery card image and the preview_url when the user taps a template.

6. Single Template

GET/api/templates/{id}

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

GET/api/templates/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)

POST/api/templates/{id}/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.

Accepts JSON Resume OR upload_idSend the full JSON Resume body, or just { "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)

POST/api/templates/{id}/render

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)

GET/api/templates/{id}/preview

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

ParamTypeDescription
upload_idintegerOmit 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 }}
/>
When to use which?
  • 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.

IDNameCategoryLayoutPhotoPremium
1The Minimalistmoderntwo_columnFree
2The Creativecreativesidebar_leftFree
3Dark Mode Devmoderntwo_columnFree
4The Executiveclassictwo_columnFree
5Modern Splitmodernsplit_headerFree
6Gradient Glowmoderntwo_columnFree
7Infographiccreativesidebar_leftFree
8Academic Scholarclassicsingle_columnFree
9Portfolio Cardcreativesidebar_leftFree
10Marketing Dynamicmoderntwo_columnFree
11Photo Classicclassicsingle_columnYesFree
12Photo Modernmoderntwo_columnYesFree
13Photo Designercreativesidebar_leftYesFree
14Photo Executiveclassictwo_columnYesFree
15Photo Minimalmodernsingle_columnYesFree
16Photo Corporateclassicsingle_columnYesFree
17Photo Creativecreativesidebar_leftYesFree
18Photo Elegantclassictwo_columnYesFree
19Compact Promodernsingle_columnFree
20Timelinecreativesingle_columnFree
21Tech Sleekmoderntwo_columnFree
22Data Scientistmodernsingle_columnFree
23Cyber Shieldcreativesingle_columnFree
24Game Developermodernsidebar_leftYesFree
25Cloud Engineermodernsingle_columnFree
26Finance Proclassictwo_columnFree
27Consultant Eliteclassicsidebar_leftFree
28Sales Powermodernsingle_columnFree
29Startup Foundermodernsingle_columnFree
30Project Managermodernsingle_columnFree
31Healthcaremodernsidebar_leftYesFree
32Pharma Professionalclassicsingle_columnFree
33Lab Scientistclassictwo_columnFree
34Veterinary Caremodernsingle_columnYesFree
35Dental Promodernsidebar_rightYesFree
36Fashion Editorialclassicsingle_columnYesFree
37Photographerclassicsingle_columnYesFree
38Music Artistmodernsingle_columnFree
39Journalistclassictwo_columnFree
40Architectmodernsidebar_leftFree
41Construction Managermodernsingle_columnFree
42Hospitalityclassicsingle_columnFree
43Chef Culinaryclassicsidebar_rightYesFree
44Fitness Trainermodernsingle_columnYesFree
45Aviation Pilotmodernsingle_columnFree
46Educatorclassicsidebar_leftYesFree
47Legal Briefclassicsingle_columnFree
48NGO / Humanitarianmodernsingle_columnFree
49Real Estate Agentmodernsidebar_leftYesFree
50Universal Proclassictwo_columnFree
51Pearson Engineermodernsingle_columnFree
52Paulsen Strategistmodernsingle_columnFree
53Pearson HRmoderntwo_columnYesFree
54Ross Projectmodernsingle_columnYesFree
55Elegantclassicsingle_columnFree
56Creative Sidebarmodernsidebar_leftYesFree
57Executiveclassicsingle_columnYesFree
58Classicclassicsingle_columnFree
59Standardmodernsingle_columnYesFree
60Luminaryclassicsingle_columnYesFree
61Simplemodernsingle_columnFree
62Zenithmoderntwo_columnYesFree

12. ATS Analyze

POST/api/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)

NameTypeRequiredDescription
resumeobject*JSON Resume object to analyze.
upload_idinteger*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:

FieldTypeDescription
missing_sectionsstring[]Sections the resume is missing
weak_areasstring[]Specific weaknesses with explanations
keyword_suggestionsstring[]Keywords to add for the target role
action_verb_upgrades{from, to}[]Suggested replacements for weak verbs
quantification_tipsstring[]Hints for adding numbers/metrics
summary_rewritestringSuggested rewritten professional summary
strengthsstring[]What the resume does well
ats_risk_flagsstring[]Layout / formatting risks for ATS parsers
overall_recommendationstringOne-paragraph plan of action
inferred_target_rolestringRole the AI inferred from the content
ai_poweredbooleanfalse if the AI provider was unavailable and fallback rules were used
Fallback behaviorIf the AI provider is unreachable, 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
}
HTTPCodeMeaning
400NO_FILE_FIELDMissing file field on upload
400EMPTY_FILENAMEUploaded file has empty filename
404NOT_FOUNDUpload or template not found
413FILE_TOO_LARGEFile exceeds 16 MB
415INVALID_FILE_TYPEUnsupported file type (only PDF accepted)
422EMPTY_TEXTCouldn't extract text (scanned or corrupt PDF)
422AI_PARSE_FAILEDAI returned non-JSON response
500SAVE_FAILEDServer 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

MethodPathDescription
POST/api/upload-resumeUpload 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/schemaJSON Resume schema reference
GET/api/templatesList all 62 templates (filter + paginate)
GET/api/templates/{id}Single template details
GET/api/templates/categoriesCategory counts
POST/api/templates/{id}/htmlLive preview (JSON Resume → raw HTML)
POST/api/templates/{id}/renderRender template (HTML inside JSON envelope)
GET/api/templates/{id}/previewPreview saved resume (by upload_id)
POST/api/ats-analyzeRule-based + AI ATS feedback
You're ready to integrate.A mobile or web dev can hook up the full pipeline in under 30 minutes. Every endpoint speaks the same JSON Resume shape end to end — parse, render, analyze.