Skip to main content

Documentation Index

Fetch the complete documentation index at: https://nova.dweet.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

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 { 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 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

Tenant-scoped methods accept an optional RequestOptions object as their 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 { criteria } = 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 { criterion: 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...',
    },
  },
});

// Each question has: id, question (string), selectionType, options (string[]), hint
// 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, guarded against stale overwrites
const currentCriteriaVersion = await nova.jobs.criteria.versions.getCurrent('job-123');

await nova.jobs.criteria.set('job-123', {
  criteria: [
    {
      text: '3+ years of Python experience',
      importance: 'MUST_HAVE',
    },
  ],
}, {
  expectedCriteriaRevision: currentCriteriaVersion.criteriaRevision,
});

// 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
// Returns { version: CriteriaVersionMeta, criteria: Criterion[] }
const { version, criteria } = await nova.jobs.criteria.versions.getCurrent('job-123');

// List all versions (metadata only, no criteria arrays)
const { versions } = await nova.jobs.criteria.versions.list('job-123');

// Get a specific version (includes criteria)
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 { items } = await nova.jobs.criteria.getBatch(['job-123', 'job-456', 'job-789']);
// items: [{ jobId: 'job-123', criteria: [...] }, { jobId: 'job-456', criteria: null }, ...]

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, acceptedApplications, rejectedApplications } =
  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...',
  },
});

console.log(acceptedApplications); // applications with scoring jobs
console.log(rejectedApplications); // invalid resume inputs rejected before queueing

// Check batch progress
const status = await nova.jobs.scoringBatches.getStatus({
  jobId: 'job-123',
  scoringBatchId: batch.id,
});

console.log(status.rejectedApplications);

Retrieve results

Use webhooks for real-time delivery. For polling, use these methods:
// Get a specific scoring job
const { scoringJob } = await nova.jobs.applications.scoringJobs.get({
  jobId: 'job-123',
  applicationId: 'app-456',
  scoringJobId: 'sj_abc123',
});
// scoringJob.score and scoringJob.assessment are top-level fields

// Get the latest score for an application (across all scoring jobs)
const { scoringJob: latest } = await nova.jobs.applications.getLatestScore({
  jobId: 'job-123',
  applicationId: 'app-456',
});

Deletion requests

Use deletion requests when a customer leaves, a tenant asks you to delete its data, or an application must be removed before automatic cleanup runs. Deletion runs in the background. The SDK returns a deletionRequest.id immediately, then you poll until the request is completed or failed.
// Delete one application under one job
const { deletionRequest } = await nova.deletion.deleteApplication({
  jobId: 'job-123',
  applicationId: 'app-456',
});

// Delete all active Embed data for one tenant
await nova.deletion.deleteTenant({
  tenantExternalId: 'acme-corp',
});

// Poll for completion
const status = await nova.deletion.getRequest(deletionRequest.id);
console.log(status.deletionRequest.status); // 'pending' | 'processing' | 'completed' | 'failed'
Application deletion uses the client tenant ID, or a per-request tenant override. Tenant deletion scopes the request by tenantExternalId, so it doesn’t need X-Tenant-Id. Deletion request responses include only the request ID, scope, status, and timestamps. They don’t include internal cleanup counts, raw tenant IDs, job IDs, or application IDs. See Deletion Requests, Data Retention, Delete Application Data, Delete Tenant Data, and Get Deletion Request Status.

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 DeletionRequest,
  type WebhookEvent,
  CriterionImportance,
  ScoringJobStatus,
  DeletionStatus,
  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.deletion.deleteApplication({ jobId, applicationId }, opts?)
nova.deletion.deleteTenant({ tenantExternalId })
nova.deletion.getRequest(deletionRequestId)

nova.rateLimitStatus()

Requirements

  • Node.js >= 18 (or any runtime with Fetch API support)
  • TypeScript >= 5.0 (if using TypeScript)