# Invisible Cap Table API Reference

Base URL: `https://api.invisible.app`

JSON naming: all request/response fields use **camelCase**. Enum values use **camelCase** (e.g., `"common"`, `"convertibleNote"`).

---

## 1. Authentication

All endpoints except `/health`, `/api/chat/public`, and OAuth metadata require authentication.

### Method A: API Key

Get a key by asking the chat assistant at [invisible.app](https://invisible.app).

```
Authorization: Bearer icap_YOUR_KEY
```

The API key is scoped to a single organization. No additional headers needed.

### Method B: JWT (Cognito)

```
Authorization: Bearer <jwt_token>
X-Organization-Id: <org_uuid>
```

`X-Organization-Id` is required for all endpoints except `/api/me`, `/api/organizations`, `/api/invites/redeem`, `/api/org-invites/redeem`, `/api/chat`, and `/api/conversations`.

### Role-Based Access

| Role | Read cap table | Write cap table | Manage members |
|------|---------------|-----------------|----------------|
| `owner` | Full | Yes | Yes |
| `admin` | Full | Yes | Yes |
| `member` | Full | Yes | No |
| `viewer` | Full (if `canViewFullCapTable=true`) or own holdings only | No | No |
| `employee` | Own holdings only | No | No |

Write endpoints (`POST`, `PUT`, `DELETE` on cap table data) return `403` for `viewer` and `employee` roles.

**Visibility modifiers** for restricted viewers (viewer without full cap table, employee):
- **Manager hierarchy**: If the user's linked stakeholder has reports (via `manager_id`), they see the full subtree of reports' equity data.
- **Department access**: Admins can grant users view access to all equity data in specific departments via `department_access`.
- The user sees the union of: own holdings + report tree + department access.

---

## 2. Cap Table (Read)

### GET /api/cap-table

Full cap table with all stakeholders, summary, security classes, and investor groups.

**Auth:** Required. Restricted viewers see only their own holdings.

```bash
curl -s https://api.invisible.app/api/cap-table \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "stakeholders": [
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000001",
      "name": "Sarah Chen",
      "type": "individual",
      "commonShares": 4000000,
      "preferredShares": 0,
      "preferredSeriesDetail": [],
      "optionShares": 0,
      "fullyDilutedShares": 4000000,
      "ownershipPct": 40.0
    },
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000002",
      "name": "Sequoia Capital",
      "type": "entity",
      "commonShares": 0,
      "preferredShares": 2000000,
      "preferredSeriesDetail": [
        { "series": "Series A Preferred", "shares": 2000000, "asConvertedShares": 2000000 }
      ],
      "optionShares": 0,
      "fullyDilutedShares": 2000000,
      "ownershipPct": 20.0
    }
  ],
  "summary": {
    "totalCommon": 6000000,
    "totalPreferred": 2000000,
    "totalOptionsOutstanding": 500000,
    "totalFullyDiluted": 10000000,
    "optionPoolAuthorized": 1000000,
    "optionPoolAvailable": 500000
  },
  "securityClasses": [
    {
      "id": "b2c3d4e5-0001-4000-8000-000000000001",
      "name": "Common Stock",
      "type": "common",
      "authorizedShares": 8000000,
      "outstandingShares": 6000000,
      "originalIssuePrice": null,
      "liquidationPreference": null,
      "conversionRatio": 1.0,
      "isParticipating": false,
      "participationCap": null,
      "seniorityRank": null
    },
    {
      "id": "b2c3d4e5-0001-4000-8000-000000000002",
      "name": "Series A Preferred",
      "type": "preferred",
      "authorizedShares": 3000000,
      "outstandingShares": 2000000,
      "originalIssuePrice": 1.50,
      "liquidationPreference": 1.0,
      "conversionRatio": 1.0,
      "isParticipating": false,
      "participationCap": null,
      "seniorityRank": 1
    }
  ],
  "investorGroups": [
    {
      "id": "c3d4e5f6-0001-4000-8000-000000000001",
      "name": "Series A Investors",
      "totalShares": 2000000,
      "ownershipPct": 20.0,
      "memberIds": ["a1b2c3d4-0001-4000-8000-000000000002"]
    }
  ]
}
```

---

### GET /api/stakeholders

List all stakeholders. Supports search.

**Auth:** Required. Restricted viewers see only themselves.

**Query params:**

| Param | Type | Description |
|-------|------|-------------|
| `search` | string | Filter by name (partial match) |

```bash
curl -s "https://api.invisible.app/api/stakeholders?search=chen" \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "stakeholders": [
    {
      "id": "a1b2c3d4-0001-4000-8000-000000000001",
      "name": "Sarah Chen",
      "type": "individual",
      "email": "sarah@acme.com",
      "jobTitle": "CEO",
      "department": "Executive",
      "managerId": null,
      "managerName": null,
      "isActive": true
    }
  ]
}
```

---

### GET /api/stakeholders/{id}

Detailed view of a single stakeholder including holdings, transactions, and option grants.

**Auth:** Required. Restricted viewers can only access their own linked stakeholder.

```bash
curl -s https://api.invisible.app/api/stakeholders/a1b2c3d4-0001-4000-8000-000000000001 \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "stakeholder": {
    "id": "a1b2c3d4-0001-4000-8000-000000000001",
    "organizationId": "d4e5f6a7-0001-4000-8000-000000000001",
    "name": "Sarah Chen",
    "type": "individual",
    "email": "sarah@acme.com",
    "jobTitle": "CEO",
    "department": "Executive",
    "employeeId": "EMP-001",
    "userId": null,
    "isActive": true,
    "createdAt": "2024-01-15T00:00:00Z",
    "updatedAt": "2024-01-15T00:00:00Z"
  },
  "holdings": [
    { "securityClass": "Common Stock", "shares": 4000000, "pctOwnership": 40.0 }
  ],
  "transactions": [
    {
      "id": "e5f6a7b8-0001-4000-8000-000000000001",
      "securityId": "CS-001",
      "type": "issuance",
      "shares": 4000000,
      "pricePerShare": 0.001,
      "date": "2024-01-15T00:00:00Z",
      "notes": "Founder shares"
    }
  ],
  "optionGrants": []
}
```

---

### GET /api/security-classes

List all security classes for the organization.

**Auth:** Required.

```bash
curl -s https://api.invisible.app/api/security-classes \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "classes": [
    {
      "id": "b2c3d4e5-0001-4000-8000-000000000001",
      "name": "Common Stock",
      "type": "common",
      "authorizedShares": 8000000,
      "outstandingShares": 6000000,
      "originalIssuePrice": null,
      "liquidationPreference": null,
      "conversionRatio": 1.0,
      "isParticipating": false,
      "participationCap": null,
      "seniorityRank": null
    }
  ]
}
```

---

### GET /api/equity-plans

List all equity incentive plans.

**Auth:** Required.

```bash
curl -s https://api.invisible.app/api/equity-plans \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "plans": [
    {
      "id": "f6a7b8c9-0001-4000-8000-000000000001",
      "organizationId": "d4e5f6a7-0001-4000-8000-000000000001",
      "name": "2024 Equity Incentive Plan",
      "year": 2024,
      "authorizedShares": 1000000,
      "createdAt": "2024-01-15T00:00:00Z"
    }
  ]
}
```

---

## 3. Option Grants

### GET /api/option-grants

List option grants with optional filters.

**Auth:** Required. Restricted viewers see only their own grants.

**Query params:**

| Param | Type | Description |
|-------|------|-------------|
| `stakeholderId` | uuid | Filter by stakeholder |
| `department` | string | Filter by department |
| `status` | string | Comma-separated. Values: `proposed`, `pendingApproval`, `approved`, `active`, `fullyVested`, `exercised`, `partiallyExercised`, `cancelled`, `expired` |
| `planName` | string | Filter by equity plan name |
| `asOfDate` | string | Calculate vesting as of this date (`YYYY-MM-DD`) |

```bash
curl -s "https://api.invisible.app/api/option-grants?status=active,fullyVested&department=Engineering" \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "grants": [
    {
      "id": "11111111-0001-4000-8000-000000000001",
      "employeeName": "James Park",
      "jobTitle": "Senior Engineer",
      "department": "Engineering",
      "grantNumber": "OPT-2024-001",
      "optionType": "iso",
      "grantDate": "2024-03-01T00:00:00Z",
      "vestingStartDate": "2024-03-01T00:00:00Z",
      "exercisePrice": 0.50,
      "sharesGranted": 50000,
      "sharesVested": 12500,
      "sharesUnvested": 37500,
      "sharesExercised": 0,
      "sharesExercisable": 12500,
      "expirationDate": "2034-03-01T00:00:00Z",
      "status": "active",
      "category": "newHire",
      "notes": null,
      "planName": "2024 Equity Incentive Plan"
    }
  ]
}
```

---

### POST /api/option-grants

Create an option grant. Uses the **preview/confirm pattern**: first call with `confirm: false` to see impact, then call with `confirm: true` to commit.

**Auth:** Required. `owner`, `admin`, or `member` role.

**Enum values:**

- `optionType`: `iso`, `nqso`, `rsu`
- `status`: `proposed`, `pendingApproval`, `approved`, `active`, `fullyVested`, `exercised`, `partiallyExercised`, `cancelled`, `expired`
- `category`: `newHire`, `promotion`, `retention`, `performance`, `refresh`, `advisory`, `other`

#### Step 1: Preview (confirm=false)

```bash
curl -s -X POST https://api.invisible.app/api/option-grants \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003",
    "equityPlanId": "f6a7b8c9-0001-4000-8000-000000000001",
    "optionType": "iso",
    "sharesGranted": 50000,
    "exercisePrice": 0.50,
    "grantDate": "2024-03-01",
    "vestingStartDate": "2024-03-01",
    "expirationDate": "2034-03-01",
    "vestingScheduleId": "22222222-0001-4000-8000-000000000001",
    "status": "active",
    "category": "newHire",
    "grantNumber": "OPT-2024-001",
    "notes": "New hire grant for Senior Engineer",
    "confirm": false
  }'
```

**Preview response:**

```json
{
  "preview": {
    "grantDetail": {
      "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003",
      "equityPlanId": "f6a7b8c9-0001-4000-8000-000000000001",
      "optionType": "iso",
      "sharesGranted": 50000,
      "exercisePrice": 0.50,
      "grantDate": "2024-03-01",
      "vestingStartDate": "2024-03-01",
      "expirationDate": "2034-03-01",
      "vestingScheduleId": "22222222-0001-4000-8000-000000000001",
      "status": "active",
      "category": "newHire",
      "grantNumber": "OPT-2024-001",
      "notes": "New hire grant for Senior Engineer",
      "confirm": false
    },
    "poolImpact": {
      "availableBefore": 500000,
      "availableAfter": 450000,
      "pctPoolUsed": 55.0
    },
    "validationWarnings": []
  }
}
```

#### Step 2: Commit (confirm=true)

Same request body with `"confirm": true`.

```bash
curl -s -X POST https://api.invisible.app/api/option-grants \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003",
    "equityPlanId": "f6a7b8c9-0001-4000-8000-000000000001",
    "optionType": "iso",
    "sharesGranted": 50000,
    "exercisePrice": 0.50,
    "grantDate": "2024-03-01",
    "vestingStartDate": "2024-03-01",
    "expirationDate": "2034-03-01",
    "vestingScheduleId": "22222222-0001-4000-8000-000000000001",
    "status": "active",
    "category": "newHire",
    "grantNumber": "OPT-2024-001",
    "confirm": true
  }'
```

**Commit response:**

```json
{
  "grantId": "11111111-0001-4000-8000-000000000001",
  "preview": {
    "grantDetail": { "..." : "same as above" },
    "poolImpact": {
      "availableBefore": 500000,
      "availableAfter": 450000,
      "pctPoolUsed": 55.0
    },
    "validationWarnings": []
  }
}
```

---

### PUT /api/option-grants

Update an existing option grant. Only provided fields are modified.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X PUT https://api.invisible.app/api/option-grants \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "grantId": "11111111-0001-4000-8000-000000000001",
    "sharesGranted": 60000,
    "exercisePrice": 0.75,
    "category": "promotion",
    "notes": "Adjusted after promotion to Staff Engineer",
    "status": "active"
  }'
