# Supervisible Public API Reference

> Organization-scoped API for third-party integrations and automation.

- Version: v1
- Base URL: https://app.supervisible.com/api/v1
- Auth: Bearer token via API key (`Authorization: Bearer sv_live_...`)
- All responses use `{ data: ... }` envelope
- Pagination: `limit` (default 50, max 200) and `offset` (default 0)
- Errors: `{ error: { message: "..." } }` with HTTP status code
- OpenAPI spec: https://www.supervisible.com/openapi/public-api-v1.json
- Developer docs: https://www.supervisible.com/developers

## Me

### GET /me

**Get API key identity**
Returns metadata for the authenticated API key and actor identity.

**Response (200):**

```json
{
  "data": {
    "keyId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
    "keyName": "Zapier Sync",
    "organizationId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
    "actorUserId": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
    "scopes": [
      "read:users",
      "write:projects"
    ]
  }
}
```

---

## Users

### GET /users

**List users**

Scope: read:users

**Query parameters:**

- `limit` (number, optional) — Number of rows (default 50, max 200).
- `offset` (number, optional) — Pagination offset (default 0).

**Response (200):**

```json
{
  "data": {}
}
```

---

### PATCH /users/{user_id}

**Update a user**

Scope: write:users

**Path parameters:**

- `user_id` (string, required) — User ID.

**Request body (JSON):**

- `name` (string, optional) — Display name.
- `image` (string, optional) — Avatar URL. — one of: string, null
- `countryCode` (string, optional) — ISO 3166-1 alpha-2 country code. — one of: string, null
- `defaultAvailability` (number, optional) — Default weekly availability in hours.
- `reportsToId` (string, optional) — Manager user ID.

Example body:

```json
{
  "name": "Updated Name",
  "defaultAvailability": 40
}
```

**Response (200):**

```json
{
  "data": {
    "name": "Updated Name",
    "defaultAvailability": 40
  }
}
```

---

## Clients

### GET /clients

**List clients**

Scope: read:clients

**Query parameters:**

- `limit` (number, optional) — Number of rows (default 50, max 200).
- `offset` (number, optional) — Pagination offset (default 0).

**Response (200):**

```json
{
  "data": {}
}
```

---

### POST /clients

**Create a client**

Scope: write:clients

**Request body (JSON):**

- `companyName` (string, required) — Client name.
- `email` (string, optional) — Client contact email. — one of: string, null
- `image` (string, optional) — Logo URL. — one of: string, null
- `countryCode` (string, optional) — ISO country code. — one of: string, null
- `website` (string, optional) — Company website URL. — one of: string, null
- `isActive` (boolean, optional) — Active status.
- `clientPriority` (string, optional) — Priority level. — one of: high, medium, low
- `categories` (array, optional) — Client categories.
- `accountManagerId` (string, optional) — Account manager user ID.

Example body:

```json
{
  "companyName": "Acme CRM",
  "website": "https://acme.example",
  "clientPriority": "high"
}
```

**Response (200):**

```json
{
  "data": {
    "companyName": "Acme CRM",
    "website": "https://acme.example",
    "clientPriority": "high"
  }
}
```

---

### PATCH /clients/{client_id}

**Update a client**

Scope: write:clients

**Path parameters:**

- `client_id` (string, required) — Client ID.

**Request body (JSON):**

- `companyName` (string, optional) — Client name.
- `email` (string, optional) — Client contact email. — one of: string, null
- `image` (string, optional) — Logo URL. — one of: string, null
- `countryCode` (string, optional) — ISO country code. — one of: string, null
- `website` (string, optional) — Company website URL. — one of: string, null
- `isActive` (boolean, optional) — Active status.
- `clientPriority` (string, optional) — Priority level. — one of: high, medium, low
- `categories` (array, optional) — Client categories.
- `accountManagerId` (string, optional) — Account manager user ID.

Example body:

```json
{
  "companyName": "Acme CRM Updated"
}
```

**Response (200):**

