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

# Scopes and Permissions

> Understanding scope-based access control in the InstaView API

## Overview

InstaView uses scope-based permissions to provide granular access control. Each API key has a set of scopes that determine which operations it can perform on which resources.

## Permission Model

### Three-Level Access Control

Every resource type supports three permission levels:

<CardGroup cols={3}>
  <Card title="Read" icon="eye" color="#3b82f6">
    **GET** operations - List resources - View resource details - Search and
    filter
  </Card>

  <Card title="Write" icon="pen" color="#10b981">
    **POST** and **PATCH** operations - Create resources - Update resources -
    Modify configurations
  </Card>

  <Card title="Delete" icon="trash" color="#ef4444">
    **DELETE** operations - Soft delete resources - Remove associations -
    Archive data
  </Card>
</CardGroup>

### Scope Format

Scopes follow the format: `<permission>:<resource>`

```
read:jobs       // Can list and view jobs
write:candidates // Can create and update candidates
delete:agents    // Can delete agents
```

## Available Scopes

<Tabs>
  <Tab title="Jobs">
    | Scope         | Permissions        | Operations                       |
    | ------------- | ------------------ | -------------------------------- |
    | `read:jobs`   | View jobs          | `GET /jobs`, `GET /jobs/{id}`    |
    | `write:jobs`  | Create/update jobs | `POST /jobs`, `PATCH /jobs/{id}` |
    | `delete:jobs` | Delete jobs        | `DELETE /jobs/{id}`              |

    **Common Combinations**:

    ```json theme={null}
    ["read:jobs"]                          // Read-only
    ["read:jobs", "write:jobs"]            // Create and read
    ["read:jobs", "write:jobs", "delete:jobs"] // Full access
    ```
  </Tab>

  {" "}

  <Tab title="Candidates">
    | Scope               | Permissions       | Operations                                   |
    | ------------------- | ----------------- | -------------------------------------------- |
    | `read:candidates`   | View candidates   | `GET /candidates`, `GET /candidates/{id}`    |
    | `write:candidates`  | Create/update     | `POST /candidates`, `PATCH /candidates/{id}` |
    | `delete:candidates` | Delete candidates | `DELETE /candidates/{id}`                    |

    **Common Combinations**:

    ```json theme={null}
    ["read:candidates", "read:jobs"]                           // View candidates and their jobs
    ["read:candidates", "write:candidates", "read:jobs"]       // Manage candidates
    ```
  </Tab>

  {" "}

  <Tab title="Interviews">
    | Scope               | Permissions         | Operations                                |
    | ------------------- | ------------------- | ----------------------------------------- |
    | `read:interviews`   | View interviews     | `GET /interviews`, `GET /interviews/{id}` |
    | `write:interviews`  | Schedule interviews | `POST /interviews`                        |
    | `delete:interviews` | Delete interviews   | `DELETE /interviews/{id}`                 |

    **Common Combinations**:

    ```json theme={null}
    ["read:interviews"]                                                      // View results only
    ["read:interviews", "write:interviews", "read:candidates", "read:agents"] // Full interview management
    ["read:interviews", "write:interviews", "delete:interviews", "read:candidates", "read:agents"] // Full access
    ```
  </Tab>

  {" "}

  <Tab title="Agents">
    \| Scope | Permissions | Operations | |-------|-------------|------------| |
    `read:agents` | View agents | `GET /agents`, `GET /agents/{id}` | |
    `write:agents` | Create/update | `POST /agents`, `PATCH /agents/{id}` | |
    `delete:agents` | Delete agents | `DELETE /agents/{id}` | **Common
    Combinations**: `json ["read:agents"] // View agent templates   ["read:agents", "write:agents"] // Manage agent configurations `
  </Tab>

  {" "}

  <Tab title="Companies">
    \| Scope | Permissions | Operations | |-------|-------------|------------| |
    `read:companies` | View company | `GET /companies/{id}` | | `write:companies`
    \| Update company | `PATCH /companies/{id}` | | - | No delete | Companies
    cannot be deleted via API | **Note**: Regular API keys can only access their
    own company. ATS keys can access multiple companies.
  </Tab>

  <Tab title="Billing">
    | Scope          | Permissions       | Operations                                 |
    | -------------- | ----------------- | ------------------------------------------ |
    | `read:billing` | View billing info | `GET /billing/usage`                       |
    | -              | No write          | Billing changes must be made via dashboard |
    | -              | No delete         | Billing data cannot be deleted             |

    **Read-only resource** for monitoring usage and subscription status.
  </Tab>

  <Tab title="Webhooks">
    | Scope            | Permissions            | Operations                                                                                                                         |
    | ---------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
    | `read:webhooks`  | View webhooks          | `GET /webhooks`, `GET /webhooks/{id}`                                                                                              |
    | `write:webhooks` | Create/update webhooks | `POST /webhooks`, `PATCH /webhooks/{id}`, `DELETE /webhooks/{id}`, `POST /webhooks/{id}/test`, `POST /webhooks/{id}/reset-circuit` |
    | -                | No delete scope        | Deletion uses `write:webhooks` scope                                                                                               |

    **Common Combinations**:

    ```json theme={null}
    ["read:webhooks"]                     // View webhook configurations
    ["read:webhooks", "write:webhooks"]   // Full webhook management
    ```
  </Tab>
