Work and Workforce Engineering · API

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.

Documentation Pricing
POST /v1/job-talent/match
Auth Bearer token Content-Type application/json Status Stable · v1 Max doc size 4 MB per document Max documents 500 combined (job + talent) Max combined size 512 MB

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

StageWhat 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 header
Authorization: Bearer ok_xxxxxxxxxxxxxxxxxxxxxxxx
Heads-up. API keys are tied to the user account that generated them. Credit deductions are applied to that user's balance. A key that is revoked or expired returns 401 unauthorized.

Parameters

Request body

NameTypeRequiredDescription
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

NameTypeRequiredDescription
idstringrequiredCaller-assigned identifier, echoed back on each result as jobId.
docContentstring (base64)required*Base64-encoded job description document. Accepted formats: .pdf, .doc, .docx, .html, .txt. Max 4 MB before encoding.
skillsarrayrequired*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.
titlestringoptionalRole title.
locationstringoptionalJob location.
jobGradestringoptionalInternal grade/band code, e.g. G2.
experienceRangeobjectoptional{ "fromYears": number, "toYears": number }.
docFileNamestringoptionalOriginal filename, echoed back for display/audit purposes only.

Talent object

NameTypeRequiredDescription
idstringrequiredCaller-assigned identifier, echoed back on each result as talentId.
docContentstring (base64)required*Base64-encoded resume / talent profile document. Accepted formats: .pdf, .doc, .docx, .html, .txt. Max 4 MB before encoding.
skillsarrayrequired*Structured skills as { "skill": string, "proficiencyLevel"?: 1-4 }. Either docContent or a non-empty skills list is required.
currentTitlestringoptionalCandidate's current title.
yearsOfExperiencenumberoptionalTotal years of experience.
locationstringoptionalCandidate location.
docFileNamestringoptionalOriginal 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

ConstraintLimit
Max document size4 MB per document (decoded, not the base64 string length)
Max documents per request500, combined across jobs and talents
Max combined document size512 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.

application/json
{
  "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.

LabelMeaning
ExpertVery strong, well-evidenced alignment between job and talent.
AdvancedStrong alignment with solid supporting evidence.
ProficientStrong alignment with solid supporting evidence - same tier as Advanced.
IntermediateModerate alignment - meaningful overlap alongside notable gaps.
BeginnerLimited alignment - few demonstrated skills overlap with what the job requires.
No MatchNo meaningful overlap - richnessIndex is 0 and skillMatch is empty.

Result fields

FieldTypeDescription
richnessIndexnumber0-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.
skillMatcharraySkills the talent demonstrates that the job also requires - including adjacency matches (see below).
skillGaparraySkills the job requires that the talent does not demonstrate.
jobTitle / talentRolestringThe job's title and the talent's currentTitle, echoed back as submitted. Empty string if not submitted - not omitted.
jobLocation / talentLocationstringThe 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 / jobToExperiencenumberThe 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.
talentTotalExperiencenumberThe talent's yearsOfExperience in months - e.g. a submitted 3 comes back as 36.
isRoleMatching / isLocationMatch / isExperienceMatchbooleanWhether 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.
supplyAdjSkillMatcharrayAdjacency 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 / resumeFileNamestringEchoed 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

FieldTypeDescription
jobIdstringUpstream's own identifier for this call.
matchModestringWhich endpoint served this call, e.g. "REAL TIME".
summary.resultRecords / jobRecords / talentRecordsnumberCounts for this call - how many results, jobs, and talents were processed.
summary.jobContextCompute / talentContextCompute / contextMatchComputenumberRaw compute units this call actually consumed - what creditsUsed below is billed from, at jobContextRate / talentContextRate / contextMatchRate respectively.
summary.jobContextSummary / talentContextSummaryobjectPer-status counts for how each job/talent document was processed, e.g. {"OK": 10}.
creditsUsednumberActual credits deducted for this call, recomputed from the upstream service's own reported compute units - not the pre-check estimate.
newBalancenumberAccount credit balance after this call's deduction.
jobContextRate / talentContextRate / contextMatchRatenumberThe 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:

FactorWhat it isRate
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

StatusCodeMeaning
400-jobs/talents missing, empty, or an entry is missing id and either docContent or a non-empty skills list.
401unauthorizedMissing, invalid, expired, or revoked API key.
402insufficient_creditsAccount balance too low. Response includes required (credits needed) and balance (current balance).
502upstream_unavailableThe matching service didn't respond or returned a body we couldn't parse. Safe to retry after a short delay.
503match_intelligence_not_configuredThe matching service is not enabled in this environment.
503match_intelligence_pricing_not_configuredCredit rates for this API are not configured in this environment.
402 - insufficient_credits
{
  "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.