Errors follow RFC 7807:
{
"type": "https://docs.nova.dweet.com/embed-api/errors#validation-error",
"code": "VALIDATION_ERROR",
"status": 400,
"message": "Request validation failed",
"retryable": false,
"traceId": "5c2f4f5b2c0a4ce0b6a31a1a18f8e9a1",
"details": [
{
"field": "resume.url",
"code": "invalid",
"message": "Resume URL must be a valid URL"
}
]
}
| Field | Description |
|---|
type | RFC 7807 URI identifying the error category |
code | Machine-readable error code for programmatic matching |
status | HTTP status code |
message | Human-readable error description |
retryable | Whether retrying may succeed |
traceId | Trace ID for debugging (also in X-Trace-Id header) |
details | Field-level validation errors (when present) |
When to retry
retryable: true: retry with exponential backoff
429 RATE_LIMITED: wait for Retry-After header value
- Other
4xx: fix the request first
For criteria and library mutations, use Idempotency-Key and reuse the same key on retries. Scoring submissions already deduplicate by jobId and applicationId.
Common error codes
| Code | Status | Action |
|---|
UNAUTHORIZED | 401 | Check API key and Authorization header |
VALIDATION_ERROR | 400 | Fix the fields listed in details |
ANSWER_MISMATCH | 400 | Answers must match the question set |
CRITERIA_NOT_FOUND | 404 | Generate criteria for the job first |
SCORING_JOB_NOT_FOUND | 404 | Verify scoringJobId exists for this tenant |
RESUME_FETCH_FAILED | 422 | Check resume URL is accessible and not expired |
RESUME_TOO_LARGE | 422 | File must be under 50 MB |
RESUME_ENCRYPTED | 422 | Remove password protection |
RESUME_PARSE_FAILED | 422 | Confirm valid PDF, DOC, or DOCX |
RATE_LIMITED | 429 | Wait for Retry-After and retry |
Idempotency
These errors apply to routes that support the HTTP Idempotency-Key replay layer. Scoring submissions use built-in scoring idempotency instead.
| Code | Status | What to do |
|---|
IDEMPOTENCY_REQUEST_IN_PROGRESS | 409 | Wait briefly and retry using the same Idempotency-Key |
IDEMPOTENCY_KEY_ALREADY_USED | 422 | Do not retry. Generate a new key and retry only after you have fixed the request parameters |
If a mutation reaches endpoint logic and returns a cacheable 4xx, retrying with the same Idempotency-Key replays that same error. If the request fails before endpoint logic runs, for example because a required header is missing, the Content-Type is invalid, the JSON is malformed, schema or param validation fails, or rate limiting blocks the request, the response is not cached.
This page covers the most common errors. For the complete list of error codes with HTTP status, retryability, and surfaces, see the Error Code Reference.
Error handling with the SDK
The TypeScript SDK retries automatically on 429 and 5xx errors with exponential backoff and Retry-After support. You only need to handle non-retryable errors:
import { Nova, NovaApiError } from '@nova-sdk/api';
const nova = new Nova({
apiKey: process.env.NOVA_API_KEY!,
tenantId: 'acme-corp',
});
try {
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...',
},
});
} catch (err) {
if (err instanceof NovaApiError) {
console.log(err.code); // e.g. 'VALIDATION_ERROR'
console.log(err.status); // e.g. 400
console.log(err.retryable); // false
console.log(err.traceId); // for debugging
console.log(err.details); // field-level validation errors
}
}
Disable retries if you want full control:
const nova = new Nova({
apiKey: process.env.NOVA_API_KEY!,
tenantId: 'acme-corp',
retry: { maxRetries: 0 },
});
Manual retry logic (cURL / raw HTTP)
If you aren’t using the SDK, implement retries yourself:
async function novaRequest(url, { method, headers, body }) {
const maxAttempts = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const response = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (response.ok) {
return response.json();
}
const error = await response.json();
if (response.status === 429) {
const retryAfterSeconds = Number(response.headers.get("Retry-After") ?? "60");
await new Promise((r) => setTimeout(r, retryAfterSeconds * 1000));
continue;
}
if (error.retryable && attempt < maxAttempts) {
const delayMs = 250 * Math.pow(2, attempt - 1);
await new Promise((r) => setTimeout(r, delayMs));
continue;
}
throw Object.assign(new Error(error.message), { error });
}
}
Debugging
When contacting support, include the traceId, your timestamp and endpoint, and the HTTP status code.