Overview
The Jobs API allows you to create, manage, and organize job postings within your company. Jobs are the foundation of the InstaView recruitment workflow, serving as the primary container for candidates and interviews.
Resource Structure
{
"id" : "550e8400-e29b-41d4-a716-446655440000" ,
"companyId" : "987e6543-e21b-12d3-a456-426614174000" ,
"title" : "Senior Software Engineer" ,
"description" : "We are seeking an experienced software engineer..." ,
"requiredSkills" : [ "JavaScript" , "React" , "Node.js" , "TypeScript" ],
"niceToHaveSkills" : [ "GraphQL" , "Docker" , "AWS" ],
"languageRequirements" : [
{ "language" : "EN" , "proficiency" : "C1" },
{ "language" : "ES" , "proficiency" : "B1" }
],
"location" : {
"workMode" : "HYBRID" ,
"street" : "123 Tech Street" ,
"city" : "San Francisco" ,
"postalCode" : "94105" ,
"countryCode" : "US"
},
"salary" : {
"min" : 120000 ,
"max" : 180000 ,
"currency" : "USD" ,
"period" : "YEARLY"
},
"jobUrl" : "https://company.com/careers/senior-engineer" ,
"status" : "OPEN" ,
"createdAt" : "2024-01-15T10:30:00Z" ,
"updatedAt" : "2024-01-15T10:30:00Z"
}
Required Scopes
Operation Required Scope List jobs read:jobsGet job by ID read:jobsCreate job write:jobsCreate job from URL write:jobsUpdate job write:jobsDelete job delete:jobs
Creating Jobs
Basic Job Creation
curl -X POST https://api.instaview.sk/jobs \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Senior Software Engineer",
"description": "We are seeking an experienced software engineer...",
"requiredSkills": ["JavaScript", "React", "Node.js"],
"status": "OPEN"
}'
Create Job from URL
You can also create a job by providing a URL to an existing job posting. The API will scrape the page, extract structured data using AI, and create the job automatically. Set createJob to false to only extract data without creating a job entity:
curl -X POST https://api.instaview.sk/jobs/url \
-H "Authorization: Bearer your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"jobUrl": "https://example.com/careers/senior-developer"
}'
Set createJob to false to preview extracted data without creating a job.
The response returns 200 (instead of 201 ) and data contains a preview with all extracted fields — id and timestamps will be empty strings.
This endpoint has a stricter rate limit of 5 requests per minute due to
web scraping and AI processing.
Comprehensive Job Creation
const comprehensiveJob = {
// Required fields
title: "Senior Software Engineer" ,
description:
"We are seeking an experienced software engineer with 5+ years of experience..." ,
// Skills
requiredSkills: [ "JavaScript" , "React" , "Node.js" , "TypeScript" , "REST APIs" ],
niceToHaveSkills: [ "GraphQL" , "Docker" , "Kubernetes" , "AWS" ],
// Language requirements (ISO 639-1 language codes + CEFR proficiency levels)
languageRequirements: [
{ language: "EN" , proficiency: "C1" }, // English - Proficient
{ language: "ES" , proficiency: "B1" }, // Spanish - Intermediate
],
// Location
location: {
workMode: "HYBRID" , // Options: REMOTE, HYBRID, ONSITE
street: "123 Tech Street" ,
city: "San Francisco" ,
postalCode: "94105" ,
countryCode: "US" ,
},
// Compensation
salary: {
min: 120000 ,
max: 180000 ,
currency: "USD" ,
period: "YEARLY" ,
},
// Additional info
jobUrl: "https://company.com/careers/senior-engineer" ,
status: "OPEN" ,
};
const response = await createJob ( comprehensiveJob );
Listing Jobs
List All Jobs
async function listJobs ( page = 1 , limit = 20 ) {
const response = await fetch (
`https://api.instaview.sk/jobs?page= ${ page } &limit= ${ limit } ` ,
{
headers: { Authorization: `Bearer ${ apiKey } ` },
},
);
const result = await response . json ();
return result . data ;
}
// Usage
const { items , pagination } = await listJobs ( 1 , 50 );
console . log ( `Found ${ pagination . total } jobs` );
Filter by Status
async function getActiveJobs () {
const response = await fetch (
"https://api.instaview.sk/jobs?status=OPEN&limit=100" ,
{
headers: { Authorization: `Bearer ${ apiKey } ` },
},
);
return await response . json ();
}
Search Jobs
async function searchJobs ( query ) {
const response = await fetch (
`https://api.instaview.sk/jobs?search= ${ encodeURIComponent ( query ) } ` ,
{
headers: { Authorization: `Bearer ${ apiKey } ` },
},
);
return await response . json ();
}
// Search by title
const engineerJobs = await searchJobs ( "engineer" );
Best Practice : For production integrations, always implement request timeouts.const controller = new AbortController ();
const timeoutId = setTimeout (() => controller . abort (), 10000 ); // 10s timeout
try {
const response = await fetch ( url , {
headers: { Authorization: `Bearer ${ apiKey } ` },
signal: controller . signal ,
});
} finally {
clearTimeout ( timeoutId );
}
Updating Jobs
Partial Update
async function updateJob ( jobId , updates ) {
const response = await fetch ( `https://api.instaview.sk/jobs/ ${ jobId } ` , {
method: "PATCH" ,
headers: {
Authorization: `Bearer ${ apiKey } ` ,
"Content-Type" : "application/json" ,
},
body: JSON . stringify ( updates ),
});
return await response . json ();
}
// Update only specific fields
await updateJob ( "job-uuid" , {
title: "Staff Software Engineer" , // Promoted!
salary: {
min: 150000 ,
max: 220000 ,
currency: "USD" ,
period: "YEARLY" ,
},
});
Best Practice : For production integrations, always implement request timeouts.const controller = new AbortController ();
const timeoutId = setTimeout (() => controller . abort (), 10000 ); // 10s timeout
try {
const response = await fetch ( url , {
headers: { Authorization: `Bearer ${ apiKey } ` },
signal: controller . signal ,
});
} finally {
clearTimeout ( timeoutId );
}
Change Job Status
// Mark job as closed
await updateJob ( "job-uuid" , { status: "CLOSED" });
// Reopen job
await updateJob ( "job-uuid" , { status: "OPEN" });
Job Status Management
Available Statuses
Job is open for applications - Visible to candidates - Can schedule
interviews - Accepts new applications
Position has been filled or paused - Not visible to new candidates -
Existing candidates retained - No new applications accepted
Status Workflow
class JobWorkflow {
async createJob ( jobData ) {
return await createJob ({
... jobData ,
status: "OPEN" , // Jobs start as OPEN
});
}
async closeJob ( jobId ) {
return await updateJob ( jobId , { status: "CLOSED" });
}
async reopenJob ( jobId ) {
return await updateJob ( jobId , { status: "OPEN" });
}
}
Location and Work Mode
Work Modes
Mode Description REMOTEFully remote position ONSITEOffice-based only HYBRIDMix of remote and office
Location Structure
{
location : {
street : '123 Tech Street' , // Optional
city : 'San Francisco' , // Recommended
postalCode : '94105' , // Optional
countryCode : 'US' // Required (ISO 3166-1 alpha-2)
}
}
Examples
// Fully remote position
{
location : {
workMode : 'REMOTE' ,
countryCode : 'US' // Country for legal/tax purposes
}
}
// Hybrid with specific office
{
location : {
workMode : 'HYBRID' ,
street : '1 Apple Park Way' ,
city : 'Cupertino' ,
postalCode : '95014' ,
countryCode : 'US'
}
}
// On-site position
{
location : {
workMode : 'ONSITE' ,
city : 'New York' ,
countryCode : 'US'
}
}
Salary Structure
{
salary : {
min : 100000 , // Minimum compensation
max : 150000 , // Maximum compensation
currency : 'USD' , // ISO 4217 currency code
period : 'YEARLY' // Options: 'YEARLY', 'MONTHLY', 'HOURLY'
}
}
Examples
// Annual salary range
{
salary : {
min : 120000 ,
max : 180000 ,
currency : 'USD' ,
period : 'YEARLY'
}
}
// Hourly rate
{
salary : {
min : 75 ,
max : 125 ,
currency : 'USD' ,
period : 'HOURLY'
}
}
// Monthly salary (common in some regions)
{
salary : {
min : 8000 ,
max : 12000 ,
currency : 'EUR' ,
period : 'MONTHLY'
}
}
// Omit salary (not disclosed)
{
// salary field can be omitted entirely
}
Skills Management
Best Practices
// ✅ Good: Specific technologies
requiredSkills : [ 'React 18+' , 'Node.js' , 'PostgreSQL' , 'TypeScript' ]
// ❌ Too vague
requiredSkills : [ 'Frontend' , 'Backend' , 'Database' ]
Separate Required vs Nice-to-Have
{
requiredSkills : [
'JavaScript' ,
'React' ,
'REST APIs'
],
niceToHaveSkills : [
'TypeScript' ,
'GraphQL' ,
'Docker'
]
}
// ✅ Consistent
requiredSkills : [ 'JavaScript' , 'TypeScript' , 'Node.js' ]
// ❌ Inconsistent casing/spelling
requiredSkills : [ 'javascript' , 'Typescript' , 'NodeJS' ]
Deleting Jobs
Soft Delete
Jobs are soft-deleted by default:
async function deleteJob ( jobId ) {
const response = await fetch ( `https://api.instaview.sk/jobs/ ${ jobId } ` , {
method: "DELETE" ,
headers: { Authorization: `Bearer ${ apiKey } ` },
});
return await response . json ();
}
Deleted jobs are not returned in list queries but maintain their relationships
with candidates and interviews for audit purposes.
Before Deleting
Consider the impact:
async function safeDeleteJob ( jobId ) {
// Check for active candidates
const candidates = await listCandidates ({ jobId });
if ( candidates . pagination . total > 0 ) {
console . warn ( `Job has ${ candidates . pagination . total } candidates` );
const confirm = await askUser ( "Delete anyway?" );
if ( ! confirm ) return ;
}
// Proceed with deletion
return await deleteJob ( jobId );
}
Common Patterns
Bulk Job Creation
async function bulkCreateJobs ( jobsData ) {
const results = [];
const errors = [];
for ( const jobData of jobsData ) {
try {
const job = await createJob ( jobData );
results . push ( job );
// Rate limiting: wait between requests
await sleep ( 200 );
} catch ( error ) {
errors . push ({
jobData ,
error: error . message ,
});
}
}
return { results , errors };
}
Job Synchronization
async function syncJobsFromATS ( atsJobs ) {
const existingJobs = await listAllJobs ();
const existingJobsMap = new Map ( existingJobs . map (( j ) => [ j . externalId , j ]));
for ( const atsJob of atsJobs ) {
const existing = existingJobsMap . get ( atsJob . id );
if ( ! existing ) {
// Create new job
await createJob ( transformAtsJob ( atsJob ));
} else if ( hasChanges ( existing , atsJob )) {
// Update existing job
await updateJob ( existing . id , transformAtsJob ( atsJob ));
}
}
}
Job Analytics
async function getJobStats ( jobId ) {
const [ candidates , interviews ] = await Promise . all ([
listCandidates ({ jobId }),
listInterviews ({ jobId }),
]);
return {
totalCandidates: candidates . pagination . total ,
activeCandidates: candidates . items . filter (( c ) => c . status === "OPEN" )
. length ,
totalInterviews: interviews . pagination . total ,
completedInterviews: interviews . items . filter (
( i ) => i . status === "completed" ,
). length ,
};
}
Validation Rules
Job title (1-200 characters)
Detailed job description (max 10,000 characters)
Array of required skills (max 50 items, each max 100 chars)
One of: REMOTE, ONSITE, HYBRID
ISO 3166-1 alpha-2 country code (e.g., “US”, “GB”, “DE”)
ISO 4217 currency code (e.g., “USD”, “EUR”, “GBP”)
One of: OPEN, CLOSED, UNDEFINED Note : In practice, use OPEN
(accepting applications) or CLOSED (no longer hiring)
Error Scenarios
{
"error" : {
"code" : "VALIDATION_ERROR" ,
"message" : "Invalid work mode" ,
"details" : {
"field" : "workMode" ,
"provided" : "flexible" ,
"allowed" : [ "REMOTE" , "ONSITE" , "HYBRID" ]
}
}
}
{
"error" : {
"code" : "RESOURCE_NOT_FOUND" ,
"message" : "Job not found" ,
"details" : {
"resourceType" : "Job" ,
"resourceId" : "invalid-uuid"
}
}
}
{
"error" : {
"code" : "RESOURCE_ACCESS_DENIED" ,
"message" : "Access denied to this job" ,
"details" : {
"reason" : "Job belongs to different company"
}
}
}
Best Practices
Use Draft Status Create jobs as drafts while preparing content
Consistent Skills Maintain a standardized skill taxonomy
Include Salary Transparency improves candidate quality
Update Regularly Keep job descriptions current and accurate
Next Steps