```json
{
  "data": {
    "companyName": "Acme CRM Updated"
  }
}
```

---

## Projects

### GET /projects

**List projects**

Scope: read:projects

**Query parameters:**

- `limit` (number, optional) — Number of rows (default 50, max 200).
- `offset` (number, optional) — Pagination offset (default 0).

**Response (200):**

```json
{
  "data": {}
}
```

---

### POST /projects

**Create a project**

Scope: write:projects

**Request body (JSON):**

- `name` (string, required) — Project name.
- `clientId` (string, required) — Client ID.
- `startDate` (string, required) — Project start date.
- `endDate` (string, required) — Project end date.
- `objective` (string, optional) — Project objective. — one of: string, null
- `projectManagerId` (string, optional) — Project manager user ID.
- `status` (string, optional) — Project status. — one of: draft, planned, active, completed, cancelled
- `billingType` (string, optional) — Project billing type. — one of: fixed_price, retainer, time_materials, non_billable
- `amount` (number, optional) — Project amount for fixed or retainer billing.
- `hourlyRate` (number, optional) — Hourly rate for time & materials billing.

Example body:

```json
{
  "name": "CRM Sync Project",
  "clientId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
  "startDate": "2026-01-01",
  "endDate": "2026-03-01",
  "status": "planned"
}
```

**Response (200):**

```json
{
  "data": {
    "name": "CRM Sync Project",
    "clientId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
    "startDate": "2026-01-01",
    "endDate": "2026-03-01",
    "status": "planned"
  }
}
```

---

### PATCH /projects/{project_id}

**Update a project**

Scope: write:projects

**Path parameters:**

- `project_id` (string, required) — Project ID.

**Request body (JSON):**

- `name` (string, optional) — Project name.
- `objective` (string, optional) — Project objective. — one of: string, null
- `startDate` (string, optional) — Project start date.
- `endDate` (string, optional) — Project end date.
- `projectManagerId` (string, optional) — Project manager user ID.
- `status` (string, optional) — Project status. — one of: draft, planned, active, completed, cancelled

Example body:

```json
{
  "status": "active"
}
```

**Response (200):**

```json
{
  "data": {
    "status": "active"
  }
}
```

---

## Assignments

### GET /assignments

**List user assignments**
Returns planned user assignments scoped to your organization.

Scope: read:assignments

**Query parameters:**

- `user_id` (string, optional) — Filter by user.
- `project_id` (string, optional) — Filter by project.
- `start_date` (string, optional) — Include assignments on/after this date.
- `end_date` (string, optional) — Include assignments on/before this date.
- `limit` (number, optional) — Number of rows (default 50, max 200).
- `offset` (number, optional) — Pagination offset (default 0).

**Response (200):**

```json
{
  "data": {}
}
```

---

### POST /assignments

**Upsert user assignments**
Creates or updates assignment rows by unique key (user, project, capability, date).

Scope: write:assignments

**Request body (JSON):**

- `items` (array, required) — Bulk upsert payload.

Example body:

```json
{
  "items": [
    {
      "userId": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
      "projectId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
      "capabilityId": null,
      "date": "2026-01-06",
      "hours": 8
    }
  ]
}
```

**Response (200):**

```json
{
  "data": [
    {
      "id": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
      "userId": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
      "projectId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
      "capabilityId": null,
      "date": "2026-01-06",
      "hours": 8
    }
  ]
}
```

---

## Actual hours

### GET /actual-hours

**List actual hours**
Returns submitted/actual hours scoped to your organization.

Scope: read:actual_hours

**Query parameters:**

- `user_id` (string, optional) — Filter by user.
- `project_id` (string, optional) — Filter by project.
- `start_date` (string, optional) — Include rows on/after this date.
- `end_date` (string, optional) — Include rows on/before this date.
- `limit` (number, optional) — Number of rows (default 50, max 200).
- `offset` (number, optional) — Pagination offset (default 0).

**Response (200):**

```json
{
  "data": {}
}
```

---

### POST /actual-hours

