Overview
The Candidates API allows you to create, manage, and track candidates throughout your recruitment process. Candidates can optionally be associated with one or more jobs, or exist in your candidate pool without job assignments. They serve as the foundation for scheduling interviews.
Resource Structure
{
"id" : "550e8400-e29b-41d4-a716-446655440000" ,
"jobId" : "987e6543-e21b-12d3-a456-426614174000" ,
"jobIds" : [ "987e6543-e21b-12d3-a456-426614174000" ],
"firstName" : "Jane" ,
"lastName" : "Doe" ,
"email" : "[email protected] " ,
"phoneNumber" : "+421915123456" ,
"status" : "APPLIED" ,
"gdprExpiryDate" : "2026-11-16" ,
"overallRating" : 85 ,
"metadata" : {
"source" : "LinkedIn" ,
"externalId" : "CAND-12345" ,
"resumeUrl" : "https://storage.example.com/resumes/jane-doe.pdf" ,
"linkedinUrl" : "https://linkedin.com/in/janedoe"
},
"analysisCount" : 2 ,
"interviewCount" : 3 ,
"links" : {
"analyses" : "/candidates/550e8400-e29b-41d4-a716-446655440000/analyses" ,
"interviews" : "/candidates/550e8400-e29b-41d4-a716-446655440000/interviews"
},
"createdAt" : "2025-11-20T10:30:00Z" ,
"updatedAt" : "2025-11-20T10:30:00Z"
}
Custom Fields : Additional candidate information (resume URL, LinkedIn profile, skills, etc.) can be stored in the metadata field as key-value pairs.
Required Scopes
Operation Required Scope Additional Scopes List candidates read:candidatesread:jobs (for filtering)Get candidate by ID read:candidatesCreate candidate write:candidatesread:jobs (validation)Update candidate write:candidatesDelete candidate delete:candidates
Creating Candidates
Basic Candidate Creation
Candidates can be created with or without an associated job. When created without a job, they exist in your candidate pool and can be assigned to jobs later.
cURL (with job)
cURL (without job)
Node.js (with job)
Node.js (without job)
Python (with job)
Python (without job)
curl -X POST https://api.instaview.sk/candidates \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"jobId": "job-uuid",
"firstName": "Jane",
"lastName": "Doe",
"email": "[email protected] ",
"phoneNumber": "+421915123456",
"gdprExpiryDate": "2026-11-16"
}'
Comprehensive Candidate Profile
const comprehensiveCandidate = {
// Required fields
firstName: 'Jane' ,
lastName: 'Doe' ,
email: '[email protected] ' ,
phoneNumber: '+1234567890' ,
gdprExpiryDate: '2026-11-16' ,
// Optional: Associate with job(s)
jobId: 'job-uuid' , // Single job association
// Store additional data in metadata (max 10KB)
metadata: {
// Professional information
yearsOfExperience: 5 ,
currentJobTitle: 'Senior Software Engineer' ,
currentCompany: 'Tech Corp' ,
skills: [ 'JavaScript' , 'React' , 'Node.js' , 'TypeScript' , 'AWS' ],
// Education
educationLevel: 'bachelors' ,
// Location
location: {
city: 'San Francisco' ,
countryCode: 'US'
},
// Availability
availability: {
canStart: '2024-03-01' ,
noticePeriod: 30 // days
},
// Links
resumeUrl: 'https://storage.example.com/resumes/jane-doe.pdf' ,
linkedinUrl: 'https://linkedin.com/in/janedoe' ,
portfolioUrl: 'https://janedoe.dev' ,
// Additional context
notes: 'Strong technical background with excellent communication skills'
}
};
const response = await createCandidate ( comprehensiveCandidate );
Listing Candidates
List All Candidates
async function listCandidates ( page = 1 , limit = 20 ) {
const response = await fetch (
`https://api.instaview.sk/candidates?page= ${ page } &limit= ${ limit } ` ,
{
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
}
);
const result = await response . json ();
return result . data ;
}
// Usage
const { items , pagination } = await listCandidates ( 1 , 50 );
console . log ( `Found ${ pagination . total } candidates` );
Filter by Job
async function getCandidatesForJob ( jobId ) {
const response = await fetch (
`https://api.instaview.sk/candidates?jobId= ${ jobId } &limit=100` ,
{
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
}
);
return await response . json ();
}
Filter by Status
async function getActiveCandidates () {
const response = await fetch (
'https://api.instaview.sk/candidates?status=APPLIED' ,
{
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
}
);
return await response . json ();
}
Search Candidates
async function searchCandidates ( query ) {
const response = await fetch (
`https://api.instaview.sk/candidates?search= ${ encodeURIComponent ( query ) } ` ,
{
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
}
);
return await response . json ();
}
// Search by name or email
const results = await searchCandidates ( 'jane doe' );
Updating Candidates
Partial Update
async function updateCandidate ( candidateId , updates ) {
const response = await fetch (
`https://api.instaview.sk/candidates/ ${ candidateId } ` ,
{
method: 'PATCH' ,
headers: {
'Authorization' : `Bearer ${ apiKey } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ( updates )
}
);
return await response . json ();
}
// Update contact information
await updateCandidate ( 'candidate-uuid' , {
email: '[email protected] ' ,
phoneNumber: '+1987654321'
});
Update Status
// Move candidate through pipeline
await updateCandidate ( 'candidate-uuid' , { status: 'IN_PROCESS' });
await updateCandidate ( 'candidate-uuid' , { status: 'ACCEPTED' });
Update Job Assignments
// Assign candidate to multiple jobs
await updateCandidate ( 'candidate-uuid' , {
jobIds: [ 'job-uuid-1' , 'job-uuid-2' , 'job-uuid-3' ]
});
// Add candidate to a specific job
const candidate = await getCandidate ( 'candidate-uuid' );
await updateCandidate ( 'candidate-uuid' , {
jobIds: [ ... candidate . jobIds , 'new-job-uuid' ]
});
// Remove all job assignments (candidate pool)
await updateCandidate ( 'candidate-uuid' , {
jobIds: []
});
Candidate Status Management
Available Statuses
APPLIED
IN_PROCESS
REJECTED
ACCEPTED
Initial application status
Candidate has applied
Awaiting initial review
Default status for new candidates
Candidate is in the recruitment process
Resume screening in progress
Interviews scheduled or completed
Active evaluation and assessment
Not selected for position
Did not meet requirements
Unsuccessful interview
Position filled by another candidate
Candidate accepted the offer
Offer accepted by candidate
Process completed successfully
Candidate will start soon/has started
Status Workflow
class CandidateWorkflow {
async startProcess ( candidateId ) {
return await updateCandidate ( candidateId , {
status: 'IN_PROCESS'
});
}
async acceptCandidate ( candidateId , startDate ) {
return await updateCandidate ( candidateId , {
status: 'ACCEPTED' ,
metadata: {
startDate: startDate
}
});
}
async rejectCandidate ( candidateId , reason ) {
return await updateCandidate ( candidateId , {
status: 'REJECTED' ,
metadata: {
rejectionReason: reason
}
});
}
}
Multiple Job Applications
Managing Multi-Job Candidates
Candidates can be associated with multiple jobs through the jobIds field, allowing you to track a single candidate across different positions.
// Create candidate and assign to multiple jobs at once
const candidate = await createCandidate ({
firstName: 'Jane' ,
lastName: 'Doe' ,
email: '[email protected] ' ,
phoneNumber: '+1234567890' ,
gdprExpiryDate: '2026-11-16' ,
jobId: 'job-1-uuid' // Initial job assignment
});
// Later, assign to additional jobs
await updateCandidate ( candidate . id , {
jobIds: [ 'job-1-uuid' , 'job-2-uuid' , 'job-3-uuid' ]
});
// Response includes both fields for compatibility
// {
// "id": "candidate-uuid",
// "jobId": "job-1-uuid", // First job (backward compatible)
// "jobIds": ["job-1-uuid", "job-2-uuid", "job-3-uuid"],
// ...
// }
Job Associations : Use the jobIds array for managing multiple job assignments. The jobId field (singular) is maintained for backward compatibility and represents the first job.
Deleting Candidates
Soft Delete
async function deleteCandidate ( candidateId ) {
const response = await fetch (
`https://api.instaview.sk/candidates/ ${ candidateId } ` ,
{
method: 'DELETE' ,
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
}
);
return await response . json ();
}
GDPR Compliance : Deleted candidates are soft-deleted and retained for audit purposes. For permanent deletion (GDPR “right to be forgotten”), contact support.
Bulk Delete
async function bulkDeleteCandidates ( candidateIds ) {
const results = [];
for ( const id of candidateIds ) {
try {
await deleteCandidate ( id );
results . push ({ id , success: true });
await sleep ( 200 ); // Rate limiting
} catch ( error ) {
results . push ({ id , success: false , error: error . message });
}
}
return results ;
}
Common Patterns
Candidate Import from ATS
async function importCandidatesFromATS ( atsData , jobMapping ) {
const imported = [];
const errors = [];
for ( const atsCandidate of atsData ) {
try {
const instaviewJobId = jobMapping [ atsCandidate . jobId ];
if ( ! instaviewJobId ) {
throw new Error ( `No mapping for job ${ atsCandidate . jobId } ` );
}
const candidate = await createCandidate ({
jobId: instaviewJobId ,
firstName: atsCandidate . firstName ,
lastName: atsCandidate . lastName ,
email: atsCandidate . email ,
phoneNumber: atsCandidate . phone ,
resumeUrl: atsCandidate . resumeUrl ,
status: mapAtsStatus ( atsCandidate . status )
});
imported . push ( candidate );
await sleep ( 200 );
} catch ( error ) {
errors . push ({
candidate: atsCandidate ,
error: error . message
});
}
}
return { imported , errors };
}
function mapAtsStatus ( atsStatus ) {
const statusMap = {
'new' : 'APPLIED' ,
'in_review' : 'IN_PROCESS' ,
'interview' : 'IN_PROCESS' ,
'offer' : 'IN_PROCESS' ,
'hired' : 'ACCEPTED' ,
'rejected' : 'REJECTED'
};
return statusMap [ atsStatus ] || 'APPLIED' ;
}
Duplicate Detection
async function findDuplicateCandidates ( candidate ) {
// Search by email
const byEmail = await searchCandidates ( candidate . email );
// Search by phone
const byPhone = await searchCandidates ( candidate . phoneNumber );
// Combine and deduplicate
const allMatches = [ ... byEmail . data . items , ... byPhone . data . items ];
const uniqueMatches = Array . from (
new Map ( allMatches . map ( c => [ c . id , c ])). values ()
);
return uniqueMatches . filter ( c => c . id !== candidate . id );
}
async function createOrUpdateCandidate ( candidateData ) {
const duplicates = await findDuplicateCandidates ( candidateData );
if ( duplicates . length > 0 ) {
console . log ( `Found ${ duplicates . length } potential duplicates` );
// Update existing candidate
return await updateCandidate ( duplicates [ 0 ]. id , candidateData );
}
// Create new candidate
return await createCandidate ( candidateData );
}
Candidate Analytics
async function getCandidateAnalytics ( candidateId ) {
const [ candidate , interviews ] = await Promise . all ([
getCandidate ( candidateId ),
listInterviews ({ candidateId })
]);
return {
candidate ,
totalInterviews: interviews . pagination . total ,
completedInterviews: interviews . items . filter (
i => i . status === 'completed'
). length ,
averageScore: calculateAverageScore ( interviews . items ),
lastInterviewDate: getLastInterviewDate ( interviews . items )
};
}
function calculateAverageScore ( interviews ) {
const scored = interviews . filter ( i => i . analysis ?. overallScore );
if ( scored . length === 0 ) return null ;
const sum = scored . reduce (( acc , i ) => acc + i . analysis . overallScore , 0 );
return sum / scored . length ;
}
Resume Processing
async function uploadAndAttachResume ( candidateId , resumeFile ) {
// 1. Upload resume to your storage
const resumeUrl = await uploadToStorage ( resumeFile );
// 2. Update candidate with resume URL
const updated = await updateCandidate ( candidateId , {
resumeUrl: resumeUrl
});
// 3. Optional: Trigger resume parsing
await triggerResumeParser ( candidateId , resumeUrl );
return updated ;
}
async function parseResumeData ( resumeUrl ) {
// Use a resume parsing service
const parsed = await resumeParsingService . parse ( resumeUrl );
return {
skills: parsed . skills ,
yearsOfExperience: parsed . totalYears ,
educationLevel: parsed . highestDegree ,
currentJobTitle: parsed . currentPosition ,
currentCompany: parsed . currentEmployer
};
}
Validation Rules
Candidate’s first name (1-100 characters)
Candidate’s last name (1-100 characters)
GDPR expiry date in ISO 8601 format (must be in the future, e.g., “2026-11-16”)
Valid email address (at least email or phoneNumber required)
Phone number in E.164 format (+1234567890) (at least email or phoneNumber required)
UUID of the job to associate with (optional - must exist and belong to your company if provided)
Array of job UUIDs for multi-job assignments (used in update operations)
Custom key-value pairs for extensibility (max 10KB, max 5 levels deep, max 50 keys). Store additional fields like resumeUrl, skills, education, etc.
Error Scenarios
{
"error" : {
"code" : "RESOURCE_NOT_FOUND" ,
"message" : "Job not found" ,
"details" : {
"field" : "jobId" ,
"provided" : "invalid-uuid"
}
}
}
{
"error" : {
"code" : "RESOURCE_CONFLICT" ,
"message" : "A candidate with this email already exists for this job" ,
"details" : {
"field" : "email" ,
"existingCandidateId" : "existing-uuid"
}
}
}
{
"error" : {
"code" : "VALIDATION_ERROR" ,
"message" : "Invalid phone number format" ,
"details" : {
"field" : "phoneNumber" ,
"expected" : "E.164 format (e.g., +1234567890)"
}
}
}
Best Practices
Validate Data Validate email and phone formats before submission
Handle Duplicates Implement duplicate detection logic
Update Status Keep candidate status current throughout pipeline
Secure Resume URLs Use signed URLs with expiration for resume access
Next Steps