← Screener Drill
API
Tokens

Drive Screener Drill from your own code

Both metered steps - the tailored kit and the scorecard - are available over HTTP. A natural use is a careers-coaching site or a campus job-readiness program: your code sends a posting, shows the structured questions in your own interview room, collects answers, and prints the scorecard with the call.

The split first: the page counts; the model asks and judges; the page checks the reply. The posting scan, the free question bank, the answer meter (words, speaking time estimated at 140 words a minute, situation/task/action/result cues, numbers, "I" against "we", fillers, hedges) and the rubric arithmetic come from drillkit.js, which runs in node as well as in the browser. Its reconcile function then checks every reply against those counts.

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest call takes {"slug":"screener-drill"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: both tasks are metered and need a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

Every run sends task, which routes the one system prompt to one of two contracts:

taskWhat it returnsVerdict field
planA title and summary; style (behavioral_heavy, situational_heavy, balanced); competencies [{id, name, why, weight 1-3}] with C0 "Motivation and fit"; exactly round.questions questions [{id, competency, type, text, good, probe, window}] with type one of behavioral, situational, motivation; requirements [{text, must}]; watch_outs.none - the kit is the product
scoreA title and summary; one answers entry per question {id, score 1-4, evidence, star{situation, task, action, result}, strength, gap, rewrite}; competencies [{id, score, note}]; gaps_to_prep [{requirement, why, prep}]; next_drill; flags.verdict: advance, borderline, not_yet

The rubric behind the call: advance when the weighted average is 3.0 or more and no answer scored 1; not_yet below 2.0, or with two or more answers at 1, or two or more skipped; borderline otherwise. Weights come from each question's competency. DrillKit.rubric(scores, weights, skipped) computes the same thing locally.

The declared input schema carries scalars only, so object- and array-valued fields travel JSON-encoded as strings (DrillKit.toWire does this). task and role are required.

Input fields

FieldTypeNotes
taskstringplan or score.
rolestringThe job title, e.g. "Junior Data Analyst".
senioritystringentry, mid, senior or lead.
postingstringThe job posting. Over 6,000 characters keeps the head and tail around a marker.
roundstring (JSON)plan: {questions: 3-6, answer_seconds: 60-180}.
prescanstring (JSON)plan: {competencies[], requirements[], years} from scanPosting. score: per answer {id, words, est_seconds, star_cues[], numbers, i, we, fillers} from meter.
kitstring (JSON)score: {competencies [{id, name, weight}], questions [{id, competency, type, text, good}], requirements [{text, must}]} - usually a plan reply passed through kitFromPlan and kitForWire.
answersstring (JSON)score: [{id, answer, seconds_used, seconds_allowed, skipped}], one per question; long answers are cut in the middle with a marker.
notesstringOptional: the candidate's own context (a career change, a gap).
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with DrillKit.parseReply, then normalize, then check it with reconcile(reply, ctx). For a plan (ctx = {questions, posting}) it flags a wrong question count, questions pointing at undefined competencies, competencies with no question, repeated questions, requirements that cannot be traced to the posting, and any question touching a protected topic (age, family, religion, origin, health). For a score (ctx = {kit, answers}) it flags unscored questions, evidence that is not word for word in the answer, a 4 on a very short answer, a skipped answer scored 2 or more, a credited result the meter cannot hear, a rewrite that adds numbers the candidate never said, and a verdict that disagrees with the rubric. Speaking times are estimates; scores are a model's practice judgement, not an employer's.

Worked examples

Replies produced against the app's prompt for the three built-in examples (long lists shortened here; the full replies ship in example-result.js). Between them they cover every call: advance, borderline and not yet. The page replays each of them free from its example buttons.

task: plan - a support team lead, five questions (behavioral_heavy)

Input:

{
 "task": "plan",
 "role": "Customer Support Team Lead",
 "seniority": "lead",
 "posting": "Customer Support Team Lead\n\nWe are a 200-person company that makes scheduling software for clinics and salons. Our support team answers abou ...",
 "notes": "I have been a senior support agent for four years and covered for my manager for six months.",
 "round": "{\"questions\":5,\"answer_seconds\":120}",
 "prescan": "{\"competencies\":[\"Customer focus (5)\",\"Technical depth (5)\",\"Working with data (4)\",\"Organisation and prioritising (4)\",\"Leading people (3)\",\"Communication (2)\"],\"requirements\":[\"3+ years of experience in customer support, including at least 1 year leading or mentoring others\",\"Experience with a hel ..."
}

Reply:

{
 "task": "plan",
 "title": "First-round screen: Customer Support Team Lead",
 "summary": "This screen tests whether you can lead and coach a day-shift team of eight agents while owning first-response time and CSAT on a queue of about 9,000 tickets a month. It also checks how you handle escalations, when you pull in engineering, and whether you can turn ticket patterns into root-cause fixes and weekly queue reporting.",
 "style": "behavioral_heavy",
 "competencies": [
  {
   "id": "C0",
   "name": "Motivation and fit",
   "why": "Why you want to lead this team, and how your senior agent and cover-manager experience leads to this role",
   "weight": 1
  },
  {
   "id": "C1",
   "name": "Leading and coaching people",
   "why": "The posting asks you to lead, coach and schedule a team of 8 agents, with at least 1 year leading or mentoring",
   "weight": 3
  },
  "..."
 ],
 "questions": [
  {
   "id": "Q1",
   "competency": "C0",
   "type": "motivation",
   "text": "Briefly, why do you want to step into this Team Lead role, and what from your support experience so far prepares you for it?",
   "good": "A specific reason tied to leading this team plus one concrete piece of leadership experience, such as covering for a manager, with what changed while they did it.",
   "probe": "What did you actually own during that time, and what did you learn about leading that you would bring here?",
   "window": 90
  },
  {
   "id": "Q2",
   "competency": "C1",
   "type": "behavioral",
   "text": "Tell me about a time you coached an agent whose performance or quality was slipping. What did you do, and what changed?",
   "good": "A named situation, the specific coaching steps the candidate took personally, why they chose that approach, and a measurable change in the agent's numbers or quality.",
   "probe": "How did you measure whether the coaching worked, and how long did it take?",
   "window": 120
  },
  "..."
 ],
 "requirements": [
  {
   "text": "3+ years of experience in customer support, including at least 1 year leading or mentoring others",
   "must": true
  },
  {
   "text": "Calm, clear written communication, especially with frustrated customers",
   "must": true
  },
  "..."
 ],
 "watch_outs": [
  "Describing team results with 'we' and never saying what you personally did as the lead",
  "Talking about CSAT or first-response time without giving any before-and-after figures",
  "..."
 ]
}

task: score - a support team lead, five questions (advance)

Input:

{
 "task": "score",
 "role": "Customer Support Team Lead",
 "seniority": "lead",
 "posting": "Customer Support Team Lead\n\nWe are a 200-person company that makes scheduling software for clinics and salons. Our support team answers abou ...",
 "notes": "I have been a senior support agent for four years and covered for my manager for six months.",
 "kit": "{\"competencies\":[{\"id\":\"C0\",\"name\":\"Motivation and fit\",\"weight\":1},{\"id\":\"C1\",\"name\":\"Leading and coaching people\",\"weight\":3},{\"id\":\"C2\",\"name\":\"Escalations and customer communication\",\"weight\":2},{\"id\":\"C3\",\"name\":\"Queue metrics and prioritising\",\"weight\":3},{\"id\":\"C4\",\"name\":\"Root-cause problem  ...",
 "answers": "[{\"id\":\"Q1\",\"answer\":\"I have been a senior support agent for four years, and for the last six months I covered for my manager while she was on leave. During that cover I ran the daily schedule for seven agents, handled escalations and wrote the weekly queue report, and I found I enjoyed making the team better more than closing tickets myself. Our first-response time went from about 4 hours to under 3 while I was cove",
 "prescan": "[{\"id\":\"Q1\",\"words\":96,\"est_seconds\":41,\"star_cues\":[\"situation\",\"task\",\"action\",\"result\"],\"numbers\":2,\"i\":9,\"we\":1,\"fillers\":0},\"...\"]"
}

Reply:

{
 "task": "score",
 "title": "Scorecard: Customer Support Team Lead screen",
 "summary": "Advance. Every behavioral answer gave a specific situation, your own actions with clear reasons, and before-and-after figures, and your escalation answer set out an explicit test for involving engineering. The opener was the lightest answer and could tie your motivation more closely to coaching a team of eight.",
 "verdict": "advance",
 "answers": [
  {
   "id": "Q1",
   "score": 3,
   "evidence": "During that cover I ran the daily schedule for seven agents, handled escalations and wrote the weekly queue report",
   "star": {
    "situation": true,
    "task": false,
    "action": true,
    "result": true
   },
   "strength": "You tied your motivation to concrete lead duties you have already done and gave a response-time result from your cover period.",
   "gap": "The reason for wanting this team is general; it does not say what draws you to coaching eight agents on this queue in particular.",
   "rewrite": "For six months I covered for my manager: I ran the daily schedule for seven agents, handled escalations and wrote the weekly queue report, and first-response time went from about 4 hours to under 3 while I was covering. What I enjoyed most was [the coaching moment you are proudest of]. That is why I want to own the CSAT and response-time targets for [this team] permanently."
  },
  {
   "id": "Q2",
   "score": 4,
   "evidence": "After five weeks his QA score was back to 90 percent, his reopen rate fell from 12 to 5 percent",
   "star": {
    "situation": true,
    "task": false,
    "action": true,
    "result": true
   },
   "strength": "You diagnosed the real cause before acting, explained why you relaxed handle time, and showed the result on three measures.",
   "gap": "It does not say how you kept the change going once the month without a handle-time target ended.",
   "rewrite": "A newer agent's QA score fell from 88 to 71 percent because he was closing chats before confirming fixes; reviewing ten of his tickets with him showed handle-time pressure was the cause. I lifted handle time for a month, added a confirm-before-closing checklist macro and sat in on two sessions a week with same-day notes. After five weeks QA was back to 90 percent and reopens fell from 12 to 5 percent, and afterwards I [how you kept it there]."
  },
  "..."
 ],
 "competencies": [
  {
   "id": "C0",
   "score": 3,
   "note": "Real cover-manager experience; motivation could be more specific to this team."
  },
  {
   "id": "C1",
   "score": 4,
   "note": "Coaching story with diagnosis, reasoned approach and measured improvement."
  },
  "..."
 ],
 "gaps_to_prep": [],
 "next_drill": [
  "Q1 - tie motivation to coaching this team",
  "Q3 - add the chat channel result"
 ],
 "flags": [
  "Mostly I over we in every answer",
  "All answers finished inside their windows",
  "..."
 ]
}

task: plan - a junior data analyst, four questions (behavioral_heavy)

Input:

{
 "task": "plan",
 "role": "Junior Data Analyst",
 "seniority": "entry",
 "posting": "Junior Data Analyst - Commercial Insights\n\nOur regional grocery chain runs 64 stores. The Commercial Insights team helps buyers and store ma ...",
 "notes": "Recent graduate in economics; one summer internship at a logistics company.",
 "round": "{\"questions\":4,\"answer_seconds\":90}",
 "prescan": "{\"competencies\":[\"Working with data (10)\",\"Communication (2)\",\"Organisation and prioritising (2)\"],\"requirements\":[\"A degree in a quantitative subject, or equivalent experience\",\"Working knowledge of SQL (joins, aggregation) and strong Excel skills\",\"Able to explain numbers clearly to people who are ..."
}

Reply:

{
 "task": "plan",
 "title": "First-round screen: Junior Data Analyst, Commercial Insights",
 "summary": "This screen tests whether you can produce weekly sales and promotion reports in SQL and Excel, catch numbers that look wrong before they reach buyers, and explain results in plain language to people who are not analysts. It is pitched at entry level, so school, internship and project examples all count.",
 "style": "behavioral_heavy",
 "competencies": [
  {
   "id": "C0",
   "name": "Motivation and fit",
   "why": "Why you want an entry-level commercial insights role supporting buyers and store managers.",
   "weight": 1
  },
  {
   "id": "C1",
   "name": "Data quality and checking your own work",
   "why": "The posting asks you to check data quality and investigate numbers that look wrong before they reach the business.",
   "weight": 3
  },
  "..."
 ],
 "questions": [
  {
   "id": "Q1",
   "competency": "C0",
   "type": "motivation",
   "text": "To start, why does a junior analyst role helping a grocery chain's buyers decide what to stock, price and promote appeal to you?",
   "good": "Links something specific in your degree or internship to the commercial decisions this team supports, and says what you want to learn here.",
   "probe": "What part of the day-to-day work in this posting are you most looking forward to?",
   "window": 75
  },
  {
   "id": "Q2",
   "competency": "C1",
   "type": "behavioral",
   "text": "Tell me about a time you noticed a number in a dataset or report that looked wrong. How did you investigate it, and what happened?",
   "good": "Names the specific number, the checks you personally ran to find the cause, the fix, and how you stopped it recurring, with a concrete result.",
   "probe": "How did you confirm the corrected figure was right before anyone used it?",
   "window": 90
  },
  "..."
 ],
 "requirements": [
  {
   "text": "A degree in a quantitative subject, or equivalent experience",
   "must": true
  },
  {
   "text": "Working knowledge of SQL (joins, aggregation) and strong Excel skills",
   "must": true
  },
  "..."
 ],
 "watch_outs": [
  "Saying you are 'detail-oriented' without a story of an error you actually caught.",
  "Listing SQL or Excel as skills without describing a query or workbook you built yourself.",
  "..."
 ]
}

task: score - a junior data analyst, four questions (borderline)

Input:

{
 "task": "score",
 "role": "Junior Data Analyst",
 "seniority": "entry",
 "posting": "Junior Data Analyst - Commercial Insights\n\nOur regional grocery chain runs 64 stores. The Commercial Insights team helps buyers and store ma ...",
 "notes": "Recent graduate in economics; one summer internship at a logistics company.",
 "kit": "{\"competencies\":[{\"id\":\"C0\",\"name\":\"Motivation and fit\",\"weight\":1},{\"id\":\"C1\",\"name\":\"Data quality and checking your own work\",\"weight\":3},{\"id\":\"C2\",\"name\":\"SQL and Excel reporting\",\"weight\":2},{\"id\":\"C3\",\"name\":\"Explaining numbers to non-analysts\",\"weight\":2}],\"questions\":[{\"id\":\"Q1\",\"competency\" ...",
 "answers": "[{\"id\":\"Q1\",\"answer\":\"I studied economics and the part I enjoyed most was applied work, like my dissertation on how price changes affect demand for everyday goods. Grocery is basically that question every week. On my internship I saw how a small reporting change helped planners make faster calls, and I want to be closer to those decisions. I also like that the role has training, because I want to get properly good at",
 "prescan": "[{\"id\":\"Q1\",\"words\":72,\"est_seconds\":31,\"star_cues\":[\"situation\",\"action\"],\"numbers\":0,\"i\":8,\"we\":0,\"fillers\":1},\"...\"]"
}

Reply:

{
 "task": "score",
 "title": "Scorecard: Junior Data Analyst screen",
 "summary": "Borderline. Your data-quality story is the strongest answer in the round, with a specific error, your own fix and a measured result, but the SQL and Excel answer lists tools without a single report you built, and the promotion question stays general about how you would explain the result to a buyer.",
 "verdict": "borderline",
 "answers": [
  {
   "id": "Q1",
   "score": 3,
   "evidence": "my dissertation on how price changes affect demand for everyday goods. Grocery is basically that question every week",
   "star": {
    "situation": true,
    "task": false,
    "action": true,
    "result": false
   },
   "strength": "You tie your dissertation topic directly to the pricing and promotion decisions this team supports.",
   "gap": "The internship point is vague - say what the reporting change was and what you did on it.",
   "rewrite": "My economics dissertation looked at how price changes affect demand for everyday goods, which is the question your buyers face every week. On my internship I [what you did on the reporting change], which helped planners [what it let them do faster]. I want to build on that here and get properly good at SQL with the training you offer."
  },
  {
   "id": "Q2",
   "score": 4,
   "evidence": "I filtered the raw export and found around 400 rows were duplicated because two depots had been merged in the system",
   "star": {
    "situation": true,
    "task": true,
    "action": true,
    "result": true
   },
   "strength": "You named the suspicious figure, explained why it looked wrong, traced the cause yourself, corrected it to 89% and added a check so it would not recur.",
   "gap": "Say briefly how you confirmed 89% was right, for example by checking it against another source.",
   "rewrite": "On my internship, on-time delivery in the weekly Excel report jumped from about 88% to 97%, which looked too good. I filtered the raw export, found around 400 rows duplicated after two depots were merged, removed them with a lookup on the delivery ID and confirmed the true figure of 89% by [how you verified it]. I told my manager before the report went out and added a duplicate-ID check column, and the problem did not come back for the rest of the internship."
  },
  "..."
 ],
 "competencies": [
  {
   "id": "C0",
   "name": "Motivation and fit",
   "score": 3,
   "note": "Clear link from your dissertation to pricing and promotion work."
  },
  {
   "id": "C1",
   "name": "Data quality and checking your own work",
   "score": 4,
   "note": "A specific error found, fixed, measured and prevented."
  },
  "..."
 ],
 "gaps_to_prep": [
  {
   "requirement": "Working knowledge of SQL (joins, aggregation) and strong Excel skills",
   "why": "Q3 mentions a university SQL module and Excel features but no query or workbook you wrote and used for a real question.",
   "prep": "Think of a time you wrote a query or built a workbook yourself - in your dissertation, the module or the internship - and be ready to say what tables you joined, what you aggregated and who used the output."
  },
  {
   "requirement": "Able to explain numbers clearly to people who are not analysts",
   "why": "Only the hypothetical Q4 touched this, and it described keeping things simple rather than showing you doing it.",
   "prep": "Think of a time you explained a number or finding to someone without an analytics background - a manager, a planner, a classmate - and what you said, how they reacted and what they did next."
  }
 ],
 "next_drill": [
  "Q3 - describe one report you built",
  "Q4 - say the buyer answer aloud",
  "..."
 ],
 "flags": [
  "Q4 ran about 19 s over its 90 s window",
  "Q3 mixes 'we' with 'I', so your own part is unclear"
 ]
}

task: plan - a marketing coordinator with a skipped answer (behavioral_heavy)

Input:

{
 "task": "plan",
 "role": "Marketing Coordinator",
 "seniority": "entry",
 "posting": "Marketing Coordinator\n\nA community arts centre with a theatre, two galleries and a busy class programme is looking for a Marketing Coordinat ...",
 "round": "{\"questions\":4,\"answer_seconds\":120}",
 "prescan": "{\"competencies\":[\"Organisation and prioritising (5)\",\"Communication (2)\",\"Working with data (2)\"],\"requirements\":[\"Experience running social media or email for an organisation, club or society\",\"Organised, able to juggle several deadlines at once\",\"Bonus: photography or design skills\"],\"years\":null}"
}

Reply:

{
 "task": "plan",
 "title": "First-round screen: Marketing Coordinator",
 "summary": "This screen tests whether you can run social posts and a weekly newsletter for a busy arts centre, gather information from artists, teachers and the box office on deadline, and read simple results like open rates and ticket sales. It is pitched at entry level, so examples from a club, society or volunteer role count.",
 "style": "behavioral_heavy",
 "competencies": [
  {
   "id": "C0",
   "name": "Motivation and fit",
   "why": "Why you want to market a community arts centre with a theatre, galleries and classes",
   "weight": 1
  },
  {
   "id": "C1",
   "name": "Running social media and email",
   "why": "The core of the job is planning and scheduling social posts and a weekly email newsletter",
   "weight": 3
  },
  "..."
 ],
 "questions": [
  {
   "id": "Q1",
   "competency": "C0",
   "type": "motivation",
   "text": "What draws you to marketing for a community arts centre like ours?",
   "good": "A specific reason tied to the centre's mix of theatre, galleries and classes, plus one thing you have already done that connects to it.",
   "probe": "Which part of our programme would you most want to promote first, and why?",
   "window": 75
  },
  {
   "id": "Q2",
   "competency": "C1",
   "type": "behavioral",
   "text": "Tell me about a time you planned and ran social media posts or an email for an organisation, club or society. What did you do, and how did it go?",
   "good": "A named campaign or period, the posts or emails you personally planned and scheduled, why you made those choices, and a result you could see such as more sign-ups or replies.",
   "probe": "What exactly did you write or schedule yourself, and how did you know whether it worked?",
   "window": 120
  },
  "..."
 ],
 "requirements": [
  {
   "text": "Experience running social media or email for an organisation, club or society",
   "must": true
  },
  {
   "text": "Excellent writing and an eye for detail",
   "must": true
  },
  "..."
 ],
 "watch_outs": [
  "Saying \"we ran the socials\" without making clear which posts you planned or wrote yourself",
  "Calling yourself organised without describing how you actually track deadlines",
  "..."
 ]
}

task: score - a marketing coordinator with a skipped answer (not_yet)

Input:

{
 "task": "score",
 "role": "Marketing Coordinator",
 "seniority": "entry",
 "posting": "Marketing Coordinator\n\nA community arts centre with a theatre, two galleries and a busy class programme is looking for a Marketing Coordinat ...",
 "kit": "{\"competencies\":[{\"id\":\"C0\",\"name\":\"Motivation and fit\",\"weight\":1},{\"id\":\"C1\",\"name\":\"Running social media and email\",\"weight\":3},{\"id\":\"C2\",\"name\":\"Organisation and juggling deadlines\",\"weight\":2},{\"id\":\"C3\",\"name\":\"Working with numbers and reporting\",\"weight\":2}],\"questions\":[{\"id\":\"Q1\",\"competen ...",
 "answers": "[{\"id\":\"Q1\",\"answer\":\"Um, I've always really loved the arts and I think it would be, like, a really nice place to work. I ran our society's social accounts at uni so I think I'd be a good fit and I'm really keen to learn.\",\"seconds_used\":62,\"seconds_allowed\":75,\"skipped\":false},\"...\"]",
 "prescan": "[{\"id\":\"Q1\",\"words\":42,\"est_seconds\":18,\"star_cues\":[\"action\"],\"numbers\":0,\"i\":6,\"we\":1,\"fillers\":1},\"...\"]"
}

Reply:

{
 "task": "score",
 "title": "Scorecard: Marketing Coordinator screen",
 "summary": "Not yet: your weighted average is 1.75 out of 4, mainly because the deadlines question ran out of time with no answer and the social media answer, the core of this job, stayed general. You clearly have relevant society experience; the next step is turning it into one specific story with what you did and what changed.",
 "verdict": "not_yet",
 "answers": [
  {
   "id": "Q1",
   "score": 2,
   "evidence": "I ran our society's social accounts at uni so I think I'd be a good fit",
   "star": {
    "situation": false,
    "task": false,
    "action": true,
    "result": false
   },
   "strength": "You linked yourself to the job with real experience running a society's social accounts.",
   "gap": "Your reason for wanting this centre is general (loving the arts) and never mentions its theatre, galleries or classes.",
   "rewrite": "I want to work here because [which part of the centre - the theatre, the galleries or the classes - and why it matters to you]. At uni I ran our society's social accounts, where I [one thing you planned or wrote], and I would like to do that for [the kind of event you would promote first]."
  },
  {
   "id": "Q2",
   "score": 2,
   "evidence": "We posted about events and stuff and we tried to keep it kind of regular.",
   "star": {
    "situation": true,
    "task": false,
    "action": false,
    "result": false
   },
   "strength": "You picked a relevant example: running social accounts for your society.",
   "gap": "It never says what you personally planned, wrote or scheduled, and the result is only a guess (\"I think\").",
   "rewrite": "At uni I ran the social accounts for our society, and for [one event or term] I planned [how many posts] on a [weekly or other] schedule, writing [what kind of posts] myself because [why you chose that]. Afterwards [how many people came or signed up, compared with before], which showed me [what worked]."
  },
  "..."
 ],
 "competencies": [
  {
   "id": "C0",
   "score": 2,
   "note": "Real society experience, but no reason specific to this centre."
  },
  {
   "id": "C1",
   "score": 2,
   "note": "Relevant example, but your own actions and the result are missing."
  },
  "..."
 ],
 "gaps_to_prep": [
  {
   "requirement": "Organised, able to juggle several deadlines at once",
   "why": "Q3 was left empty and \"I'm really organised\" in Q4 is a claim, not an example.",
   "prep": "Think of a time at uni or in the society when two or three deadlines landed in the same week and you depended on other people for information. Note how you tracked them, who you chased and what got done on time."
  },
  {
   "requirement": "Excellent writing and an eye for detail",
   "why": "No answer described anything you wrote or checked yourself.",
   "prep": "Think of a post, event description or email you wrote for the society. Be ready to say who it was for, why you worded it that way and any mistake you caught before it went out."
  },
  "..."
 ],
 "next_drill": [
  "Q3 - give any deadline story at all",
  "Q2 - say what you posted, add result",
  "..."
 ],
 "flags": [
  "Q3 used the full 120 s window with no answer",
  "Q2 mostly we, little I",
  "..."
 ]
}

Build the input yourself

Load the script in node (global.window = {}; require("./drillkit.js")), then:

const K = DrillKit;
const state = K.normState({ role: "Junior Data Analyst", seniority: "entry", questions: 4, window: 90, posting: "...the posting..." });
const scan = K.scanPosting(state.posting);                          // competencies, requirements, years, checks
const planWire = K.toWire(K.buildInput("plan", { state, scan }).input);   // POST to /estimate and /run
// later: const plan = K.normalize(K.parseReply(job.output.output)); K.reconcile(plan, { questions: 4, posting: state.posting })
const kit = K.kitFromPlan(plan);                                    // or K.localKit(scan, 4, "entry") for the free bank
const answers = [{ id: "Q1", answer: "I studied economics...", seconds_used: 40 }];
const scoreWire = K.toWire(K.buildInput("score", { state, scan, kit, answers }).input);
// const sc = K.normalize(K.parseReply(job2.output.output)); K.reconcile(sc, { kit, answers }) -> [{level, text}]
// K.meter(answer, seconds, type) -> words, est_seconds, star cues, numbers, i/we, fillers, hedges

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://screener-drill.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"screener-drill"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "plan", "role": "Junior Data Analyst", "seniority": "entry", "posting": "...", "round": "{\"questions\":4,\"answer_seconds\":90}"}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses screener-drill:<task>:<inputHash>:a<attempt>, and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="screener-drill:plan:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: screener-drill:plan:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json