</Tabs>

## Scope Validation

### Request-Time Validation

Every API request validates the required scopes:

```javascript theme={null}
// Request
POST /jobs
Authorization: Bearer sk_xxx (scopes: ["read:jobs"])

// Response: 403 Forbidden
{
  "message": "Insufficient permissions: Required scope write:jobs not found",
  "error": "Forbidden",
  "statusCode": 403
}
```

### Validation Flow

<Steps>
  <Step title="Extract API Key">
    API key is extracted from `Authorization: Bearer` header
  </Step>

  <Step title="Validate Key">
    Key is validated (not revoked, not expired, not suspended)
  </Step>

  <Step title="Check Scopes">
    Request's required scope is compared against key's scopes
  </Step>

  <Step title="Company Isolation">
    Verify resource belongs to key's company (if applicable)
  </Step>

  <Step title="Execute Request">If all checks pass, request is processed</Step>
</Steps>

## Scope Strategy

### By Use Case

<Tabs>
  <Tab title="Analytics Dashboard">
    ```json theme={null}
    {
      "name": "Analytics Dashboard",
      "scopes": [
        "read:jobs",
        "read:candidates",
        "read:interviews",
        "read:billing"
      ]
    }
    ```

    **Rationale**: Dashboard only needs to view data, never modify it.
  </Tab>

  <Tab title="Candidate Sync">
    ```json theme={null}
    {
      "name": "ATS Candidate Sync",
      "scopes": [
        "read:jobs",
        "read:candidates",
        "write:candidates"
      ]
    }
    ```

    **Rationale**: Sync tool needs to create/update candidates and read job info, but shouldn't delete or modify jobs.
  </Tab>

  <Tab title="Interview Automation">
    ```json theme={null}
    {
      "name": "Interview Automation",
      "scopes": [
        "read:candidates",
        "read:agents",
        "read:interviews",
        "write:interviews"
      ]
    }
    ```

    **Rationale**: Automated interview scheduling needs read access to candidates/agents and write access to create interviews.
  </Tab>

  <Tab title="Primary Integration">
    ```json theme={null}
    {
      "name": "Primary Integration",
      "scopes": [
        "read:jobs",
        "write:jobs",
        "delete:jobs",
        "read:candidates",
        "write:candidates",
        "delete:candidates",
        "read:interviews",
        "write:interviews",
        "delete:interviews",
        "read:agents",
        "write:agents",
        "delete:agents",
        "read:companies",
        "write:companies",
        "read:billing",
        "read:webhooks",
        "write:webhooks"
      ]
    }
    ```

    **Rationale**: Main application integration needs full access to all resources.
  </Tab>

  <Tab title="Webhook Handler">
    ```json theme={null}
    {
      "name": "Webhook Configuration",
      "scopes": [
        "read:webhooks",
        "write:webhooks"
      ]
    }
    ```

    **Rationale**: Dedicated key for managing webhook configurations only.
  </Tab>