```

**Response:**

```json
{ "updated": true }
```

Returns `404` if grant not found.

---

### DELETE /api/option-grants/{id}

Delete an option grant.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X DELETE https://api.invisible.app/api/option-grants/11111111-0001-4000-8000-000000000001 \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{ "deleted": true }
```

Returns `404` if grant not found.

---

### POST /api/option-grants/exercise

Exercise vested options. Uses **preview/confirm pattern**.

**Auth:** Required. `owner`, `admin`, or `member` role.

#### Preview

```bash
curl -s -X POST https://api.invisible.app/api/option-grants/exercise \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "grantId": "11111111-0001-4000-8000-000000000001",
    "sharesToExercise": 10000,
    "exerciseDate": "2025-06-15",
    "notes": "Partial exercise",
    "confirm": false
  }'
```

**Preview response:**

```json
{
  "preview": {
    "grantId": "11111111-0001-4000-8000-000000000001",
    "sharesGranted": 50000,
    "sharesVested": 12500,
    "sharesPreviouslyExercised": 0,
    "sharesExercisable": 12500,
    "sharesToExercise": 10000,
    "sharesRemainingAfter": 2500,
    "newStatus": "partiallyExercised",
    "validationWarnings": []
  }
}
```

#### Commit

Same body with `"confirm": true`.

