API Endpoints
ATS Screener exposes a single REST endpoint for all scoring operations.
POST /api/analyze
Section titled “POST /api/analyze”Score a resume against all 6 ATS platforms, or extract requirements from a job description.
Request
Section titled “Request”curl -X POST http://localhost:5173/api/analyze \ -H "Content-Type: application/json" \ -d '{ "mode": "full-score", "resumeText": "John Doe\nSoftware Engineer\n5 years React, TypeScript..." }'Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | "full-score" or "analyze-jd" |
resumeText | string | For full-score | Raw text extracted from resume (max 50,000 chars) |
jobDescription | string | For analyze-jd | Full job description text (max 20,000 chars) |
Validation rules:
Content-Typeheader must beapplication/jsonresumeTextcannot be empty or whitespace-onlyresumeTextmaximum length: 50,000 charactersjobDescriptionmaximum length: 20,000 characters; it must contain non-whitespace text inanalyze-jdmode- Supplied text fields must be strings. Limits use JavaScript
string.length(UTF-16 code units). Oversized requests return400; inputs are never truncated. modemust be exactly"full-score"or"analyze-jd"
full-score
Section titled “full-score”Score a resume against all 6 ATS platforms. Optionally include a jobDescription for targeted scoring.
{ "mode": "full-score", "resumeText": "John Doe\nSoftware Engineer...", "jobDescription": "We are looking for a Senior Frontend Engineer..."}analyze-jd
Section titled “analyze-jd”Extract structured requirements from a job description without scoring a resume.
{ "mode": "analyze-jd", "jobDescription": "We are looking for a Senior Frontend Engineer..."}Response (full-score)
Section titled “Response (full-score)”{ "results": [ { "system": "Workday", "vendor": "Workday Inc.", "overallScore": 75, "passesFilter": true, "breakdown": { "formatting": { "score": 80, "issues": ["Header content may be skipped"], "details": ["Single-column layout detected"] }, "keywordMatch": { "score": 70, "matched": ["React", "TypeScript", "Node.js"], "missing": ["AWS", "CI/CD"], "synonymMatched": ["JavaScript frameworks"] }, "sections": { "score": 85, "present": ["Experience", "Education", "Skills"], "missing": ["Certifications"] }, "experience": { "score": 75, "quantifiedBullets": 8, "totalBullets": 12, "actionVerbCount": 10, "highlights": ["Strong quantification"] }, "education": { "score": 90, "notes": ["BS Computer Science detected"] } }, "suggestions": ["Add AWS and CI/CD keywords to match Workday's exact matching"] } ], "_provider": "gemini-3.5-flash-lite", "_fallback": false, "_cached": false}Response Fields
Section titled “Response Fields”| Field | Type | Description |
|---|---|---|
results | array | Array of 6 platform scoring objects |
results[].system | string | Platform name |
results[].overallScore | number | 0-100 weighted composite score |
results[].passesFilter | boolean | Whether resume passes initial screening |
results[].breakdown | object | Per-dimension scores and details |
results[].suggestions | string | StructuredItem[] | Platform-specific improvement tips. May be plain strings (rule-based) or structured objects (LLM-enhanced) |
_provider | string | Which LLM provider handled the request (e.g. gemini-3.5-flash-lite, groq-llama-3.3-70b) |
_fallback | boolean | true when all providers failed and the client must fall back to local rule-based scoring |
_cached | boolean | true when the response was served from the in-memory result cache (sub-100ms, zero LLM cost) |
The server keeps a SHA-256 keyed in-memory LRU of recent prompts (200 entries, 24h TTL). Identical input hits the cache and returns instantly; the _cached flag tells you whether the response was a hit. The cache lives per Vercel instance; cold starts begin empty.
Auxiliary Endpoints
Section titled “Auxiliary Endpoints”Every path below is publicly reachable. None of them require an API key. The two exceptions to “fully public” are flagged in the Notes column.
Operations and deploy identity
Section titled “Operations and deploy identity”| Path | Method | Purpose | Notes |
|---|---|---|---|
/healthz | GET | Liveness probe. Returns { status: "ok", timestamp, version, commit, env } so a single probe confirms the right build is live. | Cache-Control: no-store. Suitable for cron-job.org, BetterStack, etc. |
/api/version | GET | Public deploy identity. Returns { version, commit, branch, env }. All four fields are non-sensitive (mirror of public GitHub repo). | Browser cache 60s, CDN cache 5m, SWR 1d. |
/api/admin/rate-limit-stats | GET | In-memory counters for the per-IP rate limiter (totalChecks, totalAllowed, totalBlockedMinute, totalBlockedDaily). | Token-gated. Returns 503 if ADMIN_TOKEN env var is unset (16+ chars). Returns 401 on header mismatch. |
Telemetry ingest (sampled, console-only logging)
Section titled “Telemetry ingest (sampled, console-only logging)”| Path | Method | Purpose | Notes |
|---|---|---|---|
/api/csp-report | POST | Receives Content-Security-Policy violation reports for the Report-Only header set by hooks.server.ts. Drops browser-extension noise before the throttle. | 5-minute dedupe window per (directive, blocked-uri), 100/min hard cap, 204 No Content. |
/api/log-error | POST | Sampled client-error reporter wired from window.onerror and unhandledrejection. Body: { message, source, line, col, stack, url, ua, at }. No durable storage. | 60/min rolling cap. 5% default sample rate (env-tunable via PUBLIC_ERROR_SAMPLE_RATE). |
/api/vitals | POST | Sampled web-vitals collector for LCP and CLS. Body: { lcp, cls, url, ua, at }. Uses native PerformanceObserver and navigator.sendBeacon on the client. | 60/min rolling cap. 5% default sample rate (PUBLIC_VITALS_SAMPLE_RATE). |
Share and OpenGraph
Section titled “Share and OpenGraph”| Path | Method | Purpose | Notes |
|---|---|---|---|
/api/og | GET | Edge-cached PNG via @vercel/og for share previews. Query: score, pass, total, optional delta. Function-level LRU memo on top of CDN. | Per-route Cross-Origin-Resource-Policy: cross-origin so social platforms (LinkedIn, X) can scrape. |
/share | GET | Branded share landing page. Reads the same query params and emits og:image pointing at /api/og. Native Web Share + Copy Link buttons. | Static-ish, edge-cached. |
Discoverability and feeds
Section titled “Discoverability and feeds”| Path | Method | Purpose | Notes |
|---|---|---|---|
/robots.txt | GET | Dynamic. Tracks deployment origin. References both the main sitemap and the docs sitemap-index for full crawl coverage. | Cache: browser 1h, CDN 1d, SWR 7d. |
/sitemap.xml | GET | Dynamic. Lists public routes plus key docs landings, each with lastmod, changefreq, and priority. | Cache: browser 1h, CDN 1d, SWR 7d. |
/llms.txt | GET | Curated link list for adopting AI crawlers (Anthropic, OpenAI, Perplexity standard). Dynamic origin so previews match themselves. | Cache: browser 1h, CDN 1d, SWR 7d. |
/releases.xml | GET | RSS 2.0 feed parsed from CHANGELOG.md. One <item> per ## [X.Y.Z] - YYYY-MM-DD block with title, GitHub anchor link, RFC 822 pubDate, stable guid, and CDATA-wrapped markdown body. | ETag round-trip on no-change. Cache: browser 1h, CDN 1d, SWR 7d. |
/.well-known/security.txt | GET | RFC 9116 disclosure channel. Lists Contact, Expires, Canonical, Policy, Acknowledgments, Preferred-Languages. | Static file under static/.well-known/. Served text/plain. |
/manifest.webmanifest | GET | PWA manifest. Name, start_url, display, theme color, icons (favicon.svg plus 192/512 PNG, plus apple-touch-icon). | Static. Browsers auto-fetch via <link rel="manifest">. |
/humans.txt | GET | Author colophon. Optional courtesy file for curious humans and automated scanners. | Static. |
Redirects
Section titled “Redirects”| Path | Method | Behavior |
|---|---|---|
/privacy | GET | 308 redirect to /docs/legal/privacy/. Old footer link kept working after the move. |
Scoring identity and effective input coverage
Section titled “Scoring identity and effective input coverage”Successful responses add server-created _scoringIdentity, _inputCoverage, and _tokenUsage fields. _tokenUsage contains numeric input, output, and thinking token counts when the provider reports them. A cached response retains the original call’s usage; those counts do not represent new token consumption on a cache hit. Usage describes the winning provider response and excludes failed attempts. Existing results and _provider, _fallback, and _cached fields retain their current behavior. The returned platforms are simulations; the API analyzes text and does not inspect the original PDF.
_scoringIdentity uses schema ats-scoring-identity-v1 and records:
mode, the rubric version and SHA-256 fingerprint of the complete prompt templates and effective input limits.deployment, including a public UUID assigned once per build, application version, full commit when supplied by the deployment environment, and environment name. The build UUID also distinguishes builds containing uncommitted changes.- The winning
providerandvendor, its requested model, provider-reported model name/version, and backend fingerprint when supplied. Missing response metadata remainsnull; a requested model or alias is not relabeled as a reported revision. requestConfigurationDigest, a fingerprint of the winning provider’s actual request configuration using a fixed placeholder prompt. Credential headers are excluded.- The versioned text-analysis capability contract.
Cache keys cover the complete effective prompt, rubric, deployment, capabilities, and configured provider request identities. A hit returns the identity saved with the original result. It does not substitute the current preferred model or a separate /api/version observation. These fields identify configured and reported behavior; a provider that reuses a mutable model alias has not supplied an immutable weight identity. Consumers requiring that guarantee must suppress comparisons when the available metadata is insufficient.
Both accepted and effective limits are 50,000 résumé and 20,000 job-description UTF-16 code units. Full scoring (general or targeted) includes the entire résumé and optional job description. analyze-jd includes the entire job description. Full scoring also returns a model-generated evidence ledger of required-skill matches, verbatim résumé quotes, exact/semantic match types, and missing requirements. The ledger anchors platform scores to shared candidate evidence; it is model output, not server-certified fact. The capability version is ats-analyze-text-v2, with truncatesOversizedInput: false. Requests beyond either limit are rejected before contacting a provider. Malformed generated JSON is retried once with the identical full prompt and output budget, within the same provider deadline. Other provider failures do not trigger retries with shorter prompts or reduced output budgets. If every provider fails, the 503 response includes bounded failures entries with provider name, failure category, HTTP status when applicable, and numeric token usage when available. It never includes raw provider error text or candidate content.
_inputCoverage reports submitted and analyzed lengths separately for each input, its utf16-code-units convention, and a complete flag. Coverage is computed for each request, including cached responses. Changing any included character, even at the end of either document, changes the cache key. In analyze-jd, a supplied résumé is unused: its analyzed length is zero and complete is false when it is nonempty. Coverage describes text supplied to the model, not a guarantee that the model reasoned correctly about every detail.
GET /api/version
Section titled “GET /api/version”The existing version, commit, branch, and env fields remain. The response also includes deployment and scoring metadata with the identity schema, rubric, input limits, counting convention, and six simulation names. This endpoint is deployment/capability discovery; only the identity attached to an actual scoring result establishes that result’s provenance.