Appearance
Project
Allowed query parameters
Filters
| Filter | Type | Description |
|---|---|---|
search | String | Full-text search |
own | Boolean | Show only own projects (as project leader) |
involved | Boolean | Show only projects where user is involved |
assigned | Boolean | Show only projects assigned to the current user |
contact | Id | Filter by Contact Id |
is_archived | Boolean | Show archived projects (completed) |
is_internal | Boolean | Show only projects without billing |
with_billable_costs | Boolean | Show only top-level projects with something left to invoice, including their sub-projects' work |
company | Id | Filter by Company Id |
billing_type | String | Filter by billing type (flat, hourly) |
status | String | Filter by status (active, completed) |
custom_field | Key,String | Filter by Custom Field (eg. my_internal_id,42) |
Sorting
name, created_at, starts_at, company, budget_used, billable_costs, last_time_tracked
Includes
company.media, owner.user, times.rate, unbilledTimes.rate, offers, approvedOffers, expenses, invoices, rates, timesCount, offersCount, invoicesCount, expensesCount, overdueInvoicesCount, assignedMembers.user.media
offers, approvedOffers and offersCount are only returned to users with the show offer permission.
Appends
(Available append parameters for additional computed fields)
billing, budget, budget_total, budget_grouped, offered_rates, involved_members, costs_total, costs_limit_exceeded, billable_costs, billable_costs_breakdown, billable_total, billed_total, draft_invoices_total, turnover_total, sent_invoices_total, profit_total, expected_revenue_total, expected_profit_total, billing_waived_total, internal_costs_total, internal_costs_breakdown, return_on_investment_total
Budget and financial figures are only returned to users who can see the project's budget. billing returns billable_costs, billable_total, billed_total, draft_invoices_total, turnover_total, sent_invoices_total, profit_total, expected_revenue_total, expected_profit_total and billing_waived_total at once, and only to users with the supervise invoice permission and to the project leader.
Financial figures
All amounts are in the organization's currency and rounded to cents. Invoices and offers count net. Sub-projects hold no invoices or offers; their billable work and internal costs are included.
| Field | Type | Description |
|---|---|---|
billable_costs | Number | Work not invoiced yet: accountable hours at their rate's price, tracked products and billable expenses, including sub-projects |
billable_costs_breakdown | Object | What billable_costs is made of, see below |
billable_total | Number | Amount left to invoice. Flat-rate projects: approved offers minus all invoices, drafts included, never below 0, and 0 once the billing is completed. Hourly projects: equal to billable_costs. Sub-projects of a flat-rate project: 0, the parent's offers cover their work |
billed_total | Number | Unpaid invoices, drafts included |
draft_invoices_total | Number | Draft invoices (neither sent nor paid). Part of billed_total |
turnover_total | Number | Paid invoices |
sent_invoices_total | Number | Sent and paid invoices, including invoices scheduled for sending: billed_total − draft_invoices_total + turnover_total |
profit_total | Number | Realised result: sent_invoices_total minus internal_costs_total |
expected_revenue_total | Number | What the project brings in once everything recorded is invoiced: billable_total + draft_invoices_total + sent_invoices_total. Flat-rate projects: the approved offers, or more if more has been invoiced |
expected_profit_total | Number | expected_revenue_total minus internal_costs_total. Counts work not invoiced yet, but only the internal costs recorded so far. The result shown on the project's finances card |
internal_costs_total | Number | Sum of internal_costs_breakdown |
internal_costs_breakdown | Object | times: tracked hours at the internal hourly rate, products: tracked products at their purchase price (sales price if none), expenses: all expenses at their gross total. Including sub-projects |
billing_waived_total | Number | Flat-rate projects with completed billing: the approved offers minus all invoices, left uninvoiced. 0 otherwise |
return_on_investment_total | Number? | Sent and paid invoices relative to internal_costs_total in percent, e.g. 50 for invoices of 1.5 times the internal costs. null without internal costs or sent invoices |
billable_costs_breakdown lists unbilled hours grouped by rate (largest first), tracked products, billable expenses (net if your organization is liable for VAT, gross otherwise), each sub-project with billable costs, and the date of the oldest unbilled time entry or expense:
json
{
"billable_costs_breakdown": {
"rates": [
{ "name": "Development", "price": 150.0, "hours": 3.0, "total": 450.0 },
{ "name": "Junior", "price": 100.0, "hours": 1.5, "total": 150.0 }
],
"products": 80.0,
"expenses": 100.0,
"children": [
{ "id": "78gj0E32bW", "name": "Phase 2", "total": 150.0 }
],
"oldest_unbilled_at": "2026-09-29"
},
"internal_costs_breakdown": {
"times": 240.0,
"products": 90.0,
"expenses": 107.7
}
}Create a project
post
/project
| Attribute (* required) | Type | Description |
|---|---|---|
name * | String | Project title |
description | String | Project description |
company_id * | String | An existing Company Id of your organization |
member_id * | String | A Member Id |
starts_at * | Date | Start date |
billing_type | String | Billing type (flat, hourly) |
costs_limit | Number | Cost limit for the project (minimum: 0). Required with parent_id |
assigned_member_ids | Array | Array of Member IDs assigned to the project |
status | String | Status of project (active, completed, default: active) |
custom_fields | Object? | Custom data as Custom Fields |
parent_id | String? | Creates the project as a sub-project of this Project Id. See Sub-projects |
Example response
json
// HTTP 201 Created
{
// a project object
}Update a project
put
/project/{id}
| Attribute (* required) | Type | Description |
|---|---|---|
name | String | Project title |
description | String | Project description |
company_id | String | An existing Company Id of your organization (the project's current company is always accepted) |
member_id | String | A Member Id |
starts_at | Date | Start date |
billing_type | String | Billing type (flat, hourly) |
costs_limit | Number | Cost limit for the project (minimum: 0). Required with parent_id |
assigned_member_ids | Array | Array of Member IDs assigned to the project |
status | String | Status of project (active, completed) |
custom_fields | Object? | Custom data as Custom Fields |
parent_id | String? | Moves the project below this Project Id, or makes it a top-level project with null. The project's current parent is always accepted. See Sub-projects |
A sub-project that keeps its parent cannot switch to flat billing or change is_internal (422).
Example response
json
// HTTP 200 OK
{
// a project object
}Sub-projects
Projects have one level of sub-projects. When parent_id is set or changed, the request returns 422 unless:
- the parent is a top-level project of your organization, and not the project itself,
- the project has no sub-projects of its own, including deleted ones,
- the project belongs to the parent's company (
company_id), - the project is billed
hourly, - the project has the same
is_internalas the parent. - the project has no invoices or offers, including deleted ones.
Sub-projects only hold time entries and expenses. They are billed through their parent, which includes their work: an invoice or offer created or updated with a sub-project's project_id is assigned to its parent.
Retrieve a project
get
/project/{id}
Example response
json
// HTTP 200 OK
{
"id": "EO6YGvqMRV",
"name": "CMS Upgrade",
"description": null,
"starts_at": "2022-02-09T08:52:00.000000Z",
"due_at": null,
"billing_type": "flat",
"company_id": "PQYLdqm4wR",
"company": {
// a company object
},
"owner": {
// a member object
},
"involved_members": [
// an array of member objects with user data
],
"assigned_members": [
// an array of assigned member objects
],
"assigned_member_ids": [1, 2, 3],
"times": [
// an array of time objects
],
"invoices": [
// an array of invoice objects
],
"expenses": [
// an array of expense objects
],
"times_count": 45,
"invoices_count": 3,
"expenses_count": 12,
"status": "active",
"is_billing_completed": false,
"billing_completed_at": null,
"billing_completed_reason": null,
"is_archived": false,
"is_ongoing": true,
"is_internal": false,
"costs_limit": 5000.0,
"custom_fields": {},
"created_at": "2021-12-09T09:05:28.000000Z",
"updated_at": "2022-02-09T08:52:00.000000Z"
}Delete a project
delete
/project/{id}
Example response
json
// HTTP 204 No ContentList projects
get
/project
The list endpoint accepts the same parameters as in Retrieve a project and returns a paginated array of the same project object in the data property.
Read more about Pagination, Filtering, Sorting and Includes on the Introduction page.
Merge a project
post
/project/{id}/merge
Merges a project into another active, top-level project of the same company. All related resources — offers, invoices, times, expenses, media and assigned members — are moved from the source project into the target project. The source project is then archived (status set to completed).
Required Permission
This endpoint requires the supervise project permission. Users without it receive a 403 Forbidden response.
The target must be a top-level project — a sub-project can never be a merge target.
| Attribute (* required) | Type | Description |
|---|---|---|
target_project_id * | String | A Project Id to merge into. Must be an active, top-level project of the same company (a sub-project cannot be a target) |
move_sub_projects | Boolean | Re-parent the source's sub-projects under the target. If omitted or false, the sub-projects are archived together with the source project (default: false) |
Example request
json
{
"target_project_id": "PQYLdqm4wR",
"move_sub_projects": true
}Example response
The target project is returned.
json
// HTTP 200 OK
{
// a project object
}Complete the billing
post
/project/{id}/complete-billing
Leaves the uninvoiced rest of a flat-rate project's approved offers unbilled, for example when positions were never used. The project's billable_total drops to 0 and the rest is returned as billing_waived_total (see Financial figures). The project status does not change: a project can be active with completed billing, or completed with work still to invoice. Completing an already completed billing changes nothing.
Required Permission
This endpoint requires the edit project permission, and either the supervise invoice permission or being the project leader. Other users receive a 403 Forbidden response.
Only top-level, non-internal projects with the billing type flat can complete their billing; others return 422. Hourly work is taken out of billing per time entry instead (is_accountable).
| Attribute | Type | Description |
|---|---|---|
reason | String | Why the rest is not invoiced (max. 255 characters) |
The project then returns is_billing_completed: true, the date in billing_completed_at and the reason in billing_completed_reason (to users who can see the project's budget).
Example request
json
{
"reason": "Phase 2 not ordered"
}Example response
json
// HTTP 200 OK
{
// a project object
"is_billing_completed": true,
"billing_completed_at": "2026-10-09T16:12:00.000000Z",
"billing_completed_reason": "Phase 2 not ordered"
}Reopen the billing
post
/project/{id}/reopen-billing
Counts the uninvoiced rest of the approved offers as still to invoice again. Requires the same permissions as completing the billing. Reopening a project whose billing is not completed changes nothing.
Example response
json
// HTTP 200 OK
{
// a project object
"is_billing_completed": false,
"billing_completed_at": null,
"billing_completed_reason": null
}