Home Schema Themes Team Docs Donate Open Builder

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.

Flask AI-Powered 20 Templates CORS Enabled REST API

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:

1

Upload a PDF or DOCX

POST /api/upload-resume

Send the PDF or DOCX file as multipart/form-data. You get back parsed JSON with 26+ fields.

2

List available templates

GET /api/templates

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

3

Render the chosen template

GET /api/templates/{id}/preview?upload_id={uid}

Open this URL in a WebView (React Native) or <iframe> (ReactJS) to show the finished resume.

1. Upload Resume

POST /api/upload-resume

Upload a PDF or DOCX file and get back structured, parsed resume data.

Request

Content-Type: multipart/form-data

FieldTypeRequiredDescription
fileFileYesPDF or DOCX resume, max 16 MB

Example

curl -X POST \
  -F "file=@resume.pdf" \
  http://localhost:5000/api/upload-resume
const 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

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 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. No re-upload needed.

curl http://localhost:5000/api/resume/18

Response

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."
    ]
  }
}
Persistence noteOn Render's free tier the SQLite DB is wiped on every deploy. If 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

FieldTypeDescription
upload_idintegerSave this to fetch data later
resume_filestringServer-generated filename
ats_scoresobjectATS scoring metrics (overall_score, breakdown, feedback)
data.personal_informationobjectName, contacts, links
data.professional_summarystringOne-paragraph bio
data.work_experiencearrayJob history
data.educationarrayDegrees and schools
data.projectsarrayFeatured projects
data.hobbiesarrayString list
data.skillsarrayFlat skills list
data.certificationsarrayStructured certs (see below)
data.additional_infoobjectLanguages, awards, etc.

personal_information

FieldType
full_namestring
emailstring
phonestring
locationstring
linkedin_profilestring
portfolio_websitestring

work_experience[]

FieldType
job_titlestring
companystring
locationstring
durationstring (e.g. "Jan 2024 - Present")
start_datestring
end_datestring
responsibilitiesarray of strings (bullets)

education[]

FieldType
degreestring
majorstring
institutionstring
locationstring
start_yearstring
end_yearstring
gradestring

projects[]

FieldType
namestring
descriptionstring
technologiesarray of strings

certifications[]

FieldType
namestring
issuing_organizationstring
date_obtainedstring

additional_info

FieldTypeDescription
technical_skillsarrayTech-specific skills
soft_skillsarraySoft skills
languagesarraySpoken languages
achievementsarrayPersonal achievements
awardsarrayAwards received
github_urlstringGitHub URL
years_of_experiencenumberCalculated total years
inferred_job_titlestringMost recent title

4. List Templates

GET /api/templates

Returns all 10 available resume templates.

Query Parameters (optional)

ParamValuesExample
categorymodern, classic, creative?category=creative
is_premiumtrue, false?is_premium=false
searchany keyword?search=developer
pageinteger (default: 1)?page=2
limitinteger (default: 10)?limit=5

Example

curl http://localhost:5000/api/templates?category=modern

Response

{
  "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"
      }
    ]
  }
}
Rendering a Template GalleryUse the thumbnail URL for the gallery card image and the preview_url when the user taps a template.

5. Single Template

GET /api/templates/<id>

Fetch one template by numeric ID (1-10).

curl http://localhost:5000/api/templates/3

6. Template Categories

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

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

Accepts Grouped OR Flat OR upload_idThis endpoint automatically converts whatever format you send. Your React form can send its state directly — no mapping needed.

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)

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.).

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)

GET /api/templates/<id>/preview

Returns rendered HTML page directly. Ideal when you have a stored upload_id and want a simple URL for iframe/WebView.

Query Parameters

ParamTypeDescription
upload_idIntegerOmit 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=18

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

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

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
      }
    }
  }
}
HTTPCodeMeaning
400NO_FILE_FIELDMissing file field
400EMPTY_FILENAMEEmpty filename
404NOT_FOUNDUpload or template not found
413FILE_TOO_LARGEFile exceeds 16 MB
415INVALID_FILE_TYPEUnsupported file type (only PDF and DOCX accepted)
422EMPTY_TEXTCouldn't extract text (scanned/corrupt PDF/DOCX or empty file)
422AI_PARSE_FAILEDAI returned non-JSON response
500SAVE_FAILEDServer 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

MethodEndpointPurpose
POST/api/upload-resumeUpload PDF or DOCX → parsed JSON + upload_id
GET/api/resume/<upload_id>Retrieve stored parsed resume
GET/api/templatesList all 20 templates
GET/api/templates/<id>Single template details
GET/api/templates/categoriesCategory counts
POST/api/templates/<id>/htmlLive preview (form → raw HTML)
POST/api/templates/<id>/renderRender template (HTML inside JSON)
GET/api/templates/<id>/previewPreview saved resume (by upload_id)
Got It? You're Ready!This API is designed so a mobile or web dev can integrate in under 30 minutes. If something's unclear, ping the backend team.