**Commit response:**

```json
{
  "exerciseId": "33333333-0001-4000-8000-000000000001",
  "preview": { "...": "same as above" }
}
```

---

### POST /api/grant-status

Batch-update the status of one or more grants. Uses **preview/confirm pattern**.

**Auth:** Required. `owner`, `admin`, or `member` role.

#### Preview

```bash
curl -s -X POST https://api.invisible.app/api/grant-status \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "grantIds": [
      "11111111-0001-4000-8000-000000000001",
      "11111111-0001-4000-8000-000000000002"
    ],
    "newStatus": "approved",
    "notes": "Board approved Q1 grants",
    "confirm": false
  }'
```

**Preview response:**

```json
{
  "grantsAffected": 2,
  "confirmRequired": true
}
```

#### Commit

```bash
curl -s -X POST https://api.invisible.app/api/grant-status \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "grantIds": [
      "11111111-0001-4000-8000-000000000001",
      "11111111-0001-4000-8000-000000000002"
    ],
    "newStatus": "approved",
    "notes": "Board approved Q1 grants",
    "confirm": true
  }'
```

**Commit response:**

```json
{
  "success": true,
  "grantsAffected": 2
}
```

---

### GET /api/vesting-schedules

List all vesting schedule templates.

**Auth:** Required.

```bash
curl -s https://api.invisible.app/api/vesting-schedules \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "schedules": [
    {
      "id": "22222222-0001-4000-8000-000000000001",
      "name": "Standard 4-year with 1-year cliff",
      "totalMonths": 48,
      "cliffMonths": 12,
      "frequency": "monthly",
      "createdAt": "2024-01-15T00:00:00Z"
    }
  ]
}
```

---

### POST /api/vesting-schedules

Create a new vesting schedule template.

**Auth:** Required. `owner`, `admin`, or `member` role.

**Enum values for `frequency`:** `monthly`, `quarterly`, `annually`

```bash
curl -s -X POST https://api.invisible.app/api/vesting-schedules \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "3-year quarterly with 6-month cliff",
    "totalMonths": 36,
    "cliffMonths": 6,
    "frequency": "quarterly"
  }'
```

**Response:**

```json
{ "vestingScheduleId": "22222222-0001-4000-8000-000000000002" }
```

---

## 4. Transactions

### GET /api/transactions

Transaction history with optional filters.

**Auth:** Required.

**Query params:**

| Param | Type | Description |
|-------|------|-------------|
| `stakeholderId` | uuid | Filter by stakeholder |
| `securityClassId` | uuid | Filter by security class |
| `type` | string | One of: `issuance`, `grant`, `exercise`, `transfer`, `cancellation`, `repurchase`, `conversion` |
| `startDate` | string | Start date (`YYYY-MM-DD`) |
| `endDate` | string | End date (`YYYY-MM-DD`) |

```bash
curl -s "https://api.invisible.app/api/transactions?type=issuance&startDate=2024-01-01&endDate=2024-12-31" \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "transactions": [
    {
      "id": "e5f6a7b8-0001-4000-8000-000000000001",
      "securityId": "CS-001",
      "type": "issuance",
      "shares": 4000000,
      "pricePerShare": 0.001,
      "date": "2024-01-15T00:00:00Z",
      "notes": "Founder shares"
    }
  ]
}
```

