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

# Project Profitability & Time Estimation

> Configure how a project estimates time — total hours, by positions, or by categories — and understand income types and ratecards.

# Project Profitability & Time Estimation

When creating or updating a project, the **Profitability** section controls how time and income are estimated. These settings are sent as part of `POST /projects` and `PUT /projects/{project_id}`.

<Warning>
  These fields are **unrelated** to the **Project Estimates** endpoints (`/projects/{project_id}/project_estimate`), which manage budget line items with a title, cost, and optional fee association.
</Warning>

## Time estimation modes

Projects support three mutually exclusive time estimation modes. Only one should be active at a time.

### 1. Total hours (default)

Send a single `estimated_time` value (in hours). Do not set `estimated_by_position` or `estimation_by_categories`.

```json theme={null}
{
  "name": "Website Redesign",
  "client_id": 10,
  "estimated_time": 120,
  "income_type": "fee",
  "fee_id": 5
}
```

### 2. Estimation by positions

Set `estimated_by_position: true` and provide a `users_positions` array where each entry contains a unified position `id` and its `estimated_time` in hours.

```json theme={null}
{
  "name": "Mobile App",
  "client_id": 10,
  "estimated_by_position": true,
  "users_positions": [
    { "id": 1, "estimated_time": 80 },
    { "id": 2, "estimated_time": 40 }
  ]
}
```

When `estimated_by_position` is `false`, you may still send `users_positions` with only the `id` field to associate positions to the project without per-position hour estimates.

<Note>
  On update (`PUT`), positions that exist in the project but are **absent** from the `users_positions` array will have their `estimated_hours` set to 0. If no hours have been logged against them, they are soft-deleted.
</Note>

### 3. Estimation by categories

Set `estimation_by_categories: 1` (or `true`) and provide a `categories` array with `id` and `estimated_time`.

```json theme={null}
{
  "name": "Brand Campaign",
  "client_id": 10,
  "estimation_by_categories": 1,
  "categories": [
    { "id": 7, "estimated_time": 60 },
    { "id": 8, "estimated_time": 30 }
  ]
}
```

## Income types and ratecards

The `income_type` field determines how the project revenue is modeled:

| `income_type` | Description                                         |
| ------------- | --------------------------------------------------- |
| `fee`         | Revenue tied to a fee (`fee_id` required)           |
| `one_time`    | Fixed one-time revenue (`estimated` monetary value) |
| `hourly_rate` | Revenue calculated from hourly rates via a ratecard |
| `contract`    | Revenue tied to a contract (`contract_id` required) |

When using `hourly_rate`, set `estimated_by_hourly_rates: true` and assign a ratecard:

```json theme={null}
{
  "income_type": "hourly_rate",
  "estimated_by_hourly_rates": true,
  "ratecard_id": 3
}
```

The `ratecard_id` field is an alias for `user_positions_header_id` — either name is accepted, but `ratecard_id` takes precedence if both are sent. Use `GET /ratecards` to list available ratecards for your company.

## Reading profitability data

When you fetch a single project with `GET /projects/{project_id}`, the response includes:

| Field                       | Type            | Description                                                      |
| --------------------------- | --------------- | ---------------------------------------------------------------- |
| `estimated_time`            | number          | Total estimated hours                                            |
| `estimated_by_position`     | boolean         | Whether estimation is split per position                         |
| `estimation_by_categories`  | boolean/integer | Whether estimation is split per category                         |
| `estimated_by_hourly_rates` | boolean         | Whether hourly-rate income mode is active                        |
| `user_positions_header_id`  | integer         | Assigned ratecard ID                                             |
| `usersPositionsProject`     | array           | Positions with `estimated_hours`, `hours_charged`, `total_cost`  |
| `categoriesProject`         | array           | Categories with `estimated_hours`, `hours_charged`, `total_cost` |
| `ratecard`                  | object          | Ratecard details (`name`, `percent`, `currency_id`, etc.)        |

## Field reference

| Request field               | Type            | Used in   | Description                                                                     |
| --------------------------- | --------------- | --------- | ------------------------------------------------------------------------------- |
| `estimated_time`            | number          | POST, PUT | Total estimated hours (used when neither by-position nor by-category is active) |
| `estimated_by_position`     | boolean         | POST, PUT | Enable per-position time estimation                                             |
| `estimation_by_categories`  | boolean/integer | POST, PUT | Enable per-category time estimation (`1` or `true`)                             |
| `estimated_by_hourly_rates` | boolean         | POST, PUT | Enable hourly-rate income calculation                                           |
| `ratecard_id`               | integer         | POST, PUT | Ratecard to assign (alias for `user_positions_header_id`)                       |
| `users_positions`           | array           | POST, PUT | Positions with optional `estimated_time` per item                               |
| `categories`                | array           | POST, PUT | Categories with optional `estimated_time` per item                              |
| `income_type`               | string          | POST, PUT | `fee`, `one_time`, `hourly_rate`, or `contract`                                 |
| `exchange`                  | number          | POST, PUT | Exchange rate when project currency differs from base                           |
| `currency_change_amount`    | number          | POST, PUT | Original amount in selected currency before conversion                          |