**Upsert actual hours**
Creates or updates actual-hour rows by unique key (user, project, capability, date).

Scope: write:actual_hours

**Request body (JSON):**

- `items` (array, required) — Bulk upsert payload.

Example body:

```json
{
  "items": [
    {
      "userId": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
      "projectId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
      "capabilityId": null,
      "date": "2026-01-06",
      "hours": 7
    }
  ]
}
```

**Response (200):**

```json
{
  "data": {
    "items": [
      {
        "userId": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
        "projectId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
        "capabilityId": null,
        "date": "2026-01-06",
        "hours": 7
      }
    ]
  }
}
```

---

## Time off

### GET /time-off

**List time off requests**

Scope: read:time_off

**Query parameters:**

- `user_id` (string, optional) — Filter by user.
- `status` (string, optional) — Filter by request status. — one of: pending, approved, rejected
- `start_date` (string, optional) — Include requests ending on/after this date.
- `end_date` (string, optional) — Include requests starting on/before this date.
- `limit` (number, optional) — Number of rows (default 50, max 200).
- `offset` (number, optional) — Pagination offset (default 0).

**Response (200):**

```json
{
  "data": {}
}
```

---

### POST /time-off

**Create a time off request**

Scope: write:time_off

**Request body (JSON):**

- `userId` (string, required) — Requesting user ID.
- `timeOffTypeId` (string, required) — Time off type ID.
- `startDate` (string, required) — Start date.
- `endDate` (string, required) — End date.
- `availability` (number, required) — Daily available hours (0-24).
- `reason` (string, required) — Reason for request.
- `status` (string, optional) — Optional initial status. Use pending for normal request flow. — one of: pending, approved, rejected

Example body:

```json
{
  "userId": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
  "timeOffTypeId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
  "startDate": "2026-02-03",
  "endDate": "2026-02-05",
  "availability": 0,
  "reason": "Vacation",
  "status": "pending"
}
```

**Response (200):**

```json
{
  "data": {
    "userId": "019cb675-c4df-7f8b-8a83-5f28581f5e7e",
    "timeOffTypeId": "019cb675-c4d6-7e90-8806-25e5145c3a06",
    "startDate": "2026-02-03",
    "endDate": "2026-02-05",
    "availability": 0,
    "reason": "Vacation",
    "status": "pending"
  }
}
```

---

### PATCH /time-off/{request_id}

**Update a time off request**

Scope: write:time_off

**Path parameters:**

- `request_id` (string, required) — Time off request ID.

**Request body (JSON):**

- `timeOffTypeId` (string, optional) — Updated time off type.
- `startDate` (string, optional) — Updated start date.
- `endDate` (string, optional) — Updated end date.
- `availability` (number, optional) — Updated daily available hours (0-24).
- `reason` (string, optional) — Updated reason.

Example body:

```json
{
  "reason": "Vacation updated"
}
```

**Response (200):**

```json
{
  "data": {
    "reason": "Vacation updated"
  }
}
```

---

### DELETE /time-off/{request_id}

**Delete a time off request**

Scope: write:time_off

**Path parameters:**

- `request_id` (string, required) — Time off request ID.

**Response (200):**

```json
{
  "data": {}
}
```

---

### POST /time-off/{request_id}/approve

**Approve a time off request**
Requires manager/admin approval permissions for the requesting user.

Scope: write:time_off

**Path parameters:**

- `request_id` (string, required) — Time off request ID.

**Response (200):**

```json
{
  "data": {}
}
```

---

### POST /time-off/{request_id}/reject

**Reject a time off request**
Requires manager/admin approval permissions for the requesting user.

Scope: write:time_off

**Path parameters:**

- `request_id` (string, required) — Time off request ID.

**Request body (JSON):**

- `rejectionReason` (string, required) — Human-readable reason for rejection.

Example body:

```json
{
  "rejectionReason": "Coverage needed this week"
}
```

**Response (200):**

```json
{
  "data": {
    "rejectionReason": "Coverage needed this week"
  }
}
```

---
