Why Resume Scoring Should Be an API Call, Not a Custom Build

Every ATS team eventually faces the same crossroads: applicant volume is growing, manual screening can't keep up, and someone proposes building an in-house scoring system.

Don't. Building resume scoring from scratch means maintaining prompt engineering, handling edge cases across hundreds of resume formats, calibrating scores so they're consistent across job types, and paying for GPU infrastructure or LLM API management. That's a product, not a feature.

A scoring API externalizes all of that. Your integration is a single HTTP call — send a resume and a job description, get back a structured score with explanations. You keep your existing workflow, your existing ATS, and your existing tooling. The scoring intelligence lives behind a REST endpoint that you call like any other service.

Stackwright's API scores resumes in under 3 seconds using contextual AI — not keyword matching. It evaluates actual job fit: does this person's experience demonstrate the skills you need, or did they just learn to put "TypeScript" on their resume? The response is structured JSON with numeric scores, strength/weakness analysis, and skill-by-skill matching. Deterministic, auditable, and fast.

What You'll Build

By the end of this guide: a working integration that scores resumes via API, parses the structured response, and optionally handles batch processing via webhooks. Total time: about 5 minutes of actual coding.

Prerequisites

You need three things:

  1. An API key. Use the free demo key below to follow along, or grab your own from the docs page. The demo key gives you 100 calls/day — more than enough to evaluate the API.
  2. An HTTP client. cURL (comes with macOS/Linux), Node.js 18+, or Python 3.8+ with requests.
  3. A resume and job description. Plain text. We'll use example data in the snippets, but swap in real content for meaningful scores.
Free Demo Key

Use sk-sw-demo-stackwright2025 for all examples in this guide. 100 calls/day, no signup, no credit card. Rate limits apply: 10 requests/minute for demo keys.

1
Score Your First Resume

One endpoint. One POST request. Send a resume and a job description, get a structured score back. Here's the call in all three languages.

cURL

bash
curl -X POST https://stackwright.polsia.app/api/v1/score-resume \ -H "Content-Type: application/json" \ -H "X-API-Key: sk-sw-demo-stackwright2025" \ -d '{ "resume_text": "Sarah Chen. Senior Software Engineer, 6 years experience. Built distributed caching layer handling 50k req/s at Datadog. Led migration from monolith to microservices (Node.js, Go, PostgreSQL). Open-source maintainer of redis-cluster-kit (2.1k stars). AWS certified, strong in system design and observability.", "job_description": "Senior Backend Engineer. We need someone to design and scale our API infrastructure (Node.js, PostgreSQL). Must have experience with distributed systems, caching, and high-throughput services. 5+ years required." }'

JavaScript (Node.js)

javascript
const response = await fetch('https://stackwright.polsia.app/api/v1/score-resume', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'sk-sw-demo-stackwright2025' }, body: JSON.stringify({ resume_text: 'Sarah Chen. Senior Software Engineer, 6 years...', job_description: 'Senior Backend Engineer. We need someone to...' }) }); const score = await response.json(); console.log('Fit score:', score.fit_score); console.log('Strengths:', score.strengths); console.log('Gaps:', score.gaps);

Python

python
import requests response = requests.post( 'https://stackwright.polsia.app/api/v1/score-resume', headers={ 'Content-Type': 'application/json', 'X-API-Key': 'sk-sw-demo-stackwright2025' }, json={ 'resume_text': 'Sarah Chen. Senior Software Engineer, 6 years...', 'job_description': 'Senior Backend Engineer. We need someone to...' } ) score = response.json() print(f"Fit score: {score['fit_score']}") print(f"Strengths: {score['strengths']}") print(f"Gaps: {score['gaps']}")

That's it. One HTTP call. The API handles prompt engineering, caching (identical inputs return cached results), and response normalization. You get back structured JSON in under 3 seconds.

2
Parse the Response

The API returns a JSON object with everything you need to make a hiring decision. Here's what a real response looks like:

{ "fit_score": 82, "strengths": [ "6 years of backend experience directly matches 5+ year requirement", "Proven distributed systems work: 50k req/s caching layer at Datadog", "Node.js and PostgreSQL are primary stack — exact match", "Open-source maintenance shows initiative and code quality standards" ], "gaps": [ "No explicit API design or REST architecture experience mentioned", "Observability focus may not translate to API infrastructure design" ], "skill_matches": [ { "skill": "Node.js", "match": "strong" }, { "skill": "PostgreSQL", "match": "strong" }, { "skill": "Distributed systems", "match": "strong" }, { "skill": "Caching", "match": "strong" }, { "skill": "API infrastructure", "match": "partial" } ], "experience_alignment": "Senior-level experience with direct relevance to role requirements. Microservices migration and high-throughput caching demonstrate the architectural thinking needed for API infrastructure.", "overall_assessment": "Strong candidate. Core technical skills align well. The gap in explicit API design experience is minor given demonstrated system design capability.", "processing_time_ms": 2340 }

What Each Field Means

Field Type Description
fit_score integer 0–100 composite score. How well this candidate matches this specific job. Not a generic "quality" number — a 95 for one role can be a 40 for another.
strengths string[] Evidence-based reasons the candidate fits. Each entry references specific resume content against specific job requirements.
gaps string[] Missing or weak alignment areas. These are gaps, not disqualifiers — use them to guide interview questions.
skill_matches object[] Skill-by-skill breakdown. Each entry has a skill name and match level: "strong", "partial", or "none".
experience_alignment string Narrative assessment of how the candidate's experience level and trajectory align with the role's seniority expectations.
overall_assessment string One-paragraph summary a recruiter can skim in 10 seconds. Combines score, strengths, and gaps into a recommendation.
processing_time_ms integer End-to-end processing time in milliseconds. Typically 1,500–3,000ms. Cached responses return in <100ms.
Score Interpretation