</Tabs>

### Least Privilege Principle

Always grant the minimum scopes needed:

<AccordionGroup>
  <Accordion title="Why Least Privilege?" icon="shield-halved">
    * **Reduces Attack Surface**: Compromised keys have limited damage potential
    * **Prevents Accidents**: Applications can't accidentally delete data they shouldn't
    * **Audit Trail**: Scope requirements make it clear what each integration does
    * **Compliance**: Many regulations require least-privilege access
  </Accordion>

  {" "}

  <Accordion title="Example: Webhook Handler" icon="webhook">
    A webhook handler that processes interview completion events: ❌ **Too many
    scopes**: `json ["read:interviews", "write:interviews", "read:candidates",
          "write:candidates"] ` ✅ **Minimal scopes**: `json ["read:interviews",
          "read:candidates"] ` The webhook only needs to read interview data, not
    create or modify it.
  </Accordion>

  <Accordion title="Example: Reporting Tool" icon="chart-line">
    A reporting tool that generates analytics:

    ❌ **Includes write scopes**:

    ```json theme={null}
    ["read:jobs", "write:jobs", "read:candidates", "write:candidates"]
    ```

    ✅ **Read-only**:

    ```json theme={null}
    ["read:jobs", "read:candidates", "read:interviews", "read:billing"]
    ```

    Reporting tools should never have write or delete permissions.
  </Accordion>
</AccordionGroup>

## Scope Dependencies

Some operations require multiple scopes due to resource relationships:

### Creating Candidates

```javascript theme={null}
POST /candidates
{
  "jobId": "job-uuid",
  "firstName": "Jane",
  "lastName": "Doe"
}
```

**Required Scopes**:

* `write:candidates` - To create the candidate
* `read:jobs` - To validate the job exists and belongs to your company

### Creating Interviews

```javascript theme={null}
POST /interviews
{
  "candidateId": "candidate-uuid",
  "agentId": "agent-uuid"
}
```

**Required Scopes**:

* `write:interviews` - To create the interview
* `read:candidates` - To validate candidate ownership
* `read:agents` - To validate agent ownership

### Listing Candidates with Job Filters

```javascript theme={null}
GET /candidates?jobId=job-uuid
```

**Required Scopes**:

* `read:candidates` - To list candidates
* `read:jobs` - To filter by job

## Cross-Resource Operations

### Inline Resources

Some operations allow creating inline resources:

```javascript theme={null}
// Create interview with inline candidate
POST /interviews
{
  "candidate": {
    "jobId": "job-uuid",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@example.com",
    "gdprExpiryDate": "2026-11-16T12:00:00Z"
  },
  "agentId": "agent-uuid"
}
```

**Required Scopes**:

* `write:interviews` - Primary operation
* `write:candidates` - To create inline candidate
* `read:agents` - To validate agent
* `read:jobs` - To validate job

<Info>
  Inline resources (candidate, job, agent) are created automatically as part of
  the parent operation and follow the same scope requirements. The XOR behavior
  ensures you use either existing resource IDs or inline resource definitions,
  not both.
</Info>

## Company Isolation

All scopes are enforced within company boundaries:

### Regular API Keys

```javascript theme={null}
// Key belongs to Company A
Authorization: Bearer sk_abc123def456ghi789

// ✅ Can access Company A's jobs
GET /jobs

// ❌ Cannot access Company B's jobs (even if you know the ID)
GET /jobs/company-b-job-uuid
// Returns: 403 Forbidden
```

### ATS Integration Keys

