# SPECIFICATIONS/06_API/06_10_INVESTORS_ENDPOINTS.md

**PURPOSE:** Specifies the investor management API surface.

> **Business rules enforced by these endpoints:** `../01_BUSINESS_CONTEXT/03_BUSINESS_RULES.md` → BR-001-6, BR-001-8, BR-005-*
> **Computed field formulas:** `../05_ALGORITHMS/04_COMPUTED_FIELDS.md` → Sections 5.1, 5.2, 5.7
> **Data minimization rules:** `../../CONSTITUTIONS/07_BOUNDARY_LAYERS.md`
> **Shares operations:** `06_09_SHARES_ENDPOINTS.md`

**Investor = Client with investor flag.** The Investor domain handles flag assignment and read operations. Shares management is a separate domain — see `06_09_SHARES_ENDPOINTS.md`.

---

## GET /api/v1/investors

**Permission:** `investors.view`

**Inputs (Query Params):**

| Field | Type | Required | Validation |
|:---|:---|:---|:---|
| `search` | string | No | max 150 chars |
| `cursor` | string | No | Valid cursor |
| `per_page` | integer | No | Default 20, max 100 |

**Outputs:**
```json
{
    "success": true,
    "message": "string",
    "data": [
        {
            "id": 1,
            "name": "string",
            "phone": "string",
            "avatar_url": "string|null",
            "shares_balance": 10
        }
    ],
    "meta": {
        "current_cursor": "string|null",
        "has_more": false,
        "per_page": 20
    }
}
```

**Behavior:**
- Filters clients where `investor` flag is present
- Search by name and phone (partial match)
- Sorted alphabetically by name
- `shares_balance`: computed sum of active shares (add positive, withdraw negative, exclude modified/deleted)

---

## POST /api/v1/investors

**Permission:** `investors.create`

**Inputs:**

| Field | Type | Required | Validation |
|:---|:---|:---|:---|
| `client_id` | integer | No | exists in clients, must NOT have `investor` flag |
| `name` | string | Conditional* | max 150 |
| `phone` | string | Conditional* | unique, max 20 |
| `description` | text | No | nullable |
| `avatar` | file | No | image, jpg/jpeg/png/webp, max 5MB |

*Required only when `client_id` is not provided.

**Outputs:**
```json
{
    "success": true,
    "message": "string",
    "data": {
        "id": 1,
        "name": "string",
        "phone": "string",
        "description": "string|null",
        "avatar_url": "string|null"
    }
}
```

**Behavior:**

> **Full investor creation rules (client conversion, new client, flag permanence):** `../01_BUSINESS_CONTEXT/03_BUSINESS_RULES.md` → BR-001-8

1. **With `client_id`:**
   - Reject if the client already has the `investor` flag
   - Update provided fields (name, phone, description, avatar)
   - Do NOT modify existing flags or reference number
2. **Without `client_id`:**
   - Create a new client with the provided data
   - Assign `investor` flag immediately
   - Generate reference number automatically
3. **Shares are NOT handled here** — shares are added independently via `POST /api/v1/clients/{id}/shares`
4. **Flag Permanence:** Once assigned, the `investor` flag is NEVER removed, even if no shares are ever added or all shares are withdrawn later

**Errors:**

| Condition | Key | HTTP |
|:---|:---|:---|
| Already an investor | `errors/investor.already_investor` | 422 |
| Duplicate phone | `errors/investor.phone_unique` | 422 |

---

## GET /api/v1/investors/{id}

**Permission:** `investors.view`

**Inputs:** None (route param: `id`)

**Outputs:**
```json
{
    "success": true,
    "message": "string",
    "data": {
        "id": 1,
        "name": "string",
        "phone": "string",
        "avatar_url": "string|null",
        "shares_balance": 10,
        "investor_profit": "500.00",
        "referred_customers": [
            {
                "id": 5,
                "name": "string",
                "avatar_url": "string|null",
                "description": "string|null",
                "contract_margin": "250.00",
                "investor_profit": "12.50"
            }
        ]
    }
}
```

**Behavior:**
- Guard: `investor` flag → 404 if not
- `shares_balance`: current active shares balance (0.00 if no shares added)
- `investor_profit`: 5% of total contract margin from contracts where this person is `agent_id` AND contract is active/completed
- `referred_customers`: customers who have contracts where this person is the `agent_id`. Each entry includes:
  - `contract_margin`: total contract margin (`total_after_profit - purchase_amount`) from that customer's active/completed contracts via this investor
  - `investor_profit`: 5% of that contract margin
- If no contracts exist where this person is the `agent_id`: `investor_profit` is "0.00" and `referred_customers` is an empty array `[]`

---

## PUT /api/v1/investors/{id}

**Permission:** `investors.update`

**Inputs:**

| Field | Type | Required | Validation |
|:---|:---|:---|:---|
| `name` | string | No | — |
| `phone` | string | No | unique (ignore current) |
| `description` | text | No | nullable |
| `avatar` | file | No | image, jpg/jpeg/png/webp, max 5MB |

**Outputs:** Same shape as POST create response

**Behavior:**
- If new avatar provided: old deleted securely, new stored
- Never modify `client_type_flags`, `reference_number`, or shares
- At least one field must be provided
- Shares operations use dedicated endpoints in `06_09_SHARES_ENDPOINTS.md`

---

## DELETE /api/v1/investors/{id}

**This endpoint does not exist.**

Investors are hard immutable entities (BR-001-1).