Skip to main content
This guide covers all error types you may encounter and how to handle them.

Error Response Format

Errors use an RFC 7807-inspired structure wrapped in an error envelope:
{
  "error": {
    "type": "https://embed.nova.dweet.com/errors/validation-error",
    "code": "VALIDATION_ERROR",
    "status": 400,
    "message": "Request validation failed",
    "details": [...],
    "requestId": "req_abc123def456"
  }
}
FieldDescription
typeURI reference for documentation
codeMachine-readable error code
statusHTTP status code
messageHuman-readable summary
detailsField-level errors (validation only)
requestIdUnique ID for debugging

Synchronous Error Codes

Authentication Errors

CodeStatusDescriptionAction
UNAUTHORIZED401Missing/invalid API keyCheck your API key
FORBIDDEN403Valid key, insufficient permissionsContact support

Validation Errors

CodeStatusDescriptionAction
VALIDATION_ERROR400Request body failed validationFix fields listed in details
QUESTION_SET_NOT_FOUND404Invalid questionSetIdUse ID from /v1/criteria/questions
ANSWER_MISMATCH400Answer doesn’t match questionCheck answer ID and type

Resource Errors

CodeStatusDescriptionAction
NOT_FOUND404Resource not foundVerify the ID

Rate Limiting

CodeStatusDescriptionAction
RATE_LIMITED429Too many requestsWait for Retry-After duration

Server Errors

CodeStatusDescriptionAction
INTERNAL_ERROR500Unexpected errorRetry with backoff
SERVICE_UNAVAILABLE503Temporary unavailabilityRetry with backoff

Async Scoring Error Codes

These appear in webhook payloads or GET /v1/score/{scoringJobId} responses:
CodeDescriptionAction
CRITERIA_NOT_FOUNDNo active criteria for this jobGenerate criteria first via /v1/criteria/generate
RESUME_FETCH_FAILEDCould not download resumeVerify URL is accessible and not expired
RESUME_ENCRYPTEDPDF is password-protectedRequest unprotected version from candidate
RESUME_CORRUPTEDPDF is malformed or truncatedVerify file opens correctly, re-upload if needed
RESUME_PARSE_FAILEDCould not parse resume contentCheck file format is supported
RESUME_TOO_LARGEFile exceeds 10MB limitReduce file size
RESUME_EMPTYNo extractable contentVerify file isn’t corrupted
CRITERIA_INVALIDMalformed criteriaVerify criteria format
AI_PROCESSING_FAILEDAI model errorRetry - usually transient
TIMEOUTProcessing exceeded limitRetry - may indicate complex resume

Validation Error Details

For VALIDATION_ERROR, the details array provides field-level information:
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "jobContext.jobTitle",
        "code": "REQUIRED",
        "message": "jobTitle is required"
      },
      {
        "field": "answers[0].value",
        "code": "INVALID_TYPE",
        "message": "Expected string[] for multiSelect, got string"
      }
    ]
  }
}

Detail Codes

CodeDescription
REQUIREDField is required but missing
TOO_SHORTString is shorter than minimum
TOO_LONGString exceeds maximum
INVALID_TYPEWrong data type
INVALID_FORMATWrong format (URL, UUID, etc.)
INVALID_VALUEValue not in allowed set
NOT_FOUNDReferenced resource doesn’t exist

Retry Strategy

These errors should be retried with exponential backoff:
  • 429 RATE_LIMITED - Wait for Retry-After
  • 500 INTERNAL_ERROR - Retry 2-3 times
  • 503 SERVICE_UNAVAILABLE - Retry 2-3 times
  • RESUME_FETCH_FAILED - Regenerate URL and retry
  • AI_PROCESSING_FAILED - Retry 1-2 times
  • TIMEOUT - Retry once

Implementation

class NovaClient {
  async request(method, path, body) {
    const maxRetries = 3;
    let lastError;
    
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        const response = await fetch(`${this.baseUrl}${path}`, {
          method,
          headers: this.headers,
          body: body ? JSON.stringify(body) : undefined,
        });
        
        if (response.ok) {
          return response.json();
        }
        
        const error = await response.json();
        lastError = error.error;
        
        // Handle rate limiting
        if (response.status === 429) {
          const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
          console.log(`Rate limited. Waiting ${retryAfter}s...`);
          await sleep(retryAfter * 1000);
          continue;
        }
        
        // Retry on server errors
        if (response.status >= 500 && attempt < maxRetries - 1) {
          const delay = Math.pow(2, attempt) * 1000;
          console.log(`Server error. Retrying in ${delay}ms...`);
          await sleep(delay);
          continue;
        }
        
        // Non-retryable error
        throw new NovaError(lastError);
        
      } catch (networkError) {
        lastError = networkError;
        if (attempt < maxRetries - 1) {
          await sleep(Math.pow(2, attempt) * 1000);
          continue;
        }
      }
    }
    
    throw new NovaError(lastError);
  }
}

class NovaError extends Error {
  constructor(error) {
    super(error.message);
    this.code = error.code;
    this.status = error.status;
    this.details = error.details;
    this.requestId = error.requestId;
  }
  
  get isRetryable() {
    return ['RATE_LIMITED', 'INTERNAL_ERROR', 'SERVICE_UNAVAILABLE'].includes(this.code);
  }
}

Debugging with Request ID

Every response includes a requestId. When contacting support:
Request ID: req_abc123def456
Timestamp: 2025-01-15T10:30:45Z
Error: VALIDATION_ERROR - jobDescription is required
Log the requestId for all failed requests. This helps us trace issues quickly.

Monitoring Recommendations

Track error rates by code and alert if they exceed baseline.
Track P50/P99 latency for early warning of issues.
Include requestId, request body, and response for debugging.
High retry rates may indicate configuration issues.

Common Issues

Causes:
  • URL expired before we could fetch it
  • Firewall blocking our IP range
  • Certificate issues
Solutions:
  • Set longer expiry (24h recommended)
  • Whitelist our IP ranges (contact support)
  • Verify SSL certificates are valid
Causes:
  • Answer ID doesn’t match any question
  • Answer type doesn’t match question type
  • Using wrong questionSetId
Solutions:
  • Verify answer IDs match question IDs exactly
  • Use string for singleSelect/text, array for multiSelect
  • Store and use the correct questionSetId
Causes:
  • Transient infrastructure issues
  • High load
Solutions:
  • Implement retry with exponential backoff
  • These typically resolve within seconds