ResumeGuard

Resume screening API

Resume Screening & Verification API

ResumeGuard is a developer-first resume screening API and AI resume verification service. POST a resume file and receive structured JSON with composite, fabrication, and polish risk scores, per-dimension breakdowns, evidence flags, and interview questions — built for ATS, HRIS, and batch hiring pipelines. See the ATS / HRIS API page, integrations, or the ATS implementation guide for POST, poll, and ?wait=1.

Try one free resume validation in the browser, then add a payment method and generate API keys for production resume fraud detection at scale.

Quick start

  1. Create an account and add a payment method in the dashboard.
  2. Generate an API key from the dashboard.
  3. POST a resume file to /api/analyses.

Authentication

Include your API key on every request:

Authorization: Bearer rg_live_xxxxxxxx
Content-Type: multipart/form-data

Anonymous free checks use the X-Anonymous-Session header (issued by POST /api/auth/anonymous-session).

Rate limits

Short-term rate limits protect the service from abuse. They are separate from billing — you are charged per resume screened, not per request within these limits.

PlanKindPer minuteBurstApplies to
Free (anonymous)Write58POST / DELETE analysis uploads
Free (anonymous)Read6090GET poll / list (status checks)
Paid (API key)Write3060Authenticated POST / DELETE
Paid (API key)Read120180Authenticated GET poll / list

Every API response includes rate-limit headers so clients can back off before hitting a hard limit:

Example 429 response:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please slow down.",
    "limit": 30,
    "reset": 1718270400
  }
}

Async screening (default)

Each POST accepts one file. To screen multiple resumes, send parallel POST requests (one per file). The dashboard uses this pattern — there is no multi-file field on a single request.

  1. POST /api/analyses returns 202 Accepted with { id, status: "processing", filename }.
  2. Poll GET /api/analyses/{id} every 1–2 seconds until status is complete (or failed).
  3. Optionally use GET /api/analyses to list in-flight and completed screenings.

For synchronous integrations, add ?wait=1 to the POST — the server holds the connection until scoring finishes and returns 201 Created with the full report.

Example request

# Default: async (202 + poll)
curl -X POST https://www.resumeguard.io/api/analyses \
  -H "Authorization: Bearer rg_live_YOUR_KEY" \
  -F "file=@candidate-resume.pdf"

# Synchronous (201 with full JSON)
curl -X POST "https://www.resumeguard.io/api/analyses?wait=1" \
  -H "Authorization: Bearer rg_live_YOUR_KEY" \
  -F "file=@candidate-resume.pdf"

Example responses

202 Accepted — analysis queued; poll by id until complete.

{
  "id": "ana_k9m2x7p4q1",
  "status": "processing",
  "filename": "candidate-resume.pdf",
  "message": "Analysis queued. Poll GET /api/analyses/{id} until status is complete."
}

