npm package: @nova-sdk/api
Description: TypeScript SDK for the Nova Embed API
Latest version: 0.3.0

# @nova-sdk/api

TypeScript SDK for the [Nova Embed API](https://nova.dweet.com/docs/embed-api).

## Installation

```bash
npm install @nova-sdk/api
```

## Quick start

```typescript
import { Nova } from '@nova-sdk/api';

const nova = new Nova({
  apiKey: 'sk_live_...',
  tenantId: 'acme-corp',
});

// Generate criteria for a job
const criteria = await nova.jobs.criteria.generate.create({
  jobId: 'job_123',
  body: {
    jobContext: {
      jobTitle: 'Senior Engineer',
      companyName: 'Acme Corp',
      jobDescription: 'We are looking for...',
    },
  },
});

// Submit a scoring job
const score = await nova.jobs.applications.scoringJobs.submit({
  jobId: 'job_123',
  applicationId: 'app_456',
  body: {
    resume: { type: 'url', url: 'https://example.com/resume.pdf' },
    jobDescription: 'We are looking for...',
  },
});

// Get the latest score for an application
const latest = await nova.jobs.applications.getLatestScore({
  jobId: 'job_123',
  applicationId: 'app_456',
});
```

## Configuration

```typescript
const nova = new Nova({
  // Required
  apiKey: 'sk_live_...',
  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
});
```

## Resource hierarchy

```
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()
```

## Per-request options

All methods accept an optional `RequestOptions` object as the last argument.

Use it to override the tenant ID for a single request:

```typescript
await nova.criteriaLibrary.list({}, { tenantId: 'other-tenant' });
```

For criteria and library mutations, you can also pass an `idempotencyKey`:

```typescript
await nova.jobs.criteria.items.add(
  {
    jobId: 'job123',
    body: { text: '5+ years backend experience', importance: 'required' },
  },
  { idempotencyKey: 'some-key-123' },
);
```

The SDK only sends `Idempotency-Key` to endpoints that support the generic replay layer. Scoring endpoints still use their built-in idempotency rules.

## Error handling

All methods throw `NovaApiError` on non-2xx responses:

```typescript
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'
    console.log(err.message);   // 'Job not found'
    console.log(err.details);   // field validation errors (on 400s)
  }
}
```

## Retries

The SDK retries requests that fail with `429` (rate limited) or `5xx` (server error) status codes using exponential backoff with jitter. The `Retry-After` response header is respected when present.

For other error responses (4xx), the SDK also checks the response body's `retryable` field — set by the API per the OpenAPI spec — and retries if `true`. The `retryableStatusCodes` config is a fast-path for well-known status codes; body-driven retryability covers the rest automatically.

Disable retries:

```typescript
const nova = new Nova({
  apiKey: 'sk_live_...',
  tenantId: 'acme-corp',
  retry: { maxRetries: 0 },
});
```

## Development

### Generated code

`src/generated/` contains the typed API client generated from `docs/embed-api/openapi.json` via [`@hey-api/openapi-ts`](https://heyapi.dev). These files are committed to the repo so that workspace consumers and npm users don't need to run the generator.

To regenerate after OpenAPI spec changes:

```bash
cd packages/embed-sdk
pnpm generate
```

### Workspace consumption

Within the monorepo, apps import source directly (`./src/index.ts`) via the `exports` field. Both `apps/nova` and `apps/embed` include `"@nova-sdk/api"` in their `transpilePackages` config. The `publishConfig.exports` override switches to `dist/` for npm publishing.

## Types

All request/response types and enums are exported from the package root:

```typescript
import {
  type Criterion,
  type ScoringJob,
  type JobContext,
  CriterionImportance,
  ScoringJobStatus,
  ErrorCode,
} from '@nova-sdk/api';
```