Refleum API
v2.2An API-first SaaS that enables developers to build resume tailoring workflows into their own products. Upload a resume, pass a job description, and receive a tailored resume, cover letter, and outreach message — all grounded strictly in the original resume content. No fabricated skills, no invented metrics.
Base URL
Protocol
REST over HTTPSResponse Format
application/jsonAPI-key auth
Per-org keys, revocable any time
Multi-tenant
All data scoped to your organization
Truthful AI
Never fabricates skills or experience
Single-step
One request in, complete response out
Quickstart
Get your first tailored resume in 3 API calls.
Upload your resume
curl -X POST https://refleum.vercel.app/api/v1/resumes \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@resume.pdf" \
-F "set_as_master=true"Tailor to a job description
curl -X POST https://refleum.vercel.app/api/v1/resumes/{resume_id}/tailor \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"job_description": "We are looking for a Senior Software Engineer...",
"strategy": "KEYWORDS",
"generate_pdf": true
}'Download the tailored PDF
curl https://refleum.vercel.app/api/v1/resumes/{resume_id}/pdf \
--output tailored_resume.pdfAuthentication
All endpoints require a valid organization API key passed in the Authorization header or the x-api-key header. Keys are created and revoked from your organization dashboard. Each key is hashed (SHA-256) and shown only once at creation.
Authorization: Bearer sk_live_••••••••••••••••
x-api-key: sk_live_••••••••••••••••organizationId is always derived from your verified API key — it is never read from the request body. A leaked key must never impersonate a session.Rate Limits & Plans
Limits are enforced per organization using a sliding window via Upstash Redis. Every response includes rate-limit headers.
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1748400060
Retry-After: 42 # only present on 429| Plan | Price / mo | Tailor calls | Rate limit | Features |
|---|---|---|---|---|
| Free | $0 | 10 / month | 1 / min | Upload, CRUD, PDF export |
| Starter | $29 | 200 / month | 20 / min | + Tailoring, cover letters, outreach |
| Pro | $99 | 1,000 / month | 60 / min | + Parallel generation, multi-language |
| Enterprise | Custom | Unlimited | Custom | + Custom rate limits, SLA, isolation |
Error Responses
All errors share a consistent envelope. The detail field is present on validation errors.
{ "error": "SNAKE_CASE_LITERAL", "detail": "string?" }| HTTP | Code | Meaning |
|---|---|---|
| 401 | MISSING_KEY / INVALID_KEY | API key missing or invalid |
| 401 | UNAUTHORIZED | Authentication failed |
| 403 | PLAN_REQUIRED | Endpoint requires Starter+ plan |
| 400 | VALIDATION_ERROR | Body or query validation failed |
| 400 | NO_MASTER_RESUME | Tailoring requires a master resume or valid resume_id |
| 400 | NO_JOB_DESCRIPTION | Regenerate requires a linked job description |
| 404 | NOT_FOUND | Resource not found or belongs to another organization |
| 429 | RATE_LIMITED | Request rate limit exceeded |
| 504 | TIMEOUT | Tailoring exceeded the hard timeout |
| 503 | PDF_RENDER_FAILED | PDF rendering failed |
| 500 | INTERNAL_ERROR | Unexpected server error |
API Playground
Try requests live/api/v1/resumesFile Upload
Drop PDF or DOCX here, or browse
Max 4 MB
Request Body
Resumes
/api/v1/resumesUpload a PDF or DOCX resume (≤ 10 MB) via multipart/form-data. The file is parsed to structured JSON via LLM in the background — the record is returned immediately with status PROCESSING. Accepts an optional set_as_master boolean. If no master exists for the org, the uploaded resume is automatically designated master.
Request — multipart/form-data
filerequired | File | PDF or DOCX file. Max 10 MB. |
set_as_master | boolean | Promote this resume to master for the org. Auto-masters if no master exists. |
Response — 201
{
"data": {
"resume_id": "res_01jwzxy8k3p5q",
"is_master": true,
"status": "PROCESSING",
"filename": "resume.pdf",
"warnings": []
}
}/api/v1/resumesList all resumes for your organization, sorted by updated_at desc. Supports filtering to include the master resume separately.
include_master | boolean | Include master resume in results. Default: false. |
/api/v1/resumes/{id}Retrieve a single resume including its structured_data, job_description, job_keywords, strategy, and parent_id. Org-scoped — returns 404 for IDs belonging to other orgs.
/api/v1/resumes/{id}Manually update resume fields. All fields are optional. No LLM call is made.
title | string | Display title for the resume. |
structuredData | object | Partial or full ResumeData object to overwrite structured content. |
status | string | Resume status enum value. |
filename | string | Display filename. |
/api/v1/resumes/{id}Delete a resume. Cascades to all linked CoverLetter and Outreach records.
/api/v1/resumes/{id}/tailorStarter+Tailor the specified resume to a job description. Runs the full pipeline (keyword extraction → diff generation → safety nets → multi-pass refinement) and persists the tailored resume immediately. Hard timeout: 240 s.
Request body (application/json)
job_descriptionrequired | string | Full text of the job description. Must be between 100 and 8,000 words. Used for keyword extraction and tailoring. |
strategy | "NUDGE" | "KEYWORDS" | "FULL" | NUDGE = minimal edits; KEYWORDS = reword bullets; FULL = comprehensive rewrite. Default: "NUDGE" |
output_language | string | BCP-47 language tag for all LLM output (e.g. "en", "es", "zh"). Default: "en". |
generate_pdf | boolean | Render and cache a PDF after tailoring completes. Default: false. |
Response — 201
{
"data": {
"resume_id": "res_01jx2abc99def",
"is_master": false,
"status": "READY",
"filename": "resume.pdf",
"strategy": "KEYWORDS",
"job_keywords": {
"required_skills": ["TypeScript", "React"],
"preferred_skills": ["GraphQL"],
"keywords": ["API", "SaaS"],
"key_responsibilities": ["Build APIs"],
"experience_years": 3,
"seniority_level": "Senior"
},
"parent_id": "res_01jwzxy8k3p5q",
"pdf_url": "https://app.refleum.com/api/v1/resumes/res_01jx2abc99def/pdf",
"refinement_stats": {
"passes_completed": 3,
"keywords_injected": 5,
"ai_phrases_removed": ["leveraged", "spearheaded"],
"alignment_violations_fixed": 0,
"initial_match_percentage": 54.2,
"final_match_percentage": 81.7
},
"warnings": []
}
}201 is always returned when the pipeline completes. Hard timeout: 240 s → 504 TIMEOUT.
/api/v1/resumes/{id}/retryRe-run LLM parsing on the stored markdown. Useful when status is 'FAILED'. No request body required.
/api/v1/resumes/{id}/pdfNo authentication required. Returns the raw PDF binary (application/pdf) for the resume. Serves a cached copy when available; otherwise renders on-the-fly from stored HTML and caches the result. Use Content-Disposition: inline for preview or attachment for download.
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="resume.pdf"
<binary PDF data>Cover Letters
/api/v1/resumes/{id}/cover-lettersList cover letters for a specific resume.
idrequired | string | Resume ID to list cover letters for. |
limit | number | Page size. Default: 20. |
offset | number | Pagination offset. |
/api/v1/resumes/{id}/cover-lettersStarter+Generate a new cover letter for the specified resume. Accepts an optional job_description override; falls back to the resume's stored job description.
job_description | string | Optional JD override. Falls back to the linked resume's stored JD if omitted. |
/api/v1/resumes/{id}/cover-letters/{clId}Retrieve a single cover letter including its full content.
/api/v1/resumes/{id}/cover-letters/{clId}Delete a cover letter record.
/api/v1/resumes/{id}/cover-letters/{clId}/regenerateStarter+Regenerate the cover letter via AI. Optionally override the stored job description. Updates the record in place (same ID).
job_description | string | Optional JD override. Falls back to the linked resume's stored JD if omitted. |
Outreach Messages
/api/v1/resumes/{id}/outreachList outreach messages for a specific resume.
idrequired | string | Resume ID to list outreach messages for. |
limit | number | Page size. Default: 20. |
offset | number | Pagination offset. |
/api/v1/resumes/{id}/outreachStarter+Generate a new outreach message for the specified resume. Accepts an optional job_description override; falls back to stored resume JD.
job_description | string | Optional JD override. Falls back to the linked resume's stored JD if omitted. |
/api/v1/resumes/{id}/outreach/{oId}Retrieve a single outreach message.
/api/v1/resumes/{id}/outreach/{oId}Delete an outreach message.
/api/v1/resumes/{id}/outreach/{oId}/regenerateStarter+Regenerate the outreach message via AI. Optionally override the stored job description. Updates in place (same ID).
job_description | string | Optional JD override. Falls back to the linked resume's stored JD if omitted. |
Settings & Health
/api/v1/llm-configRetrieve the organisation's LLM configuration. The stored API key is masked in the response.
/api/v1/llm-configUpdate LLM provider, model, API key, and feature flags. All fields are optional — omit any field to leave it unchanged.
provider | string | LLM provider identifier (e.g. openai, anthropic). |
model | string | Model name (e.g. gpt-4o, claude-3-5-sonnet). |
api_key | string | Raw API key for the provider (stored encrypted). |
api_base | string | null | Custom base URL for self-hosted or proxy endpoints. |
reasoning_effort | "minimal"|"low"|"medium"|"high" | Reasoning effort for supported models. Pass empty string to clear. |
enable_cover_letter | boolean | Enable automatic cover letter generation during tailoring. |
enable_outreach_message | boolean | Enable automatic outreach message generation during tailoring. |
content_language | string | Default output language (BCP-47 tag, e.g. en, es, zh). |
default_prompt_id | string | Default tailoring strategy (nudge | keywords | full). |
/api/v1/healthPublic endpoint — no authentication required. Returns database liveness status. Useful for uptime monitors and deployment smoke tests.
// 200 OK
{ "data": { "status": "ok", "db": "ok" } }
// 503 — DB unreachable
{ "error": "INTERNAL_ERROR", "detail": "Database unreachable" }Data Models
ResumeData
The structured representation of a parsed resume. Returned in structured_data on resume GET and in resume_data on tailor responses.
type ResumeData = {
personalInfo: {
name: string; title: string; email: string; phone: string;
location: string; website?: string; linkedin?: string; github?: string;
}
summary: string
workExperience: Array<{
id: number; title: string; company: string;
location?: string; years: string; description: string[];
}>
education: Array<{
id: number; institution: string; degree: string;
years: string; description?: string;
}>
personalProjects: Array<{
id: number; name: string; role: string; years: string;
github?: string; website?: string; description: string[];
}>
additional: {
technicalSkills: string[]; languages: string[];
certificationsTraining: string[]; awards: string[];
}
customSections: Record<string, CustomSection>
}JobKeywords
Extracted from the job description during the tailoring pipeline. Returned in job_keywords on tailored resume records.
type JobKeywords = {
required_skills: string[]
preferred_skills: string[]
keywords: string[]
key_responsibilities: string[]
experience_years: number | null
seniority_level: string | null
}RefinementStats
Included in every tailor response. Summarises the outcome of the 3-pass refinement step.
type RefinementStats = {
passes_completed: number // 0–3
keywords_injected: number // keywords added in Pass 1
ai_phrases_removed: string[] // phrases removed in Pass 2
alignment_violations_fixed: number // fabrications removed in Pass 3
initial_match_percentage: number // % before refinement
final_match_percentage: number // % after refinement
}Pass 1 — Keyword Injection: Only injects keywords that already exist in the master resume (whole-word boundary matching). Non-injectable keywords are never added.
Pass 2 — AI Phrase Removal: Regex pass removing blacklisted phrases (leveraged, spearheaded, orchestrated, etc.). Phrases appearing in the JD are protected.
Pass 3 — Alignment Validation: Compares tailored resume against master. Fabricated skills, certifications, and employers are automatically removed.