> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instaview.sk/llms.txt
> Use this file to discover all available pages before exploring further.

# Jobs

> Manage job postings and position requirements with the Jobs API

## 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

```json theme={null}
{
  "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:jobs`    |
| Get job by ID       | `read:jobs`    |
| Create job          | `write:jobs`   |
| Create job from URL | `write:jobs`   |
| Update job          | `write:jobs`   |
| Delete job          | `delete:jobs`  |

## Creating Jobs

### Basic Job Creation

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript Node.js theme={null}
  const job = await fetch("https://api.instaview.sk/jobs", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.INSTAVIEW_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: "Senior Software Engineer",
      description: "We are seeking an experienced software engineer...",
      requiredSkills: ["JavaScript", "React", "Node.js"],
      status: "OPEN",
    }),
  });

  const result = await job.json();
  console.log("Job created:", result.data.id);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://api.instaview.sk/jobs',
      headers={
          'Authorization': f'Bearer {os.environ["INSTAVIEW_API_KEY"]}',
          'Content-Type': 'application/json'
      },
      json={
          'title': 'Senior Software Engineer',
          'description': 'We are seeking an experienced software engineer...',
          'requiredSkills': ['JavaScript', 'React', 'Node.js'],
          'status': 'OPEN'
      }
  )

  job = response.json()['data']
  print(f'Job created: {job["id"]}')
  ```
</CodeGroup>

### 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:

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.instaview.sk/jobs/url", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.INSTAVIEW_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jobUrl: "https://example.com/careers/senior-developer",
    }),
  });

  const result = await response.json();
  console.log("Job created:", result.data.data.id);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://api.instaview.sk/jobs/url',
      headers={
          'Authorization': f'Bearer {os.environ["INSTAVIEW_API_KEY"]}',
          'Content-Type': 'application/json'
      },
      json={
          'jobUrl': 'https://example.com/careers/senior-developer'
      }
  )

  result = response.json()['data']
  print(f'Job created: {result["data"]["id"]}')
  ```
</CodeGroup>

<Tip>
  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.
</Tip>

<Info>
  This endpoint has a stricter rate limit of **5 requests per minute** due to
  web scraping and AI processing.
</Info>

### Comprehensive Job Creation

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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");
```

<Info>
  **Best Practice**: For production integrations, always implement request timeouts.

  ```javascript theme={null}
  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);
  }

  ```
</Info>

## Updating Jobs

### Partial Update

```javascript theme={null}
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",
},
});
```

<Info>
  **Best Practice**: For production integrations, always implement request timeouts.

  ```javascript theme={null}
  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);
  }
  ```
</Info>

### Change Job Status

```javascript theme={null}
// Mark job as closed
await updateJob("job-uuid", { status: "CLOSED" });

// Reopen job
await updateJob("job-uuid", { status: "OPEN" });
```

## Job Status Management

### Available Statuses

<Tabs>
  <Tab title="OPEN">
    Job is open for applications - Visible to candidates - Can schedule
    interviews - Accepts new applications
  </Tab>

  <Tab title="CLOSED">
    Position has been filled or paused - Not visible to new candidates -
    Existing candidates retained - No new applications accepted
  </Tab>
</Tabs>

### Status Workflow

```javascript theme={null}
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              |
| -------- | ------------------------ |
| `REMOTE` | Fully remote position    |
| `ONSITE` | Office-based only        |
| `HYBRID` | Mix of remote and office |

### Location Structure

```javascript theme={null}
{
  location: {
    street: '123 Tech Street',      // Optional
    city: 'San Francisco',           // Recommended
    postalCode: '94105',             // Optional
    countryCode: 'US'                // Required (ISO 3166-1 alpha-2)
  }
}
```

### Examples

```javascript theme={null}
// 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 Information

### Salary Structure

```javascript theme={null}
{
  salary: {
    min: 100000,           // Minimum compensation
    max: 150000,           // Maximum compensation
    currency: 'USD',       // ISO 4217 currency code
    period: 'YEARLY'       // Options: 'YEARLY', 'MONTHLY', 'HOURLY'
  }
}
```

### Examples

```javascript theme={null}
// 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

