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}.
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.
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.
{
"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.
{
"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.
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.
3. Estimation by categories
Set estimation_by_categories: 1 (or true) and provide a categories array with id and estimated_time.
{
"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:
{
"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 |