```javascript theme={null}
// ATS key with access to multiple companies
Authorization: Bearer sk_ats123xyz456abc789

// ✅ Must specify companyId for Company A
GET /jobs?companyId=company-a-uuid

// ✅ Can access Company B with different companyId
GET /jobs?companyId=company-b-uuid

// ❌ Cannot access Company C (not managed by this ATS key)
GET /jobs?companyId=company-c-uuid
// Returns: 403 Forbidden - "Access denied to this company"
```

## Scope Errors

<ResponseField name="Insufficient Permissions (403)" type="error">
  The API key lacks the required scope

  ```json theme={null}
  {
    "message": "Insufficient permissions: Required scope write:jobs not found",
    "error": "Forbidden",
    "statusCode": 403
  }
  ```

  **Solution**: Add the required scope to your API key or create a new key with appropriate scopes.
</ResponseField>

<ResponseField name="Access Denied (403)" type="error">
  Resource doesn't exist or belongs to another company

  ```json theme={null}
  {
    "message": "Access denied to this resource",
    "error": "Forbidden",
    "statusCode": 403
  }
  ```

  **Causes**:

  * Resource belongs to a different company
  * Resource has been soft-deleted
  * Invalid resource ID
</ResponseField>

## Best Practices

<AccordionGroup>
  <Accordion title="Create Purpose-Specific Keys" icon="key">
    Instead of one key with all scopes, create multiple keys for different purposes:

    ```javascript theme={null}
    // Separate keys for different integrations
    const analyticsKey = 'sk_live_analytics';  // read-only
    const syncKey = 'sk_live_sync';            // write candidates
    const automationKey = 'sk_live_automation'; // write interviews
    ```
  </Accordion>

  {" "}

  <Accordion title="Document Required Scopes" icon="book">
    Document which scopes each part of your application needs: \`\`\`javascript /\*\* \*
    Candidate Sync Service \* \* Required Scopes: \* - read:jobs (validate job
    ownership) \* - read:candidates (check for duplicates) \* - write:candidates
    (create/update candidates) \*/ class CandidateSyncService{" "}

    {
          // ...
      }

    ````
    </Accordion>

    <Accordion title="Handle Permission Errors" icon="triangle-exclamation">
      Gracefully handle insufficient permission errors:
      
      ```javascript
      async function createJob(jobData) {
        try {
          return await api.post('/jobs', jobData);
        } catch (error) {
          if (error.response?.status === 403) {
            console.error(
              `Permission denied: ${error.response.data.message}`
            );
            // Notify admin or fallback to alternative workflow
          }
          throw error;
        }
      }
    ````
  </Accordion>

  <Accordion title="Regular Scope Audits" icon="magnifying-glass">
    Periodically review API key scopes:

    * Are all scopes still necessary?
    * Can any keys be downgraded to read-only?
    * Are there unused keys that can be revoked?
  </Accordion>
</AccordionGroup>

## Testing Scopes

### Verify Your Scopes

```javascript theme={null}
// Verify your API key by making a simple GET request
// to any read endpoint you have access to, such as:
GET /jobs
Authorization: Bearer sk_abc123def456ghi789

// If successful, your API key is valid and has the required scope
```

### Test Scope Requirements

```javascript theme={null}
// Test each operation with your key
const tests = [
  { method: "GET", path: "/jobs", scope: "read:jobs" },
  { method: "POST", path: "/jobs", scope: "write:jobs" },
  { method: "GET", path: "/candidates", scope: "read:candidates" },
  { method: "POST", path: "/candidates", scope: "write:candidates" },
];

for (const test of tests) {
  try {
    await api.request(test.method, test.path);
    console.log(`✅ ${test.scope}: Success`);
  } catch (error) {
    if (error.response?.status === 403) {
      console.log(`❌ ${test.scope}: Missing`);
    }
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="API Keys" icon="key" href="/guides/api-keys">
    Learn about API key management
  </Card>

  <Card title="Rate Limiting" icon="gauge" href="/guides/rate-limiting">
    Understand rate limits per scope
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/guides/error-handling">
    Handle permission errors gracefully
  </Card>

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