---

### POST /api/transactions

Record a transaction. Uses **preview/confirm pattern**.

**Auth:** Required. `owner`, `admin`, or `member` role.

**Enum values for `type`:** `issuance`, `grant`, `exercise`, `transfer`, `cancellation`, `repurchase`, `conversion`

#### Preview

```bash
curl -s -X POST https://api.invisible.app/api/transactions \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "securityId": "SA-001",
    "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000002",
    "securityClassId": "b2c3d4e5-0001-4000-8000-000000000002",
    "type": "issuance",
    "shares": 2000000,
    "pricePerShare": 1.50,
    "transactionDate": "2024-06-01",
    "notes": "Series A investment",
    "confirm": false
  }'
```

**Preview response:**

```json
{
  "preview": {
    "transactionDetail": {
      "securityId": "SA-001",
      "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000002",
      "securityClassId": "b2c3d4e5-0001-4000-8000-000000000002",
      "type": "issuance",
      "shares": 2000000,
      "pricePerShare": 1.50,
      "transactionDate": "2024-06-01",
      "notes": "Series A investment",
      "confirm": false
    },
    "validationWarnings": [],
    "impact": {
      "sharesBefore": 0,
      "sharesAfter": 2000000,
      "ownershipPctChange": 20.0
    }
  }
}
```

#### Commit

Same body with `"confirm": true`.

**Commit response:**

```json
{
  "transactionId": "e5f6a7b8-0001-4000-8000-000000000002",
  "preview": { "...": "same as above" }
}
```

---

## 5. Organization Management

### GET /api/me

Current user profile with all org memberships and linked stakeholders.

**Auth:** Required. No `X-Organization-Id` needed.

```bash
curl -s https://api.invisible.app/api/me \
  -H "Authorization: Bearer <jwt_token>"
```

**Response:**

```json
{
  "userId": "44444444-0001-4000-8000-000000000001",
  "email": "sarah@acme.com",
  "name": "Sarah Chen",
  "organizations": [
    {
      "organizationId": "d4e5f6a7-0001-4000-8000-000000000001",
      "organizationName": "Acme Inc",
      "role": "owner",
      "canViewFullCapTable": true
    }
  ],
  "linkedStakeholders": [
    {
      "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000001",
      "organizationId": "d4e5f6a7-0001-4000-8000-000000000001",
      "organizationName": "Acme Inc",
      "name": "Sarah Chen",
      "type": "individual"
    }
  ]
}
```

---

### POST /api/organizations

Create a new organization. The creating user becomes `owner`.

**Auth:** Required (JWT). No `X-Organization-Id` needed.

```bash
curl -s -X POST https://api.invisible.app/api/organizations \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Inc",
    "slug": "acme",
    "domain": "acme.com"
  }'
```

**Response:**

```json
{ "organizationId": "d4e5f6a7-0001-4000-8000-000000000001" }
```

---

### GET /api/organizations/{id}/members

List all members of an organization.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s https://api.invisible.app/api/organizations/d4e5f6a7-0001-4000-8000-000000000001/members \
  -H "Authorization: Bearer <jwt_token>" \
  -H "X-Organization-Id: d4e5f6a7-0001-4000-8000-000000000001"
```

**Response:**

```json
{
  "members": [
    {
      "id": "55555555-0001-4000-8000-000000000001",
      "organizationId": "d4e5f6a7-0001-4000-8000-000000000001",
      "userId": "44444444-0001-4000-8000-000000000001",
      "role": "owner",
      "canViewFullCapTable": true,
      "createdAt": "2024-01-15T00:00:00Z"
    }
  ]
}
```

---

### POST /api/organizations/{id}/members

Invite a user to the organization. If the user already has an account, they are added immediately. Otherwise, an invite code is generated.

**Auth:** Required. `owner` or `admin` role.

**Enum values for `role`:** `owner`, `admin`, `member`, `viewer`, `employee`

```bash
curl -s -X POST https://api.invisible.app/api/organizations/d4e5f6a7-0001-4000-8000-000000000001/members \
  -H "Authorization: Bearer <jwt_token>" \
  -H "X-Organization-Id: d4e5f6a7-0001-4000-8000-000000000001" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "james@acme.com",
    "role": "member",
    "expiresInDays": 7
  }'
```

**Response (user exists):**

```json
{
  "added": true,
  "message": "User james@acme.com added to organization as member"
}
```

**Response (user must sign up):**

```json
{
  "added": false,
  "inviteCode": "abc123def456",
  "message": "Invite created for james@acme.com (user must sign up first)"
}
```

---

### PUT /api/organizations/{id}/members/{userId}

Update a member's role or cap table visibility.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X PUT https://api.invisible.app/api/organizations/d4e5f6a7-0001-4000-8000-000000000001/members/44444444-0001-4000-8000-000000000002 \
  -H "Authorization: Bearer <jwt_token>" \
  -H "X-Organization-Id: d4e5f6a7-0001-4000-8000-000000000001" \
  -H "Content-Type: application/json" \
  -d '{
    "role": "admin",
    "canViewFullCapTable": true
  }'
```

**Response:**

```json
{ "updated": true }
```

---

### POST /api/org-invites/redeem

Redeem an organization invite code.

**Auth:** Required (JWT). No `X-Organization-Id` needed.

```bash
curl -s -X POST https://api.invisible.app/api/org-invites/redeem \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{ "inviteCode": "abc123def456" }'
```

**Response:**

```json
{ "redeemed": true }
```

---

### POST /api/organizations/{id}/invite