80+ = strong match, likely worth interviewing. 60–79 = partial match, review gaps before deciding. Below 60 = significant misalignment with this specific role. Scores are contextual — the same resume scores differently against different job descriptions.

Using scores in your pipeline: Most teams set a threshold (e.g., 70+) to auto-advance candidates to the next stage, review 50–69 manually, and auto-reject below 50. The gaps array feeds directly into interview prep — ask about the areas where the API found weak signal.

3
Add Webhook Callbacks

The synchronous endpoint works for real-time scoring (candidate applies, score appears in your ATS within 3 seconds). But if you're batch-processing — importing a backlog of 500 resumes, or scoring against a new job description — you'll want async processing.

Stackwright's webhook integration flips the model: instead of your app polling for results, Stackwright pushes scores to your endpoint as they complete. This is how ATS integrations work in production.

Setting Up a Webhook Receiver

javascript — express webhook handler
import express from 'express'; const app = express(); app.use(express.json()); // Stackwright sends POST to this endpoint when scoring completes app.post('/webhooks/stackwright/score', (req, res) => { const { candidate_id, fit_score, strengths, gaps, skill_matches } = req.body; // Update your database / ATS console.log(`Candidate ${candidate_id} scored ${fit_score}`); // Auto-advance high scorers if (fit_score >= 70) { // Move to interview stage in your ATS advanceCandidate(candidate_id, 'interview'); } res.status(200).json({ received: true }); }); app.listen(3000);

Python (Flask)

python — flask webhook handler
from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/webhooks/stackwright/score', methods=['POST']) def handle_score(): data = request.get_json() score = data['fit_score'] candidate = data['candidate_id'] print(f"Candidate {candidate} scored {score}") # Update your ATS, send notifications, etc. if score >= 70: advance_candidate(candidate, 'interview') return jsonify({'received': True}), 200

For a full walkthrough of webhook architecture — including retry logic, signature verification, and multi-ATS patterns (Ashby, Greenhouse, Lever) — see How to Automate Resume Screening with ATS Webhooks.

4
Go to Production

The demo key is for evaluation. When you're ready to run scoring in production, here's the checklist.

Upgrade to Pro

Pro tier removes rate limits and unlocks production features:

  • Unlimited API calls — no daily caps, no per-minute throttling
  • Priority processing — dedicated capacity, consistent sub-2s response times
  • Webhook support — push-based scoring for batch and ATS integration
  • Usage dashboard — per-key analytics, cost tracking, error monitoring

Production Best Practices

  1. Store your API key securely. Use environment variables, not hardcoded strings. Never commit keys to version control.
  2. Handle rate limit responses. The API returns 429 Too Many Requests with a Retry-After header. Implement exponential backoff:
javascript — retry logic
async function scoreWithRetry(payload, retries = 3) { for (let i = 0; i < retries; i++) { const res = await fetch('https://stackwright.polsia.app/api/v1/score-resume', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.STACKWRIGHT_API_KEY }, body: JSON.stringify(payload) }); if (res.status === 429) { const wait = parseInt(res.headers.get('Retry-After') || '2') * 1000; await new Promise(r => setTimeout(r, wait * (i + 1))); continue; } return res.json(); } throw new Error('Max retries exceeded'); }
  1. Validate inputs. Send plain-text resumes (not HTML or PDF binary). If you're extracting from PDFs, strip formatting first — the scoring model works on content, not layout.
  2. Use caching. The API caches responses by content hash. Identical resume + job description pairs return instantly. Don't re-score unchanged content on your side either.
  3. Monitor error codes. 400 = bad payload (check resume_text and job_description are present). 401 = invalid API key. 503 = temporary outage (retry). The API returns structured error messages in every failure response.

Rate Limits by Tier

Tier Daily Limit Per-Minute Webhooks
Demo 100 calls/day 10/min No
Free 10 calls/day 5/min No
Pro Unlimited Unlimited Yes

Start scoring resumes now

Use the demo key to test the API immediately. Upgrade to Pro when you're ready for production.

X-API-Key: sk-sw-demo-stackwright2025

What You Can Build From Here

The scoring endpoint is your foundation. Once it's integrated, the natural next steps are:

  • Automated interview questions — Use POST /api/v1/generate-questions to create role-specific technical questions based on the score's gaps array. Interviewers get targeted questions instead of generic ones.
  • ATS webhook automation — Connect Ashby, Greenhouse, or Lever webhooks so every applicant is scored the moment they apply. See our webhook integration guide for full setup.
  • Candidate ranking — Score all applicants against the same job description, sort by fit_score, and present recruiters with a pre-ranked shortlist.
  • Bias auditing — Use the structured skill_matches data to verify scoring consistency across resume styles and backgrounds. See how to build fair scoring for the methodology.

Each extension builds on the same API. No new infrastructure, no new dependencies, no ML pipeline to maintain.

Ready to integrate?

Start scoring resumes in minutes. Free tier ships immediately — no credit card. Pro starts at $49/mo for production scale.

Try the API Free → See Pricing →