Job Talent Match API
An API that matches job requirements against talent profiles - scoring fit with a richness index and surfacing concrete skill matches and gaps.
When to use this API
Use this API when you need a structured, explainable fit score between a job requirement and a candidate profile - without building your own skill-extraction or matching pipeline.
The API compares a job against a talent to detect skill overlap and evaluate fit
based on contextual evidence. Each job × talent pair is mapped to a
richness-index score and fit tier with an explainable skill match / skill gap
breakdown, enabling high-confidence decision-making across recruiting, staffing,
internal mobility, and workforce planning. Both jobs and
talents are arrays, so a single request can submit several of each at
once every job is scored against every talent, and the response's
results array contains one entry per pair evaluated.
How the matching works
| Stage | What happens |
|---|---|
| 1 · Submit | Send one or more jobs and talents - structured fields (title, location, job grade, experience range, skills) plus the source document for each - in a single request. |
| 2 · Contextual Matching | The engine extracts skills from both documents and cross-references them against each other and the structured skill fields, evaluating depth and relevance. |
| 3 · Match Output | Returns a structured JSON response with a richness-index match score, a fit tier, and a concrete skill-match / skill-gap breakdown for every job × talent pair. |
Authentication
All requests require a user-scoped API key passed as a bearer token.
Generate one in the platform under Settings → API Keys.
Keys are prefixed ok_ and expire after 90 days.
Authorization: Bearer ok_xxxxxxxxxxxxxxxxxxxxxxxx
401 unauthorized.
Parameters
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| jobs | array | required | Non-empty array of job objects (see below). Each job is scored against every talent in the same request. |
| talents | array | required | Non-empty array of talent objects (see below). |
Job object
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | required | Caller-assigned identifier, echoed back on each result as jobId. |
| docContent | string (base64) | required* | Base64-encoded job description document. Accepted formats: .pdf, .doc, .docx, .html, .txt. Max 4 MB before encoding. |
| skills | array | required* | Structured skills as { "skill": string, "proficiencyLevel"?: 1-4 }. Either docContent or a non-empty skills list is required a meta-only job (no document, matched purely on structured skills) is valid. |
| title | string | optional | Role title. |
| location | string | optional | Job location. |
| jobGrade | string | optional | Internal grade/band code, e.g. G2. |
| experienceRange | object | optional | { "fromYears": number, "toYears": number }. |
| docFileName | string | optional | Original filename, echoed back for display/audit purposes only. |
Talent object
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | required | Caller-assigned identifier, echoed back on each result as talentId. |
| docContent | string (base64) | required* | Base64-encoded resume / talent profile document. Accepted formats: .pdf, .doc, .docx, .html, .txt. Max 4 MB before encoding. |
| skills | array | required* | Structured skills as { "skill": string, "proficiencyLevel"?: 1-4 }. Either docContent or a non-empty skills list is required. |
| currentTitle | string | optional | Candidate's current title. |
| yearsOfExperience | number | optional | Total years of experience. |
| location | string | optional | Candidate location. |
| docFileName | string | optional | Original filename, echoed back for display/audit purposes only. |
* Exactly one of docContent or a non-empty skills list is required per job/talent entry.
Document constraints
| Constraint | Limit |
|---|---|
| Max document size | 4 MB per document (decoded, not the base64 string length) |
| Max documents per request | 500, combined across jobs and talents |
| Max combined document size | 512 MB, combined across jobs and talents - must not be exceeded |
| Accepted formats | .pdf · .doc · .docx · .html · .txt |
Request
curl -X POST https://platform.openknowra.ai/api/bff/v1/job-talent/match \
-H "Authorization: Bearer $OPENKNOWRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobs": [{
"id": "JOB-001",
"title": "Senior Engineer",
"location": "Remote",
"jobGrade": "G2",
"experienceRange": {"fromYears": 3.0, "toYears": 8.0},
"skills": [
{"skill": "software engineering", "proficiencyLevel": 4},
{"skill": "troubleshooting", "proficiencyLevel": 4}
],
"docContent": "base64 encoded job description content",
"docFileName": "job_description.pdf"
}],
"talents": [{
"id": "TALENT-001",
"currentTitle": "Software Engineer",
"yearsOfExperience": 3,
"location": "Remote",
"skills": [
{"skill": "software development", "proficiencyLevel": 4}
],
"docContent": "base64 encoded resume content",
"docFileName": "resume.pdf"
}]
}'import os
import base64
import requests
API_KEY = os.environ["OPENKNOWRA_API_KEY"]
API_URL = "https://platform.openknowra.ai/api/bff/v1/job-talent/match"
def b64(path: str) -> str:
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
payload = {
"jobs": [{
"id": "JOB-001",
"title": "Senior Engineer",
"location": "Remote",
"jobGrade": "G2",
"experienceRange": {"fromYears": 3.0, "toYears": 8.0},
# proficiencyLevel: 1-Beginner, 2-Intermediate, 3-Advanced, 4-Expert
"skills": [
{"skill": "software engineering", "proficiencyLevel": 4},
{"skill": "troubleshooting", "proficiencyLevel": 4},
],
"docContent": b64("./job_description.pdf"),
"docFileName": "job_description.pdf",
}],
"talents": [{
"id": "TALENT-001",
"currentTitle": "Software Engineer",
"yearsOfExperience": 3,
"location": "Remote",
"skills": [
{"skill": "software development", "proficiencyLevel": 4},
],
"docContent": b64("./resume.pdf"),
"docFileName": "resume.pdf",
}],
}
res = requests.post(API_URL, headers={"Authorization": f"Bearer {API_KEY}"}, json=payload)
if res.status_code == 402:
err = res.json()["error"]
raise ValueError(
f"Insufficient credits: need {err['required']}, have {err['balance']}"
)
res.raise_for_status()
result = res.json()
for rec in result["results"]:
print(f"{rec['jobId']} x {rec['talentId']}: {rec['richnessIndex']} ({rec['algorator']})")import fs from "fs";
const API_KEY = process.env.OPENKNOWRA_API_KEY!;
const API_URL = "https://platform.openknowra.ai/api/bff/v1/job-talent/match";
function b64(filePath: string): string {
return fs.readFileSync(filePath).toString("base64");
}
async function matchCandidate() {
// proficiencyLevel: 1-Beginner, 2-Intermediate, 3-Advanced, 4-Expert
const payload = {
jobs: [{
id: "JOB-001",
title: "Senior Engineer",
location: "Remote",
jobGrade: "G2",
experienceRange: { fromYears: 3.0, toYears: 8.0 },
skills: [
{ skill: "software engineering", proficiencyLevel: 4 },
{ skill: "troubleshooting", proficiencyLevel: 4 },
],
docContent: b64("./job_description.pdf"),
docFileName: "job_description.pdf",
}],
talents: [{
id: "TALENT-001",
currentTitle: "Software Engineer",
yearsOfExperience: 3,
location: "Remote",
skills: [
{ skill: "software development", proficiencyLevel: 4 },
],
docContent: b64("./resume.pdf"),
docFileName: "resume.pdf",
}],
};
const res = await fetch(API_URL, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (res.status === 402) {
const err = await res.json() as { error: { required: number; balance: number } };
throw new Error(
`Insufficient credits: need ${err.error.required}, have ${err.error.balance}`
);
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
for (const rec of data.results) {
console.log(`${rec.jobId} x ${rec.talentId}: ${rec.richnessIndex} (${rec.algorator})`);
}
}
await matchCandidate();Response
200 OK - Returns one scored result per job × talent pair
evaluated, plus a summary covering the whole call. The
Step-Functions-flavored executionId / mapRunArn fields some
upstream responses carry are dropped no polling or job model is involved; the full
result comes back synchronously in this one call.
{
"requestId": "9685d0cf-0b73-409e-b691-17fb6925815a",
"jobId": "req-9685d0cf-0b73-409e-b691-17fb6925815a",
"matchMode": "REAL TIME",
"success": true,
"summary": {
"resultRecords": 100,
"jobContextCompute": 10,
"talentContextCompute": 10,
"contextMatchCompute": 100,
"jobRecords": 10,
"talentRecords": 10,
"jobContextSummary": { "OK": 10 },
"talentContextSummary": { "OK": 10 }
},
"results": [
{
"jobId": "JOB-001",
"talentId": "TALENT-001",
"richnessIndex": 81.4,
"algorator": "Expert",
"skillMatch": ["database management", "api design", "computer architecture", "system design", "troubleshooting", "software engineering", "data structures"],
"skillGap": ["ui frameworks"],
"jobFileName": "job_description.pdf",
"resumeFileName": "resume.pdf",
"jobLocation": "Remote",
"talentLocation": "Remote",
"isExperienceMatch": true,
"isLocationMatch": true,
"isRoleMatching": false,
"jobTitle": "Senior Engineer",
"jobFromExperience": 36,
"jobToExperience": 96,
"talentTotalExperience": 36,
"talentRole": "Software Engineer",
"supplyAdjSkillMatch": [
{ "jobSkill": "troubleshooting", "talentSkill": "debugging" }
]
}
/* ... 99 more entries, one per job × talent pair ... */
],
"creditsUsed": 36,
"newBalance": 55541,
"jobContextRate": 1.5,
"talentContextRate": 2,
"contextMatchRate": 1
}
This example is 10 jobs × 10 talents (100 pairs) - results
is truncated to one entry above for readability; a real call returns
summary.resultRecords entries.
Algorator fit tiers
algorator is a convenience label layered on top of the richness index -
a quick-glance summary of match strength, not a separate score.
| Label | Meaning |
|---|---|
| Expert | Very strong, well-evidenced alignment between job and talent. |
| Advanced | Strong alignment with solid supporting evidence. |
| Proficient | Strong alignment with solid supporting evidence - same tier as Advanced. |
| Intermediate | Moderate alignment - meaningful overlap alongside notable gaps. |
| Beginner | Limited alignment - few demonstrated skills overlap with what the job requires. |
| No Match | No meaningful overlap - richnessIndex is 0 and skillMatch is empty. |
Result fields
| Field | Type | Description |
|---|---|---|
| richnessIndex | number | 0-100 score reflecting how densely the talent's demonstrated skills and experience overlap with what the job requires, based on structured skill fields and contextual evidence extracted from each document. Higher is stronger, better-evidenced alignment. |
| skillMatch | array | Skills the talent demonstrates that the job also requires - including adjacency matches (see below). |
| skillGap | array | Skills the job requires that the talent does not demonstrate. |
| jobTitle / talentRole | string | The job's title and the talent's currentTitle, echoed back as submitted. Empty string if not submitted - not omitted. |
| jobLocation / talentLocation | string | The job's and talent's location, echoed back as submitted. Unlike jobTitle/talentRole, this key is omitted entirely (not an empty string) when the corresponding location wasn't submitted - don't assume it's always present. |
| jobFromExperience / jobToExperience | number | The job's experienceRange in months (not years) - e.g. a submitted {"fromYears": 3, "toYears": 8} comes back as 36 / 96. Omitted when the job didn't submit an experienceRange. |
| talentTotalExperience | number | The talent's yearsOfExperience in months - e.g. a submitted 3 comes back as 36. |
| isRoleMatching / isLocationMatch / isExperienceMatch | boolean | Whether the job/talent pair agree on role, location, and experience respectively - see "Match signals" below. isRoleMatching and isLocationMatch are only present when there's a role/location to compare on both sides; isExperienceMatch is present on effectively every result. |
| supplyAdjSkillMatch | array | Adjacency matches within skillMatch - see "Adjacency skill matches" below. An empty array [] means every matched skill was a literal match, not that the field is unsupported for this pair. |
| jobFileName / resumeFileName | string | Echoed back from docFileName if you sent one; empty string otherwise. |
Treat every field above except jobId/talentId/richnessIndex/algorator/skillMatch/skillGap as
optional - which ones are present on a given result depends on what that specific
job/talent pair actually submitted.
Match signals
isRoleMatching, isLocationMatch, and isExperienceMatch
are independent booleans - each compares one attribute between the job and the
talent and is computed regardless of the others. A pair can score a high
richnessIndex from skill overlap alone while still failing on role,
location, or experience, so check these three explicitly if your workflow requires
all of them to line up (e.g. filtering out remote-only candidates for an on-site
role, or requiring the seniority band to match).
Adjacency skill matches
A skill in skillMatch isn't always a literal string match - the engine
also credits a job skill when the talent demonstrates something judged close
enough in context (e.g. a job requiring "troubleshooting" satisfied by
a talent mentioning "debugging"). supplyAdjSkillMatch lists
exactly which entries in skillMatch were matched this way, and what
talent-side term satisfied them. Each entry is
{ "jobSkill": string, "talentSkill": string }. An entry only appears
here when talentSkill is genuinely different from the job skill; a
skill matched literally never shows up in supplyAdjSkillMatch, even
though it's still in skillMatch.
Other response fields
| Field | Type | Description |
|---|---|---|
| jobId | string | Upstream's own identifier for this call. |
| matchMode | string | Which endpoint served this call, e.g. "REAL TIME". |
| summary.resultRecords / jobRecords / talentRecords | number | Counts for this call - how many results, jobs, and talents were processed. |
| summary.jobContextCompute / talentContextCompute / contextMatchCompute | number | Raw compute units this call actually consumed - what creditsUsed below is billed from, at jobContextRate / talentContextRate / contextMatchRate respectively. |
| summary.jobContextSummary / talentContextSummary | object | Per-status counts for how each job/talent document was processed, e.g. {"OK": 10}. |
| creditsUsed | number | Actual credits deducted for this call, recomputed from the upstream service's own reported compute units - not the pre-check estimate. |
| newBalance | number | Account credit balance after this call's deduction. |
| jobContextRate / talentContextRate / contextMatchRate | number | The per-unit credit rates this call was actually billed at. |
Pricing
Billed on four factors, summed and rounded up once to the nearest whole credit - not a flat per-pair rate:
| Factor | What it is | Rate |
|---|---|---|
| Job Context | One charge per job with a source document (docContent) - reported as summary.jobContextCompute, billed at jobContextRate in the response. |
1.5 credits ($0.15) |
| Talent Context | One charge per talent with a source document - same rule as Job Context, for the talent side. Reported as summary.talentContextCompute, billed at talentContextRate. |
2 credits ($0.20) |
| Job Talent Match (1000) | Job × talent pairs evaluated, billed per 1000-pair unit (rounded up) - not per pair. summary.contextMatchCompute reports the raw pair count; divide by 1000 and round up to get the units actually billed, at contextMatchRate. |
1 credit ($0.10) per 1000 pairs |
| Meta-only entries | A job or talent matched purely on structured skills (no document) doesn't incur Job Context / Talent Context - it costs nothing on that side. |
no charge |
cost = ceil(Job Context units × 1.5 + Talent Context units × 2 + Job Talent Match units × 1)
Worked example: 10 jobs and 10 talents, all with documents, matched as a full
10×10 cross-product (100 pairs). That's 10 Job Context units, 10 Talent
Context units, and ceil(100 / 1000) = 1 Job Talent Match unit:
10 × 1.5 + 10 × 2 + 1 × 1 = 36 credits - matching
creditsUsed: 36 and summary: { jobContextCompute: 10,
talentContextCompute: 10, contextMatchCompute: 100 } in the response above.
Rates and the match unit size (1000 above) are configured per deployment and can
change - jobContextRate, talentContextRate, and
contextMatchRate in every response reflect what that specific call was
actually billed at, so don't hardcode the example numbers above.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | - | jobs/talents missing, empty, or an entry is missing id and either docContent or a non-empty skills list. |
| 401 | unauthorized | Missing, invalid, expired, or revoked API key. |
| 402 | insufficient_credits | Account balance too low. Response includes required (credits needed) and balance (current balance). |
| 502 | upstream_unavailable | The matching service didn't respond or returned a body we couldn't parse. Safe to retry after a short delay. |
| 503 | match_intelligence_not_configured | The matching service is not enabled in this environment. |
| 503 | match_intelligence_pricing_not_configured | Credit rates for this API are not configured in this environment. |
{
"error": {
"code": "insufficient_credits",
"required": 75,
"balance": 10
}
}
Frequently asked questions
What documents can I submit for a job or talent?
A job accepts a job description document; a talent accepts a resume or talent profile document. PDF, DOC, DOCX, HTML, and TXT are supported, with a maximum size of 4 MB per document. A single request can carry up to 500 documents combined across jobs and talents, with a combined total of 512 MB. Documents are base64-encoded and submitted alongside structured fields (title, location, job grade, experience range, skills, etc.) in the same request.
How is the richness index computed?
The richness index is a 0-100 score reflecting how densely the talent's demonstrated skills and experience overlap with what the job requires, based on both the structured skill fields and contextual evidence extracted from each document. Higher scores indicate stronger, better-evidenced alignment.
What do the algorator fit tiers mean?
Each result includes an algorator label (for example Expert, Advanced, Intermediate, or Beginner) that summarizes the overall strength of the match at a glance, in the same spirit as a proficiency tier - it's a convenience label layered on top of the richness index, not a separate score.
Can I match multiple talents against one job (or one talent against multiple jobs) in a single call?
Yes. Both jobs and talents are arrays, so a single request can submit several jobs and several talents at once. The response's results array contains one entry per job × talent pair evaluated.
What does an API invocation cost?
Billed on three factors, not a flat per-pair rate: 1.5 credits per job with a source document (Job Context), 2 credits per talent with a source document (Talent Context), and 1 credit per 1000 job × talent pairs evaluated (Job Talent Match), rounded up and summed. See the Pricing section above for the exact formula and a worked example.
Where can I find my API key?
Generate and manage your API keys from Settings → API Keys. Use the key as a Bearer Token when calling the API. Keep your API key secure and do not share it publicly.
Is my data used to train AI models?
No. Documents and profile data you submit are processed only to return your match result and are not used to train the underlying models.