Create a stakeholder invite that links a user account to a stakeholder record (e.g., so an employee can view their own holdings).

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/organizations/d4e5f6a7-0001-4000-8000-000000000001/invite \
  -H "Authorization: Bearer <jwt_token>" \
  -H "X-Organization-Id: d4e5f6a7-0001-4000-8000-000000000001" \
  -H "Content-Type: application/json" \
  -d '{
    "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003",
    "email": "james@acme.com",
    "expiresInDays": 7
  }'
```

**Response:**

```json
{
  "inviteCode": "xyz789abc012",
  "expiresInDays": 7
}
```

---

### POST /api/invites/redeem

Redeem a stakeholder invite code (links the authenticated user to the stakeholder).

**Auth:** Required (JWT). No `X-Organization-Id` needed.

```bash
curl -s -X POST https://api.invisible.app/api/invites/redeem \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{ "inviteCode": "xyz789abc012" }'
```

**Response:**

```json
{ "redeemed": true }
```

---

## 6. Bulk Operations

All bulk endpoints require `owner`, `admin`, or `member` role.

### POST /api/bulk/stakeholders

Create multiple stakeholders in one call.

**Auth:** Required. `owner`, `admin`, or `member` role.

**Enum values for `type`:** `individual`, `entity`, `trust`, `fund`, `spv`

```bash
curl -s -X POST https://api.invisible.app/api/bulk/stakeholders \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stakeholders": [
      {
        "name": "James Park",
        "type": "individual",
        "email": "james@acme.com",
        "jobTitle": "Senior Engineer",
        "department": "Engineering",
        "employeeId": "EMP-003"
      },
      {
        "name": "Acme Ventures LLC",
        "type": "entity",
        "email": "legal@acmeventures.com"
      }
    ]
  }'
```

**Response:**

```json
{
  "results": [
    { "name": "James Park", "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003", "error": null },
    { "name": "Acme Ventures LLC", "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000004", "error": null }
  ],
  "created": 2,
  "failed": 0
}
```

---

### POST /api/bulk/transactions

Record multiple transactions. Each transaction is committed individually (no preview step). Failures do not roll back successes.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X POST https://api.invisible.app/api/bulk/transactions \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactions": [
      {
        "securityId": "CS-002",
        "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003",
        "securityClassId": "b2c3d4e5-0001-4000-8000-000000000001",
        "type": "issuance",
        "shares": 100000,
        "pricePerShare": 0.50,
        "transactionDate": "2024-03-01",
        "notes": "Early exercise of options",
        "confirm": true
      },
      {
        "securityId": "SA-002",
        "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000004",
        "securityClassId": "b2c3d4e5-0001-4000-8000-000000000002",
        "type": "issuance",
        "shares": 500000,
        "pricePerShare": 1.50,
        "transactionDate": "2024-06-01",
        "notes": "Series A participation",
        "confirm": true
      }
    ]
  }'
```

**Response:**

```json
{
  "results": [
    { "securityId": "CS-002", "transactionId": "e5f6a7b8-0001-4000-8000-000000000003", "error": null },
    { "securityId": "SA-002", "transactionId": "e5f6a7b8-0001-4000-8000-000000000004", "error": null }
  ],
  "created": 2,
  "failed": 0
}
```

---

### POST /api/bulk/option-grants

Create multiple option grants. Each grant is committed individually. Failures do not roll back successes.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X POST https://api.invisible.app/api/bulk/option-grants \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "grants": [
      {
        "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003",
        "equityPlanId": "f6a7b8c9-0001-4000-8000-000000000001",
        "optionType": "iso",
        "sharesGranted": 50000,
        "exercisePrice": 0.50,
        "grantDate": "2024-03-01",
        "vestingStartDate": "2024-03-01",
        "expirationDate": "2034-03-01",
        "vestingScheduleId": "22222222-0001-4000-8000-000000000001",
        "status": "active",
        "category": "newHire",
        "confirm": true
      },
      {
        "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000005",
        "equityPlanId": "f6a7b8c9-0001-4000-8000-000000000001",
        "optionType": "nqso",
        "sharesGranted": 25000,
        "exercisePrice": 0.50,
        "grantDate": "2024-04-01",
        "vestingStartDate": "2024-04-01",
        "expirationDate": "2034-04-01",
        "status": "active",
        "category": "advisory",
        "confirm": true
      }
    ]
  }'
```

**Response:**

```json
{
  "results": [
    { "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003", "grantId": "11111111-0001-4000-8000-000000000001", "error": null },
    { "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000005", "grantId": "11111111-0001-4000-8000-000000000002", "error": null }
  ],
  "created": 2,
  "failed": 0
}
```

---

## 7. Additional Write Endpoints

### POST /api/stakeholders

Create a single stakeholder.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X POST https://api.invisible.app/api/stakeholders \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "James Park",
    "type": "individual",
    "email": "james@acme.com",
    "jobTitle": "Senior Engineer",
    "department": "Engineering",
    "employeeId": "EMP-003",
    "managerId": "a1b2c3d4-0001-4000-8000-000000000001"
  }'
```

**Response:**

```json
{ "stakeholderId": "a1b2c3d4-0001-4000-8000-000000000003" }
```

---

### POST /api/security-classes

Create a new security class.

**Auth:** Required. `owner`, `admin`, or `member` role.

**Enum values for `type`:** `common`, `preferred`, `option`, `warrant`, `safe`, `convertibleNote`

```bash
curl -s -X POST https://api.invisible.app/api/security-classes \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Series B Preferred",
    "type": "preferred",
    "originalIssuePrice": 5.00,
    "liquidationPreference": 1.0,
    "conversionRatio": 1.0,
    "isParticipating": false,
    "seniorityRank": 2,
    "authorizedShares": 5000000
  }'
```

