Docsoverview

Refleum API

v2.2

An 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 HTTPS

Response Format

application/json
🔑

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

1

Upload your resume

bash
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"
2

Tailor to a job description

bash
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
  }'
3

Download the tailored PDF

bash
curl https://refleum.vercel.app/api/v1/resumes/{resume_id}/pdf \
  --output tailored_resume.pdf

Authentication

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.

http
Authorization: Bearer sk_live_••••••••••••••••
x-api-key: sk_live_••••••••••••••••
Security note: The 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.

http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1748400060
Retry-After: 42   # only present on 429
PlanPrice / moTailor callsRate limitFeatures
Free$010 / month1 / minUpload, CRUD, PDF export
Starter$29200 / month20 / min+ Tailoring, cover letters, outreach
Pro$991,000 / month60 / min+ Parallel generation, multi-language
EnterpriseCustomUnlimitedCustom+ Custom rate limits, SLA, isolation

Error Responses

All errors share a consistent envelope. The detail field is present on validation errors.

json
{ "error": "SNAKE_CASE_LITERAL", "detail": "string?" }
HTTPCodeMeaning
401MISSING_KEY / INVALID_KEYAPI key missing or invalid
401UNAUTHORIZEDAuthentication failed
403PLAN_REQUIREDEndpoint requires Starter+ plan
400VALIDATION_ERRORBody or query validation failed
400NO_MASTER_RESUMETailoring requires a master resume or valid resume_id
400NO_JOB_DESCRIPTIONRegenerate requires a linked job description
404NOT_FOUNDResource not found or belongs to another organization
429RATE_LIMITEDRequest rate limit exceeded
504TIMEOUTTailoring exceeded the hard timeout
503PDF_RENDER_FAILEDPDF rendering failed
500INTERNAL_ERRORUnexpected server error

API Playground

Try requests live
POST/api/v1/resumes

File Upload

Drop PDF or DOCX here, or browse

Max 4 MB

Request Body

false

Resumes

POST/api/v1/resumes

Upload 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

filerequiredFilePDF or DOCX file. Max 10 MB.
set_as_masterbooleanPromote this resume to master for the org. Auto-masters if no master exists.

Response — 201

json
{
  "data": {
    "resume_id": "res_01jwzxy8k3p5q",
    "is_master": true,
    "status": "PROCESSING",
    "filename": "resume.pdf",
    "warnings": []
  }
}
GET/api/v1/resumes

List all resumes for your organization, sorted by updated_at desc. Supports filtering to include the master resume separately.

include_masterbooleanInclude master resume in results. Default: false.
GET/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.

PATCH/api/v1/resumes/{id}

Manually update resume fields. All fields are optional. No LLM call is made.

titlestringDisplay title for the resume.
structuredDataobjectPartial or full ResumeData object to overwrite structured content.
statusstringResume status enum value.
filenamestringDisplay filename.
DELETE/api/v1/resumes/{id}

Delete a resume. Cascades to all linked CoverLetter and Outreach records.

POST/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_descriptionrequiredstringFull 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_languagestringBCP-47 language tag for all LLM output (e.g. "en", "es", "zh"). Default: "en".
generate_pdfbooleanRender and cache a PDF after tailoring completes. Default: false.

Response — 201

json
{
  "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 s504 TIMEOUT.

POST/api/v1/resumes/{id}/retry

Re-run LLM parsing on the stored markdown. Useful when status is 'FAILED'. No request body required.

GET/api/v1/resumes/{id}/pdf

No 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
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="resume.pdf"

<binary PDF data>

Cover Letters

GET/api/v1/resumes/{id}/cover-letters

List cover letters for a specific resume.

idrequiredstringResume ID to list cover letters for.
limitnumberPage size. Default: 20.
offsetnumberPagination offset.
POST/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_descriptionstringOptional JD override. Falls back to the linked resume's stored JD if omitted.
GET/api/v1/resumes/{id}/cover-letters/{clId}

Retrieve a single cover letter including its full content.

DELETE/api/v1/resumes/{id}/cover-letters/{clId}

Delete a cover letter record.

POST/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_descriptionstringOptional JD override. Falls back to the linked resume's stored JD if omitted.

Outreach Messages

GET/api/v1/resumes/{id}/outreach

List outreach messages for a specific resume.

idrequiredstringResume ID to list outreach messages for.
limitnumberPage size. Default: 20.
offsetnumberPagination offset.
POST/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_descriptionstringOptional JD override. Falls back to the linked resume's stored JD if omitted.
GET/api/v1/resumes/{id}/outreach/{oId}

Retrieve a single outreach message.

DELETE/api/v1/resumes/{id}/outreach/{oId}

Delete an outreach message.

POST/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_descriptionstringOptional JD override. Falls back to the linked resume's stored JD if omitted.

Settings & Health

GET/api/v1/llm-config

Retrieve the organisation's LLM configuration. The stored API key is masked in the response.

PUT/api/v1/llm-config

Update LLM provider, model, API key, and feature flags. All fields are optional — omit any field to leave it unchanged.

providerstringLLM provider identifier (e.g. openai, anthropic).
modelstringModel name (e.g. gpt-4o, claude-3-5-sonnet).
api_keystringRaw API key for the provider (stored encrypted).
api_basestring | nullCustom 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_letterbooleanEnable automatic cover letter generation during tailoring.
enable_outreach_messagebooleanEnable automatic outreach message generation during tailoring.
content_languagestringDefault output language (BCP-47 tag, e.g. en, es, zh).
default_prompt_idstringDefault tailoring strategy (nudge | keywords | full).
GET/api/v1/health

Public endpoint — no authentication required. Returns database liveness status. Useful for uptime monitors and deployment smoke tests.

json
// 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.

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

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

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