The @nova-sdk/api package gives you typed methods for every Nova Embed API endpoint, automatic retries with exponential backoff, and webhook signature verification. It works in Node.js 18+ and any runtime that supports the Fetch API.
Installation
npm install @nova-sdk/api
Quick start
import { Nova } from '@nova-sdk/api';
const nova = new Nova({
apiKey: 'sk_test_your_key',
tenantId: 'acme-corp',
});
// Generate criteria for a job
const criteria = await nova.jobs.criteria.generate.create({
jobId: 'job-123',
body: {
jobContext: {
jobTitle: 'Senior Software Engineer',
companyName: 'Acme Corp',
jobDescription: 'We are looking for a Senior Software Engineer with 5+ years experience...',
},
},
});
// Submit a scoring job
const score = await nova.jobs.applications.scoringJobs.submit({
jobId: 'job-123',
applicationId: 'app-456',
body: {
resume: { type: 'url', url: 'https://storage.example.com/resumes/abc123.pdf' },
jobDescription: 'We are looking for a Senior Software Engineer with 5+ years experience...',
},
});
Configuration
Both apiKey and tenantId are required. Everything else is optional.
const nova = new Nova({
// Required
apiKey: 'sk_live_your_key',
tenantId: 'acme-corp',
// Optional
baseUrl: 'https://embed.nova.dweet.com', // default
retry: {
maxRetries: 2, // default: 2 (set to 0 to disable)
initialDelayMs: 500, // default: 500
maxDelayMs: 30_000, // default: 30,000
backoffMultiplier: 2, // default: 2
retryableStatusCodes: [429, 500, 502, 503, 504], // default
},
fetch: customFetch, // custom fetch implementation
});
Use sk_test_* keys for sandbox and sk_live_* keys for production. The environment is derived from the key prefix.
Per-request tenant override
Every method accepts an optional RequestOptions object as its last argument. You can override the tenant ID for a single call without creating a new client.
const library = await nova.criteriaLibrary.list(
{ category: 'engineering' },
{ tenantId: 'other-tenant' },
);
Criteria library
The criteria library stores reusable criteria across jobs for a tenant.
// List all criteria (optionally filter by category)
const library = await nova.criteriaLibrary.list({ category: 'engineering' });
// Create a criterion
const criterion = await nova.criteriaLibrary.create({
text: '3+ years of production TypeScript experience',
importance: 'PREFERRED',
category: 'engineering',
});
// Get a single criterion
const item = await nova.criteriaLibrary.get('crit_abc123');
// Update a criterion
await nova.criteriaLibrary.update({
criterionId: 'crit_abc123',
body: { importance: 'MUST_HAVE' },
});
// Delete a criterion
await nova.criteriaLibrary.delete('crit_abc123');
Job criteria
Criteria attached to a specific job control how applications get scored.
Generate criteria with AI
Generate clarification questions (optional)
Ask Nova for clarification questions that help calibrate criteria generation.const qs = await nova.jobs.questionSets.create({
jobId: 'job-123',
body: {
jobContext: {
jobTitle: 'Senior Software Engineer',
companyName: 'Acme Corp',
jobDescription: 'We are looking for...',
},
},
});
// Show qs.questionSet.questions to the user, collect answers
Generate criteria from job context and answers
Pass the question set ID and answers to refine the generated criteria. The call is synchronous (10-20 seconds) and returns the criteria directly.const { criteria } = await nova.jobs.criteria.generate.create({
jobId: 'job-123',
body: {
jobContext: {
jobTitle: 'Senior Software Engineer',
companyName: 'Acme Corp',
jobDescription: 'We are looking for...',
},
questionSetId: qs.questionSet.id,
answers: [
{ id: 'q1', value: 'Senior' },
],
},
});
console.log(criteria); // Array of generated Criterion objects
Manage criteria manually
// List criteria for a job
const criteria = await nova.jobs.criteria.list('job-123');
// Replace all criteria at once
await nova.jobs.criteria.set('job-123', {
criteria: [
{
text: '3+ years of Python experience',
importance: 'MUST_HAVE',
},
],
});
// Add a single criterion
await nova.jobs.criteria.items.add({
jobId: 'job-123',
body: {
text: 'Docker and containerization experience',
importance: 'PREFERRED',
},
});
// Update a criterion
await nova.jobs.criteria.items.update({
jobId: 'job-123',
criterionId: 'crit_abc123',
body: { importance: 'MUST_HAVE' },
});
// Remove a criterion
await nova.jobs.criteria.items.remove({
jobId: 'job-123',
criterionId: 'crit_abc123',
});
// Archive all criteria for a job
await nova.jobs.criteria.archive('job-123');
Criteria versions
Every change to a job’s criteria creates a new version. You can list them or fetch a specific one.
// Get the current active version
const current = await nova.jobs.criteria.versions.getCurrent('job-123');
// List all versions
const versions = await nova.jobs.criteria.versions.list('job-123');
// Get a specific version
const v = await nova.jobs.criteria.versions.get({
jobId: 'job-123',
criteriaVersionId: 'cv_abc123',
});
Batch criteria lookup
Fetch criteria for multiple jobs in a single call.
const batch = await nova.jobs.criteria.getBatch(['job-123', 'job-456', 'job-789']);
Scoring
Score a single application
const { scoringJob } = await nova.jobs.applications.scoringJobs.submit({
jobId: 'job-123',
applicationId: 'app-456',
body: {
resume: { type: 'url', url: 'https://storage.example.com/resumes/abc123.pdf' },
jobDescription: 'We are looking for a Senior Software Engineer...',
},
});
console.log(scoringJob.id); // 'sj_abc123'
console.log(scoringJob.status); // 'pending'
Score a batch of applications
const { batch, scoringJobIds } = await nova.jobs.scoringBatches.submit({
jobId: 'job-123',
body: {
applications: [
{
applicationId: 'app-456',
resume: { type: 'url', url: 'https://storage.example.com/resumes/456.pdf' },
},
{
applicationId: 'app-789',
resume: { type: 'url', url: 'https://storage.example.com/resumes/789.pdf' },
},
],
jobDescription: 'We are looking for...',
},
});
// Check batch progress
const status = await nova.jobs.scoringBatches.getStatus({
jobId: 'job-123',
scoringBatchId: batch.id,
});
Retrieve results
Use webhooks for real-time delivery. For polling, use these methods:
// Get a specific scoring job
const job = await nova.jobs.applications.scoringJobs.get({
jobId: 'job-123',
applicationId: 'app-456',
scoringJobId: 'sj_abc123',
});
// Get the latest score for an application (across all scoring jobs)
const latest = await nova.jobs.applications.getLatestScore({
jobId: 'job-123',
applicationId: 'app-456',
});
Question sets
Generate and retrieve clarification question sets for jobs.
// Create a question set
const { questionSet } = await nova.jobs.questionSets.create({
jobId: 'job-123',
body: {
jobContext: {
jobTitle: 'Senior Software Engineer',
companyName: 'Acme Corp',
jobDescription: 'We are looking for...',
},
},
});
// Get the current question set for a job
const current = await nova.jobs.questionSets.getCurrent('job-123');
// Get a specific question set by ID
const specific = await nova.jobs.questionSets.get({
jobId: 'job-123',
questionSetId: questionSet.id,
});
Rate limit status
Check your current rate limit usage without consuming a rate-limited request.
const limits = await nova.rateLimitStatus();
Error handling
Every method throws NovaApiError on non-2xx responses. The error carries structured fields you can use for logging, retry decisions, and support tickets.
import { Nova, NovaApiError } from '@nova-sdk/api';
try {
await nova.jobs.criteria.list('nonexistent-job');
} catch (err) {
if (err instanceof NovaApiError) {
console.log(err.code); // 'JOB_NOT_FOUND'
console.log(err.status); // 404
console.log(err.retryable); // false
console.log(err.traceId); // 'trace-abc-123' (share this with support)
console.log(err.message); // 'Job not found'
console.log(err.details); // field validation errors (on 400s)
}
}
The traceId field is the fastest way to debug issues with Nova support. Always log it.
Error properties
| Property | Type | Description |
|---|
code | ErrorCode | Machine-readable error code (e.g., JOB_NOT_FOUND, RATE_LIMITED) |
status | number | HTTP status code |
message | string | Human-readable description |
retryable | boolean | Whether retrying might succeed |
traceId | string | Trace ID for debugging |
type | string | URI identifying the error type |
details | array | null | Field-level validation errors (400 responses only) |
Retry configuration
The SDK retries failed requests automatically using exponential backoff with jitter. Retries trigger on:
- Status codes in the
retryableStatusCodes list (default: 429, 500, 502, 503, 504)
- Body-driven retryability: for other 4xx errors, the SDK checks the response body’s
retryable field
The Retry-After header is respected when the server sends it.
Defaults
| Setting | Default |
|---|
maxRetries | 2 |
initialDelayMs | 500 |
maxDelayMs | 30,000 |
backoffMultiplier | 2 |
retryableStatusCodes | [429, 500, 502, 503, 504] |
Custom retry config
const nova = new Nova({
apiKey: 'sk_live_your_key',
tenantId: 'acme-corp',
retry: {
maxRetries: 5,
initialDelayMs: 1000,
maxDelayMs: 60_000,
},
});
Disable retries
const nova = new Nova({
apiKey: 'sk_live_your_key',
tenantId: 'acme-corp',
retry: { maxRetries: 0 },
});
Webhook verification
The SDK includes utilities for verifying webhook signatures. Both methods use the Web Crypto API and work in Node.js 18+, Cloudflare Workers, Vercel Edge Functions, and Deno.
Always verify webhook signatures before processing the payload. The raw request body must be passed as a string; don’t parse it first.
Verify and parse
import { Nova, WebhookSignatureVerificationError } from '@nova-sdk/api';
// In your webhook endpoint handler
const payload = req.body; // raw string, NOT parsed JSON
const signature = req.headers['x-webhook-signature'];
try {
const event = await Nova.webhooks.constructEvent({
payload,
signatureHeader: signature,
secret: 'whsec_your_signing_secret',
});
switch (event.event) {
case 'score.completed':
console.log('Score:', event.result.score);
break;
case 'score.failed':
console.log('Score failed:', event.error.code);
break;
case 'batch.completed':
console.log(`Batch done: ${event.completedJobs}/${event.totalJobs}`);
break;
}
} catch (err) {
if (err instanceof WebhookSignatureVerificationError) {
console.error('Invalid signature:', err.message);
// Return 401
}
}
Verify-only mode
If you want to verify the signature without parsing the JSON payload, use verify.
await Nova.webhooks.verify({
payload,
signatureHeader: signature,
secret: 'whsec_your_signing_secret',
});
Timestamp tolerance
By default, webhooks older than 5 minutes (300 seconds) are rejected. You can adjust this.
await Nova.webhooks.constructEvent({
payload,
signatureHeader: signature,
secret: 'whsec_your_signing_secret',
options: { toleranceInSeconds: 600 }, // 10 minutes
});
Types
All request/response types and enums are exported from the package root.
import {
type Criterion,
type ScoringJob,
type JobContext,
type WebhookEvent,
CriterionImportance,
ScoringJobStatus,
ErrorCode,
} from '@nova-sdk/api';
Resource reference
Full list of available methods on the Nova client.
nova.criteriaLibrary.list(query?, opts?)
nova.criteriaLibrary.create(body, opts?)
nova.criteriaLibrary.get(criterionId, opts?)
nova.criteriaLibrary.update({ criterionId, body }, opts?)
nova.criteriaLibrary.delete(criterionId, opts?)
nova.jobs.criteria.getBatch(jobIds, opts?)
nova.jobs.criteria.list(jobId, opts?)
nova.jobs.criteria.set(jobId, body, opts?)
nova.jobs.criteria.archive(jobId, opts?)
nova.jobs.criteria.generate.create({ jobId, body }, opts?)
nova.jobs.criteria.generate.get({ jobId, criteriaGenerationId }, opts?)
nova.jobs.criteria.items.add({ jobId, body }, opts?)
nova.jobs.criteria.items.update({ jobId, criterionId, body }, opts?)
nova.jobs.criteria.items.remove({ jobId, criterionId }, opts?)
nova.jobs.criteria.versions.getCurrent(jobId, opts?)
nova.jobs.criteria.versions.list(jobId, opts?)
nova.jobs.criteria.versions.get({ jobId, criteriaVersionId }, opts?)
nova.jobs.applications.getLatestScore({ jobId, applicationId }, opts?)
nova.jobs.applications.scoringJobs.submit({ jobId, applicationId, body }, opts?)
nova.jobs.applications.scoringJobs.get({ jobId, applicationId, scoringJobId }, opts?)
nova.jobs.scoringBatches.submit({ jobId, body }, opts?)
nova.jobs.scoringBatches.getStatus({ jobId, scoringBatchId }, opts?)
nova.jobs.questionSets.create({ jobId, body }, opts?)
nova.jobs.questionSets.getCurrent(jobId, opts?)
nova.jobs.questionSets.get({ jobId, questionSetId }, opts?)
nova.rateLimitStatus()
Requirements
- Node.js >= 18 (or any runtime with Fetch API support)
- TypeScript >= 5.0 (if using TypeScript)