**Response:**

```json
{ "securityClassId": "b2c3d4e5-0001-4000-8000-000000000003" }
```

---

### PUT /api/security-classes

Update a security class.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X PUT https://api.invisible.app/api/security-classes \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "securityClassId": "b2c3d4e5-0001-4000-8000-000000000002",
    "authorizedShares": 4000000,
    "conversionRatio": 1.2,
    "notes": "Updated after anti-dilution adjustment"
  }'
```

**Response:**

```json
{ "updated": true }
```

---

### POST /api/equity-plans

Create a new equity incentive plan.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X POST https://api.invisible.app/api/equity-plans \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "2025 Equity Incentive Plan",
    "year": 2025,
    "authorizedShares": 1500000
  }'
```

**Response:**

```json
{ "equityPlanId": "f6a7b8c9-0001-4000-8000-000000000002" }
```

---

### PUT /api/equity-plans

Update an equity plan.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X PUT https://api.invisible.app/api/equity-plans \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "equityPlanId": "f6a7b8c9-0001-4000-8000-000000000001",
    "authorizedShares": 1200000
  }'
```

**Response:**

```json
{ "updated": true }
```

---

### POST /api/investor-groups

Create or update an investor group.

**Auth:** Required. `owner`, `admin`, or `member` role.

```bash
curl -s -X POST https://api.invisible.app/api/investor-groups \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Series A Investors",
    "stakeholderIds": [
      "a1b2c3d4-0001-4000-8000-000000000002",
      "a1b2c3d4-0001-4000-8000-000000000004"
    ]
  }'
```

To update an existing group, include `groupId`:

```bash
curl -s -X POST https://api.invisible.app/api/investor-groups \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "groupId": "c3d4e5f6-0001-4000-8000-000000000001",
    "name": "Series A Investors",
    "stakeholderIds": [
      "a1b2c3d4-0001-4000-8000-000000000002",
      "a1b2c3d4-0001-4000-8000-000000000004",
      "a1b2c3d4-0001-4000-8000-000000000006"
    ]
  }'
```

**Response:**

```json
{ "groupId": "c3d4e5f6-0001-4000-8000-000000000001" }
```

---

## 8. MCP Integration

The Invisible Cap Table exposes an MCP (Model Context Protocol) server for AI assistant integrations.

**Endpoint:** `https://mcp.invisible.app/mcp`

**Transport:** Streamable HTTP (stateless)

**Auth:** OAuth 2.0 with PKCE. The authorization server metadata is at `https://api.invisible.app/.well-known/oauth-authorization-server`.

MCP clients (Claude Desktop, Cursor, etc.) can discover the server and authenticate automatically using the standard MCP OAuth flow. The server exposes the same cap table read/write operations as the REST API as MCP tools.

---

## 9. 409A Valuations

### POST /api/409a/start

Start a new 409A valuation. Creates a draft report with initial inputs.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/409a/start \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "valuationDate": "2026-03-31",
    "companyDescription": "Series B SaaS company, $8M ARR, 45% YoY growth",
    "industry": "Enterprise Software / SaaS",
    "financials": {
      "revenue": 8000000,
      "revenueGrowthRate": 0.45,
      "ebitda": -1200000,
      "cashBalance": 12000000,
      "totalDebt": 0,
      "projectedRevenue": [12000000, 18000000, 26000000, 35000000, 42000000]
    },
    "dcfAssumptions": {
      "discountRate": 0.30,
      "terminalGrowthRate": 0.03,
      "projectionYears": 5
    },
    "comparableCompanies": [
      { "name": "Comparable A", "evRevenue": 12.5, "evEbitda": null },
      { "name": "Comparable B", "evRevenue": 9.8, "evEbitda": 45.0 },
      { "name": "Comparable C", "evRevenue": 15.2, "evEbitda": null }
    ],
    "methodologyWeights": {
      "dcf": 0.30,
      "marketComps": 0.40,
      "opmBacksolve": 0.30
    }
  }'
