Before you price a new job, it helps to look at finished projects like it and see how profitable they were. The project list in the API and the MCP tool list_projects filter the same way as the project list in Drum, and both return gross profit when you're allowed to see it. This page covers those filters and the gross profit fields. All REST endpoints sit under /api/v1/ and need an account-scoped bearer token. See Drum API authentication, permissions and rate limits.
GET /api/v1/projects and list_projects share one set of filter rules, so a value that works on one works on the other. list_projects and get_project are in the My work and Insights toolsets. See How to choose MCP toolsets for Connected Agents and API tokens.
Filters
These filters work on GET /api/v1/projects and list_projects. Every filter you send must match, and they work alongside the existing filters (status, assigned person, tags and project number). You only get projects you can already see.
On REST, send one tag as a plain string, like ?project_tag_list=Civil, or several as project_tag_list[]=Civil&project_tag_list[]=Fire. A project must carry all of them.
Parameter | Meaning |
|
|
|
|
| Project types (project templates). A project on any of these ids matches |
| Teams. A project in any of these teams matches |
| Client companies. A project for any of these clients matches |
| Completed on or after this date, as |
| Completed on or before this date, as |
| Up to 10 custom field filters, each with a |
The rules for values:
active_onlyandcompleted_onlytaketrueorfalse. Other values, such as1, are refusedThe id filters take a list of whole-number ids, up to 100 each. In a query string, send each id with
[], for exampleteam_ids[]=4&team_ids[]=7. A single value without[]is refusedDates are ISO 8601 (
yyyy-mm-dd). A value likeMayor01/05/2025is refusedIn a query string, send custom fields with an index:
custom_fields[0][custom_field_id]=58&custom_fields[0][values][]=Residential. On MCP, send[{"custom_field_id": 58, "values": ["Residential"]}]
Example: completed residential projects of one project type, finished in the 2024 to 2025 financial year:
GET /api/v1/projects?completed_only=true&project_template_ids[]=12&completed_from=2024-07-01&completed_to=2025-06-30&custom_fields[0][custom_field_id]=58&custom_fields[0][values][]=Residential
Change for existing integrations
The project list used to ignore these parameter names. It now checks them, so a value it can't read returns 422 instead of an unfiltered list. If your integration already sends any of these names, check that the values follow the rules above.
Since 9 October 2026, deleted projects no longer appear in GET /api/v1/projects, and a single tag sent as a plain string works instead of returning an error. GET /api/v1/projects/:id answers an empty 404 for a deleted project, an opportunity, or a project you can't see. A hidden project used to answer 403.
Finding the ids
Filter | Where to get the ids |
| MCP |
| Each custom field on |
| The |
| The |
A handy way to start is to open a project that's like the new job with get_project, then reuse its project types, team, client and custom field values as filters.
Gross profit
Gross profit uses the same rule as the project list and project insights in Drum. You see it only when both of these are true:
Your account has Show Gross Profit Figures turned on (Settings > Features)
Your role has View Gross Profit. Account admins have every permission
It doesn't need View Project Financial Metrics. When either condition isn't met, the gross_profit field is left out of the response, not sent as zero. See Drum Permissions.
Where | What you get |
| Each project row has |
| The two percentages plus the amounts below, as decimals in the project currency |
| Each row has the two percentages |
| With the |
Field | Meaning |
| Realised gross profit over invoiced revenue, as a percentage. |
| Projected gross profit over projected revenue, as a percentage. |
| Invoiced revenue less realised costs |
| The cost of invoiced time at cost rates, plus invoiced expenses |
| Projected revenue less actual costs |
| Invoiced revenue plus unbilled work, capped at the budget on a fixed price project |
| All time at cost rates, plus expenses |
These costs use staff cost rates, not billable rates, so they're different from costs_to_date. Percentages are rounded to two decimal places.
Example from a project show response:
"gross_profit": {
"realised_percentage": 32.5,
"projected_percentage": 30.1,
"realised": 48750.0,
"realised_costs": 101250.0,
"projected": 48762.0,
"projected_revenue": 162000.0,
"actual_costs": 113238.0
}
Errors
A filter value that can't be read returns 422 with code invalid_input and the field it applies to, for example:
{"error": "completed_from must be an ISO 8601 date", "code": "invalid_input", "field": "completed_from"}When | field |
| The parameter you sent |
|
|
An id filter isn't a list of whole-number ids, or has more than 100 | The parameter you sent |
A date isn't |
|
More than 10 custom field filters, or |
|
A |
|
|
|
MCP list_projects refuses the same values with an invalid tool error that names the same field.
