Official Node.js / TypeScript client for the Praxicraft Assess Public API.
Use it to invite candidates, check invite quota, manage webhooks, enroll hiring pipelines, and fetch results from your ATS, backend, or automation scripts.
npm install @praxicraft/assessRequires Node.js 18+. Full API reference: https://docs.praxicraft.com
Create an organisation API key in Assess:
Assess → Developer → API Keys → create key → copy ct_live_… (shown once).
export PRAXICRAFT_API_KEY="ct_live_xxxxxxxxxxxxxxxx"Or pass the key when constructing the client:
import { Client } from "@praxicraft/assess";
const client = new Client({ apiKey: "ct_live_xxxxxxxxxxxxxxxx" });Optional: override the API host with PRAXICRAFT_API_BASE_URL or new Client({ baseUrl }).
Default host: https://assess.praxicraft.com.
Never commit API keys. Prefer environment variables or a secrets manager.
Scopes and rotation: Authentication
import { Client } from "@praxicraft/assess";
const client = new Client(); // reads PRAXICRAFT_API_KEY
const page = (await client.assessments.list()) as { results: Array<{ slug: string; status: string }> };
for (const assessment of page.results) {
console.log(assessment.slug, assessment.status);
}
// Invite a candidate (idempotent on email — safe to retry)
const invite = (await client.invites.create("senior-backend-screen", {
email: "candidate@example.com",
name: "Jane Doe",
send_email: true,
})) as { invite_token: string; invite_url?: string };
console.log(invite.invite_token, invite.invite_url);
const result = await client.results.retrieve(invite.invite_token);
console.log(result);Responses are flat JSON (same shape as the Public API — no { data: … } wrapper).
| Resource | Common methods |
|---|---|
client.org |
retrieve(), stats() |
client.assessments |
list(), retrieve(), create(), update(), activate(), listTasks(), attachTasks(), replaceTasks(), removeTask() |
client.invites |
create(), bulkCreate(), list(), retrieve(), remind(), cancel() |
client.results |
list(), retrieve(), iterAll() |
client.webhooks |
list(), create(), retrieve(), update(), delete(), test(), deliveries() |
client.pipelines |
list(), retrieve(), enroll(), bulkEnroll(), listEnrollments(), getEnrollment() |
verifySignature |
Verify X-Praxicraft-Signature on webhook payloads |
All paths target /api/v1/public/… on the Assess host.
const org = (await client.org.retrieve()) as { invites_remaining?: number };
if ((org.invites_remaining ?? 0) < candidates.length) {
throw new Error("Not enough invites remaining this month");
}const hook = (await client.webhooks.create({
url: "https://example.com/hooks/praxicraft",
events: ["assessment.completed", "candidate.passed"],
})) as { id: string; secret_key: string };
// Store hook.secret_key (whsec_…) — shown once
await client.webhooks.test(hook.id);
await client.webhooks.update(hook.id, { is_active: true });import { verifySignature } from "@praxicraft/assess";
function handleWebhook(rawBody: Buffer, signatureHeader: string, secret: string) {
return verifySignature(secret, rawBody, signatureHeader);
}Header format: X-Praxicraft-Signature: sha256=<hex>
Event catalog: Webhooks
for await (const row of client.results.iterAll("senior-backend-screen", { page_size: 50 })) {
console.log(row);
}Branch on error.code (stable), not the message text:
import {
AuthenticationError,
InsufficientScopeError,
RateLimitError,
ValidationError,
} from "@praxicraft/assess";
try {
await client.invites.create("demo", { email: "candidate@example.com" });
} catch (err) {
if (err instanceof ValidationError) {
console.log(err.code, err.details);
} else if (err instanceof InsufficientScopeError) {
console.log(err.code, err.requiredPlan);
} else if (err instanceof AuthenticationError) {
console.log(err.code);
} else if (err instanceof RateLimitError) {
console.log(err.retryAfter);
} else {
throw err;
}
}Error codes: Errors
- Node.js 18+ (native
fetch) - Product docs: docs.praxicraft.com
- Issues: GitHub Issues