```

**Response:**

```json
{ "reportId": "99999999-0001-4000-8000-000000000001" }
```

---

### GET /api/409a/reports

List all 409A valuation reports for the organization.

**Auth:** Required.

```bash
curl -s https://api.invisible.app/api/409a/reports \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "reports": [
    {
      "id": "99999999-0001-4000-8000-000000000001",
      "valuationDate": "2026-03-31",
      "status": "draft",
      "fairMarketValue": null,
      "reviewerUserId": null,
      "createdAt": "2026-03-31T14:00:00Z",
      "finalizedAt": null
    }
  ]
}
```

---

### GET /api/409a/reports/{id}

Full report detail including inputs, calculated values, equity allocation, and review status.

**Auth:** Required.

```bash
curl -s https://api.invisible.app/api/409a/reports/99999999-0001-4000-8000-000000000001 \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "id": "99999999-0001-4000-8000-000000000001",
  "valuationDate": "2026-03-31",
  "status": "computed",
  "inputs": {
    "companyDescription": "Series B SaaS company, $8M ARR, 45% YoY growth",
    "industry": "Enterprise Software / SaaS",
    "financials": { "revenue": 8000000, "revenueGrowthRate": 0.45, "ebitda": -1200000 },
    "dcfAssumptions": { "discountRate": 0.30, "terminalGrowthRate": 0.03, "projectionYears": 5 },
    "comparableCompanies": [ "..." ],
    "methodologyWeights": { "dcf": 0.30, "marketComps": 0.40, "opmBacksolve": 0.30 }
  },
  "calculations": {
    "dcfValue": 85000000,
    "marketCompsValue": 96000000,
    "opmBacksolveValue": 91000000,
    "weightedEnterpriseValue": 91500000,
    "dlomDiscount": 0.25,
    "fairMarketValuePerShare": 1.85
  },
  "allocation": {
    "enterpriseValue": 91500000,
    "commonEquityValue": 18500000,
    "commonSharesOutstanding": 10000000,
    "preDlomPerShare": 1.85,
    "dlom": 0.25,
    "postDlomPerShare": 1.39
  },
  "reviewerUserId": null,
  "reviewNotes": null,
  "reviewApproved": null,
  "createdAt": "2026-03-31T14:00:00Z",
  "finalizedAt": null
}
```

---

### PUT /api/409a/reports/{id}/inputs

Update the inputs on a draft or computed report (financials, comparable companies, assumptions). Cannot update a finalized report.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X PUT https://api.invisible.app/api/409a/reports/99999999-0001-4000-8000-000000000001/inputs \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "financials": {
      "revenue": 8500000,
      "revenueGrowthRate": 0.48,
      "ebitda": -800000,
      "cashBalance": 11000000,
      "totalDebt": 0,
      "projectedRevenue": [13000000, 19500000, 28000000, 37000000, 44000000]
    },
    "comparableCompanies": [
      { "name": "Comparable A", "evRevenue": 13.0, "evEbitda": null },
      { "name": "Comparable B", "evRevenue": 10.2, "evEbitda": 42.0 },
      { "name": "Comparable C", "evRevenue": 14.8, "evEbitda": null },
      { "name": "Comparable D", "evRevenue": 11.5, "evEbitda": 50.0 }
    ]
  }'
```

**Response:**

```json
{ "updated": true }
```

Returns `400` if the report is finalized.

---

### POST /api/409a/reports/{id}/compute

Run the full valuation computation: DCF, Market Comps, OPM Backsolve, and DLOM calculation. Updates the report with calculated values.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/409a/reports/99999999-0001-4000-8000-000000000001/compute \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "computed": true,
  "fairMarketValuePerShare": 1.39,
  "enterpriseValue": 91500000,
  "methodResults": {
    "dcf": 85000000,
    "marketComps": 96000000,
    "opmBacksolve": 91000000
  },
  "dlomDiscount": 0.25
}
```

---

### POST /api/409a/reports/{id}/assign-reviewer

Assign a reviewer to a 409A report. The reviewer must be a member of the organization.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/409a/reports/99999999-0001-4000-8000-000000000001/assign-reviewer \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reviewerUserId": "44444444-0001-4000-8000-000000000002"
  }'
```

**Response:**

```json
{ "assigned": true }
```

---

### POST /api/409a/reports/{id}/review

Submit a review for a 409A report. Only the assigned reviewer can call this endpoint.

**Auth:** Required. Must be the assigned reviewer.

```bash
curl -s -X POST https://api.invisible.app/api/409a/reports/99999999-0001-4000-8000-000000000001/review \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notes": "Methodology and assumptions look reasonable. Comparable set is appropriate for this stage and industry. DLOM within expected range.",
    "approve": true
  }'
```

**Response:**

```json
{ "reviewed": true, "approved": true }
```

Returns `403` if the caller is not the assigned reviewer.

---

### POST /api/409a/reports/{id}/finalize

Lock the report as final. Once finalized, no further edits are allowed. The fair market value is recorded for use in option pricing.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/409a/reports/99999999-0001-4000-8000-000000000001/finalize \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "finalized": true,
  "fairMarketValuePerShare": 1.39,
  "valuationDate": "2026-03-31"
}
```

Returns `400` if the report has not been computed yet.

---

## 10. SBC Expensing

### POST /api/sbc/valuations

Record a 409A valuation with Black-Scholes assumptions for SBC expense calculations.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/sbc/valuations \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "valuationDate": "2026-03-31",
    "fairMarketValue": 1.39,
    "expectedTermYears": 6.25,
    "riskFreeRate": 0.042,
    "volatility": 0.55,
    "dividendYield": 0.0,
    "notes": "Based on Q1 2026 409A valuation, volatility from public SaaS peer group"
  }'
```

**Response:**

```json
{ "sbcValuationId": "aaaaaaaa-0001-4000-8000-000000000001" }
```

---

### GET /api/sbc/valuations

List all SBC valuations (409A snapshots with Black-Scholes assumptions).

**Auth:** Required.

```bash
curl -s https://api.invisible.app/api/sbc/valuations \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "valuations": [
    {
      "id": "aaaaaaaa-0001-4000-8000-000000000001",
      "valuationDate": "2026-03-31",
      "fairMarketValue": 1.39,
      "expectedTermYears": 6.25,
      "riskFreeRate": 0.042,
      "volatility": 0.55,
      "dividendYield": 0.0,
      "notes": "Based on Q1 2026 409A valuation",
      "createdAt": "2026-03-31T15:00:00Z"
    }
  ]
}
```

---

### POST /api/sbc/compute