201 Created (with ?wait=1, or after polling) — overall scores in scores, per-dimension values in dimensions[] (each with id, label, score 0–100, and flags).

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "complete",
  "duration_ms": 14280,
  "candidate": {
    "name": "Jane Doe",
    "location": "Seattle, WA 98144"
  },
  "scores": {
    "composite_risk": 78,
    "fabrication_risk": 82,
    "polish_risk": 26,
    "confidence": 0.91,
    "raw_score": 78
  },
  "recommended_action": "escalate",
  "summary": "High fabrication risk (78/100). Resume uses obvious placeholder identity (e.g. Jane/John Doe, fake street, or example contact info) — treat as a sample or invalid submission.",
  "dimensions": [
    {
      "id": "timeline_integrity",
      "label": "Timeline Integrity",
      "score": 5,
      "weight": 0.12,
      "effective_weight": 0.12,
      "skipped": false,
      "confidence": 0.72,
      "flags": []
    },
    {
      "id": "career_trajectory",
      "label": "Career Trajectory",
      "score": 8,
      "weight": 0.1,
      "effective_weight": 0.1,
      "skipped": false,
      "confidence": 0.72,
      "flags": []
    },
    {
      "id": "claim_plausibility",
      "label": "Claim Plausibility",
      "score": 43,
      "weight": 0.1,
      "effective_weight": 0.1,
      "skipped": false,
      "confidence": 0.92,
      "flags": [
        {
          "id": "f1",
          "severity": "critical",
          "code": "placeholder_identity",
          "title": "Obvious placeholder name or contact information",
          "detail": "Resume uses example-style identity markers…",
          "evidence": [
            {
              "type": "rule",
              "ref": "placeholder_identity"
            }
          ]
        }
      ]
    },
    {
      "id": "technology_authenticity",
      "label": "Technology Authenticity",
      "score": 4,
      "weight": 0.12,
      "effective_weight": 0.12,
      "skipped": false,
      "confidence": 0.72,
      "flags": []
    },
    {
      "id": "cross_section_consistency",
      "label": "Cross-Section Consistency",
      "score": 55,
      "weight": 0.1,
      "effective_weight": 0.1,
      "skipped": false,
      "confidence": 0.92,
      "flags": []
    },
    {
      "id": "entity_verification",
      "label": "Employer & Education Verification",
      "score": 45,
      "weight": 0.14,
      "effective_weight": 0.14,
      "skipped": false,
      "confidence": 0.92,
      "flags": []
    },
    {
      "id": "credential_corroboration",
      "label": "Credential Verification",
      "score": 0,
      "weight": 0.1,
      "effective_weight": 0.1,
      "skipped": false,
      "confidence": 0.72,
      "flags": []
    },
    {
      "id": "digital_footprint",
      "label": "Professional Footprint",
      "score": 43,
      "weight": 0.08,
      "effective_weight": 0.08,
      "skipped": false,
      "confidence": 0.92,
      "flags": []
    },
    {
      "id": "document_provenance",
      "label": "Document Provenance",
      "score": 43,
      "weight": 0.08,
      "effective_weight": 0.08,
      "skipped": false,
      "confidence": 0.92,
      "flags": []
    },
    {
      "id": "synthetic_language_signals",
      "label": "Writing Pattern Analysis",
      "score": 5,
      "weight": 0.06,
      "effective_weight": 0.06,
      "skipped": false,
      "confidence": 0.72,
      "flags": []
    }
  ],
  "entities": [
    {
      "type": "employer",
      "name": "Example Corp",
      "status": "not_found",
      "claimed": {
        "title": "Software Engineer",
        "start": "2020",
        "end": "Present"
      },
      "checks": [
        "web_search"
      ],
      "evidence": []
    }
  ],
  "interview_questions": [
    "This resume lists example-style contact details — is this a sample document or your legal name and current address?"
  ],
  "usage": {
    "tier": "paid",
    "billed": true,
    "amount_usd": 0.75
  }
}

422 Unprocessable Entity with error.code: "not_a_resume" — the file is not a resume or CV. No analysis row is created and no usage is charged. The response includes a reasons array explaining why.

composite_risk — weighted overall concern (0–100)

fabrication_risk — dimensions 1–8 (factual claims)

polish_risk— dimensions 9–10 (document & writing)

recommended_action — proceed · review_recommended · escalate

HTML report (iframe / modal)

After JSON status is complete, fetch a ready-to-render HTML document for the same analysis id. Auth matches GET /api/analyses/{id} — unauthenticated requests return 401, and a caller may only fetch analyses they own. JSON remains the source of truth for automation; HTML is display-only. The document matches the dashboard: three CSS-only tabs (Overall, Findings, Dimension scores) with a composite donut, concern bars, and grouped dimension scores. No JavaScript — works in sandbox="allow-same-origin" without allow-scripts. Official color theme: ?theme=dark (default, matches the dashboard) or ?theme=light (white/off-white page for a light ATS/HRIS). Same tabs and data; only colors change. Missing or invalid theme values fall back to dark — the endpoint does not return 400.

If the analysis is still processing or pending, this endpoint returns 202 with a small HTML “still running” page. Keep polling the JSON GET until complete, then request the HTML.

# Dark (default — omit theme or pass theme=dark)
curl "https://www.resumeguard.io/api/analyses/ANALYSIS_ID/html" \
  -H "Authorization: Bearer rg_live_YOUR_KEY"

curl "https://www.resumeguard.io/api/analyses/ANALYSIS_ID/html?theme=dark" \
  -H "Authorization: Bearer rg_live_YOUR_KEY"