<AccordionGroup>
  <Accordion title="Be Specific" icon="bullseye">
    ```javascript theme={null}
    // ✅ Good: Specific technologies
    requiredSkills: ['React 18+', 'Node.js', 'PostgreSQL', 'TypeScript']

    // ❌ Too vague
    requiredSkills: ['Frontend', 'Backend', 'Database']
    ```
  </Accordion>

  <Accordion title="Separate Required vs Nice-to-Have" icon="list-check">
    ```javascript theme={null}
    {
      requiredSkills: [
        'JavaScript',
        'React',
        'REST APIs'
      ],
      niceToHaveSkills: [
        'TypeScript',
        'GraphQL',
        'Docker'
      ]
    }
    ```
  </Accordion>

  <Accordion title="Use Consistent Naming" icon="tag">
    ```javascript theme={null}
    // ✅ Consistent
    requiredSkills: ['JavaScript', 'TypeScript', 'Node.js']

    // ❌ Inconsistent casing/spelling
    requiredSkills: ['javascript', 'Typescript', 'NodeJS']
    ```
  </Accordion>
</AccordionGroup>

## Deleting Jobs

### Soft Delete

Jobs are soft-deleted by default:

```javascript theme={null}
async function deleteJob(jobId) {
  const response = await fetch(`https://api.instaview.sk/jobs/${jobId}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  return await response.json();
}
```

<Warning>
  Deleted jobs are not returned in list queries but maintain their relationships
  with candidates and interviews for audit purposes.
</Warning>

### Before Deleting

Consider the impact:

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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

<ResponseField name="title" type="string" required>
  Job title (1-200 characters)
</ResponseField>

<ResponseField name="description" type="string">
  Detailed job description (max 10,000 characters)
</ResponseField>

<ResponseField name="requiredSkills" type="array">
  Array of required skills (max 50 items, each max 100 chars)
</ResponseField>

<ResponseField name="workMode" type="enum">
  One of: `REMOTE`, `ONSITE`, `HYBRID`
</ResponseField>

<ResponseField name="location.countryCode" type="string" required>
  ISO 3166-1 alpha-2 country code (e.g., "US", "GB", "DE")
</ResponseField>

<ResponseField name="salary.currency" type="string">
  ISO 4217 currency code (e.g., "USD", "EUR", "GBP")
</ResponseField>

<ResponseField name="status" type="enum">
  One of: `OPEN`, `CLOSED`, `UNDEFINED` **Note**: In practice, use `OPEN`
  (accepting applications) or `CLOSED` (no longer hiring)
</ResponseField>

## Error Scenarios

<AccordionGroup>
  <Accordion title="Invalid Work Mode" icon="circle-xmark">
    ```json theme={null}
    {
      "error": {
        "code": "VALIDATION_ERROR",
        "message": "Invalid work mode",
        "details": {
          "field": "workMode",
          "provided": "flexible",
          "allowed": ["REMOTE", "ONSITE", "HYBRID"]
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Job Not Found" icon="magnifying-glass">
    ```json theme={null}
    {
      "error": {
        "code": "RESOURCE_NOT_FOUND",
        "message": "Job not found",
        "details": {
          "resourceType": "Job",
          "resourceId": "invalid-uuid"
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Access Denied" icon="ban">
    ```json theme={null}
    {
      "error": {
        "code": "RESOURCE_ACCESS_DENIED",
        "message": "Access denied to this job",
        "details": {
          "reason": "Job belongs to different company"
        }
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Draft Status" icon="file">
    Create jobs as drafts while preparing content
  </Card>

  <Card title="Consistent Skills" icon="list">
    Maintain a standardized skill taxonomy
  </Card>

  <Card title="Include Salary" icon="dollar-sign">
    Transparency improves candidate quality
  </Card>

  <Card title="Update Regularly" icon="rotate">
    Keep job descriptions current and accurate
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Candidates" icon="user" href="/guides/resources/candidates">
    Add candidates to your jobs
  </Card>

  <Card title="Interviews" icon="microphone" href="/guides/resources/interviews">
    Schedule interviews for job candidates
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    View complete Jobs API reference
  </Card>

  <Card title="Best Practices" icon="star" href="/guides/best-practices">
    Production integration patterns
  </Card>
</CardGroup>