Compute Black-Scholes fair values and expense schedules for all active option grants, using the most recent SBC valuation assumptions.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/sbc/compute \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "computed": true,
  "grantsProcessed": 12,
  "totalFairValue": 485000.00,
  "totalUnamortizedExpense": 312000.00,
  "summary": {
    "iso": { "grants": 8, "fairValue": 350000.00 },
    "nqso": { "grants": 4, "fairValue": 135000.00 }
  }
}
```

---

### GET /api/sbc/expense-schedule

Monthly SBC expense schedule with optional filters.

**Auth:** Required.

**Query params:**

| Param | Type | Description |
|-------|------|-------------|
| `startDate` | string | Start date (`YYYY-MM-DD`) |
| `endDate` | string | End date (`YYYY-MM-DD`) |
| `department` | string | Filter by department |
| `grantId` | uuid | Filter by specific grant |

```bash
curl -s "https://api.invisible.app/api/sbc/expense-schedule?startDate=2026-01-01&endDate=2026-12-31&department=Engineering" \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "schedule": [
    {
      "month": "2026-01",
      "totalExpense": 12500.00,
      "byDepartment": {
        "Engineering": 8200.00,
        "Product": 2800.00,
        "Sales": 1500.00
      },
      "byGrantType": {
        "iso": 9000.00,
        "nqso": 3500.00
      }
    },
    {
      "month": "2026-02",
      "totalExpense": 13100.00,
      "byDepartment": {
        "Engineering": 8600.00,
        "Product": 2900.00,
        "Sales": 1600.00
      },
      "byGrantType": {
        "iso": 9400.00,
        "nqso": 3700.00
      }
    }
  ],
  "totalForPeriod": 155000.00
}
```

---

### GET /api/sbc/disclosure

ASC 718 disclosure data for a fiscal year. Returns the information needed for stock-based compensation footnotes in financial statements.

**Auth:** Required.

**Query params:**

| Param | Type | Description |
|-------|------|-------------|
| `fiscalYearStart` | string | Fiscal year start date (`YYYY-MM-DD`) |
| `fiscalYearEnd` | string | Fiscal year end date (`YYYY-MM-DD`) |

```bash
curl -s "https://api.invisible.app/api/sbc/disclosure?fiscalYearStart=2025-01-01&fiscalYearEnd=2025-12-31" \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "fiscalYear": { "start": "2025-01-01", "end": "2025-12-31" },
  "totalSbcExpense": 185000.00,
  "grantActivity": {
    "outstandingBeginning": { "shares": 450000, "weightedAvgExercisePrice": 0.65 },
    "granted": { "shares": 120000, "weightedAvgExercisePrice": 1.39 },
    "exercised": { "shares": 30000, "weightedAvgExercisePrice": 0.50 },
    "forfeited": { "shares": 15000, "weightedAvgExercisePrice": 0.75 },
    "outstandingEnd": { "shares": 525000, "weightedAvgExercisePrice": 0.82 },
    "exercisableEnd": { "shares": 210000, "weightedAvgExercisePrice": 0.60 }
  },
  "weightedAvgAssumptions": {
    "expectedTermYears": 6.25,
    "riskFreeRate": 0.042,
    "volatility": 0.55,
    "dividendYield": 0.0
  },
  "weightedAvgGrantDateFairValue": 0.92,
  "unamortizedExpense": 312000.00,
  "weightedAvgRemainingVestingYears": 2.8
}
```

---

## Visibility & Org Hierarchy

These endpoints manage manager relationships and department-based visibility. All require `owner` or `admin` role.

### PUT /api/stakeholders/{id}/manager

Set or clear a stakeholder's reporting manager.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X PUT https://api.invisible.app/api/stakeholders/a1b2c3d4-0001-4000-8000-000000000003/manager \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "managerId": "a1b2c3d4-0001-4000-8000-000000000001" }'
```

Pass `"managerId": null` to clear the manager. Returns `400` if the assignment would create a circular reference.

**Response:**

```json
{ "updated": true }
```

---

### POST /api/stakeholders/bulk-set-managers

Set manager relationships for multiple stakeholders at once.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/stakeholders/bulk-set-managers \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "assignments": [
      { "stakeholderId": "...", "managerId": "..." },
      { "stakeholderId": "...", "managerId": "..." }
    ]
  }'
```

**Response:**

```json
{
  "results": [
    { "stakeholderId": "...", "success": true, "error": null },
    { "stakeholderId": "...", "success": false, "error": "Would create circular reference" }
  ],
  "updated": 1,
  "failed": 1
}
```

---

### POST /api/department-access

Grant a user view access to all equity data in specified departments.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/department-access \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "c3d4e5f6-0001-4000-8000-000000000001",
    "departments": ["Sales", "Marketing"]
  }'
```

**Response:**

```json
{ "granted": 2 }
```

---

### POST /api/department-access/revoke

Revoke department-based view access.

**Auth:** Required. `owner` or `admin` role.

```bash
curl -s -X POST https://api.invisible.app/api/department-access/revoke \
  -H "Authorization: Bearer icap_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "c3d4e5f6-0001-4000-8000-000000000001",
    "departments": ["Marketing"]
  }'
```

**Response:**

```json
{ "revoked": 1 }
```

---

### GET /api/department-access

List all department access grants.

**Auth:** Required. `owner` or `admin` role.

**Query params:**

| Param | Type | Description |
|-------|------|-------------|
| `userId` | uuid | Filter to a specific user |

```bash
curl -s "https://api.invisible.app/api/department-access?userId=c3d4e5f6-0001-4000-8000-000000000001" \
  -H "Authorization: Bearer icap_YOUR_KEY"
```

**Response:**

```json
{
  "departmentAccess": [
    {
      "id": "...",
      "userId": "c3d4e5f6-0001-4000-8000-000000000001",
      "department": "Sales",
      "grantedBy": "...",
      "createdAt": "2026-04-06T00:00:00Z"
    }
  ]
}
```

---

## Error Responses

All errors follow this format:

```json
{ "error": "Description of what went wrong" }
```

| Status | Meaning |
|--------|---------|
| 400 | Bad request (missing fields, invalid data) |
| 401 | Missing/invalid auth token |
| 403 | Insufficient permissions for this action |
| 404 | Resource not found |
