Skip to main content
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

1

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
2

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

PropertyTypeDescription
codeErrorCodeMachine-readable error code (e.g., JOB_NOT_FOUND, RATE_LIMITED)
statusnumberHTTP status code
messagestringHuman-readable description
retryablebooleanWhether retrying might succeed
traceIdstringTrace ID for debugging
typestringURI identifying the error type
detailsarray | nullField-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

SettingDefault
maxRetries2
initialDelayMs500
maxDelayMs30,000
backoffMultiplier2
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)