# Light — match a light ATS / HRIS UI
curl "https://www.resumeguard.io/api/analyses/ANALYSIS_ID/html?theme=light" \
  -H "Authorization: Bearer rg_live_YOUR_KEY"

Browsers do not send API keys on a raw iframe navigation. Fetch with the same Authorization header and assign srcdoc, inject into a modal, or proxy the HTML from your backend. The document is self-contained (inline CSS, no external JS) and is frameable (no X-Frame-Options: DENY). The iframe URL may include ?theme=light or ?theme=dark.

The HTML report includes a Download PDF control that links to GET /api/analyses/{id}/pdf (same Bearer or session auth as the HTML GET). Session-cookie iframes can click through; API-key-only embeds should fetch the PDF the same way they fetch HTML. The PDF is a print of the HTML report (Overall, Findings, and Dimension scores stacked; no tab chrome). It is unbranded (no logo). Official ?theme=dark or ?theme=light; default is light. Missing or invalid theme values fall back to light (never 400).

# Default theme=light. Pass theme=dark to match a dark HTML embed.
curl "https://www.resumeguard.io/api/analyses/ANALYSIS_ID/pdf" \
  -H "Authorization: Bearer rg_live_YOUR_KEY" \
  -o report.pdf
<iframe src="https://www.resumeguard.io/api/analyses/{id}/html?theme=light" sandbox="allow-same-origin" style="width:100%;min-height:640px;border:0"></iframe>

Endpoints

POST/api/analyses

Analyze a resume file

Multipart upload (PDF, DOCX, JPG, PNG). Returns 202 Accepted with { id, status: "processing" } by default — poll GET /api/analyses/{id} until status is complete. Add ?wait=1 for a synchronous 201 with full results. Returns 422 not_a_resume (no charge, no saved row) when the file is not a resume.

GET/api/analyses/{id}

Retrieve a prior analysis

Fetch full results for an analysis owned by your account or session. Poll while status is processing or pending. JSON is the source of truth for automation.

GET/api/analyses/{id}/html

HTML report for iframe or modal display

Same auth and ownership as GET /api/analyses/{id}. Returns a self-contained HTML/CSS document that matches the dashboard: Overall, Findings, and Dimension scores tabs (CSS-only radio tabs; no JS required). Official ?theme=dark (default) or ?theme=light; missing/invalid theme falls back to dark. Unbranded header (no logo). Includes a Download PDF control linking to GET /api/analyses/{id}/pdf. While status is processing or pending, returns 202 with a small “still running” HTML page — keep polling the JSON GET until complete. Display-only; do not parse HTML for scores.

GET/api/analyses/{id}/pdf

PDF download for a completed analysis

Same auth and ownership as GET /api/analyses/{id}/html. Returns an unbranded application/pdf attachment that is a print of the HTML report (Overall, Findings, and Dimension scores stacked; no tab chrome or Download PDF control). Official ?theme=dark|light; default light; missing/invalid theme falls back to light (never 400). No ResumeGuard mark or company logo. While processing, returns 202 JSON. API-key callers should fetch this the same way they fetch HTML; session-cookie iframes can use the HTML Download PDF link directly.

GET/api/analyses

List screening history

Paginated list of analyses for authenticated accounts. Optional filters: q (candidate_name ILIKE), from, to (created_at), min_risk, max_risk. Items include summary and interview_questions. Limit max 50. Use alongside parallel POSTs to track multiple in-flight screenings.

GET/api/analyses/export.csv

Export screening history as CSV

Same session or API-key auth as GET /api/analyses. Accepts the same q, from, to, min_risk, max_risk filters. Returns a CSV of matching rows (up to 200 synchronously). Larger result sets email a short-lived signed download link from hello@ — no file attachment. Most recent 10,000 matching rows. Not a paid-pack gate; columns are analysis id, date, candidate, file, risk (numeric composite_risk, empty when unscored), concern (Low concern / Moderate / Elevated / High concern from composite_risk, empty when unscored), findings summary, interview questions, and a www View URL. Cost is omitted.

GET/api/usage

Current billing period usage

Paid check count and projected bill for authenticated accounts.

POST/api/auth/keys

Create API key

Requires payment method on file. Returns secret once — store it securely.

OpenAPI specification

Full schema, request/response models, and error codes:

Download openapi.yaml →

Questions? Contact hello@resumeguard.io