Altus_API_GetAssignmentByDay - Custom API Documentation
Note
This is an internal Altus API and is planned to change in the future - If you take a dependency on this API check release notes before deploying updates.
Warning
This API can return large volumes of data and consume high amounts of server resources. Use this API carefully with narrow filters and respect Microsoft Service Protection limits and retry guidance. Failure to do so may cause throttling and failures in other parts of the system.
Overview
Altus_API_GetAssignmentByDay is a Dynamics 365 / Power Apps custom API that returns assignment hours broken down by day for a specified project. It is registered as the sensei_assignmentbyday custom action and can be called from Power Automate, client scripts, or other plugins.
The API supports two project types:
- Native Altus projects: schedule data is synthesized from Dynamics task and assignment records.
- External (imported) projects: schedule data is read from a published Microsoft Project (
.mpp) file attached to the project.
The actual hours feature is only relevant for MPP-backed external projects. For native Altus projects, the API intentionally does not populate protected actuals.
Request
- Type:
POST - URI:
https://<host>.crm.dynamics.com/api/data/v9.2/sensei_assignmentbyday - Headers:
Content-Type: application/json
Example request
{
"projectid": "2beacc65-c52b-f011-8c4d-002248112dcc",
"start": "2025-12-31T13:30:00.000Z",
"finish": "2026-06-29T14:30:00.000Z",
"aggregationType": "resource",
"resourceids": [],
"taskids": [],
"includeRelatedNamedResources": true,
"totalsonly": false,
"includeprotectedactuals": true
}
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectid |
Guid | Yes | Project record to query. The API validates the project exists. |
start |
DateTime | No | Inclusive lower bound for the date range. If omitted, no lower bound is applied. |
finish |
DateTime | No | Inclusive upper bound for the date range. If omitted, no upper bound is applied. If both are supplied and start > finish, the API throws an InvalidPluginExecutionException. |
aggregationType |
string | No | resource, task, or none. Default is none. Case-insensitive. |
resourceids |
string[] | No | Bookable resource GUIDs to include. If omitted, all resources are included. |
taskids |
string[] | No | Project task GUIDs to include. If omitted, all tasks are included. |
includeRelatedNamedResources |
bool | No | Whether to include assignments linked through named (generic) resources. Default is true. |
totalsonly |
bool | No | If true, the API omits the per-day A array and returns only subtotal values. Default is false. |
includeprotectedactuals |
bool | No | If true, the API includes a protected-actuals subtotal in the P field for MPP-backed projects. Default is false. |
Data source logic
The API determines the schedule source as follows.
- If the project has an External System Reference:
- It looks for an attached Microsoft Project (
.mpp) file in the project documents. - If an MPP file is found, it reads assignment data and maps it back to Altus task and resource IDs.
- Planner MPPs are handled through a separate mapping path, because Planner tasks do not directly match the Altus IDs used by native project records.
- If no MPP file is found, the API returns an empty dataset.
- It looks for an attached Microsoft Project (
- If the project has no external system reference:
- It synthesizes assignment-by-day data from Altus task and resource-assignment records by using
SyntheticCalculatorService.
- It synthesizes assignment-by-day data from Altus task and resource-assignment records by using
After the raw assignment-by-day records are loaded, the API applies the filters and grouping requested by the caller:
- date range (
start/finish) resourceids/taskidsincludeRelatedNamedResources- aggregation mode (
aggregationType)
When actuals can be returned
The P field is not part of the general planned-hours payload. It only appears when the API is operating on an external MPP project and the caller explicitly sets includeprotectedactuals to true.
The actuals used by this API are not read from Altus timesheet records or finance actuals. They are read from the underlying MPP, and only for protected actuals - no actuals imported from Altus timesheets are returned.
Conditions where protected actuals are returned
- The project is external / MPP-backed.
- The API finds the attached MPP file.
includeprotectedactualsis set totrue.- There is actual work in the MPP assignment data for the relevant task/resource combination.
Conditions where protected actuals are not returned
- The project is a native Altus project:
Pis always absent. includeprotectedactualsis omitted or set tofalse:Pis not included.- There are no qualifying actual-work hours:
Pis omitted. totalsonlyistrue: the per-dayAarray is omitted, butPcan still be present when enabled.
How the actuals are calculated
In the MPP reader, the API uses the task's Date10 value as the protected-actuals epoch when it is present.
- If a task has a
Date10value, the code sums only MPP actual-work slots where the timephased slot start is strictly before that epoch. - If a task does not have a
Date10value, the code falls back to the assignment's full actual-work total for that assignment - assuming that all actuals are protected. - The result is stored as
ProtectedActualHourson the assignment record and then summed during aggregation.
This behaviour is implemented only for MPP based projects and is intentionally separate from the synthetic time-phased Altus project path, which does not attempt to populate actuals.
The API does not read actuals from approved timesheets or from the native Altus schedule. For native projects, the P value is deliberately null and omitted from the response.
Response format
The API writes a single JSON-serialized output parameter named data.
{
"@odata.context": "https://<host>.crm.dynamics.com/api/data/v9.2/$metadata#Microsoft.Dynamics.CRM.sensei_assignmentbydayResponse",
"data": "[<array of stringified response objects>]"
}
The data value is a JSON string containing an array of AssignmentByDayDataResponse objects. When there are no matching rows, the returned value is [].
Response fields
Each item in the array can contain the following fields:
| Field | Meaning |
|---|---|
G |
Group key. This is the resource or task identifier used for the selected aggregation mode. It is empty when aggregationType is none. |
T |
Task ID. Only emitted when present. |
R |
Resource ID. Only emitted when present. |
S |
Subtotal planned hours for the row or group. |
P |
Protected actuals subtotal. Only emitted when includeprotectedactuals is enabled and the value is not null. |
A |
Per-day assignment data. This is omitted when totalsonly is true, or when no data is present. |
Example response
[
{
"G": "90cb0f2f-0c1d-4f4e-9d01-1d9d95d9f0d8",
"T": "60e171be-1094-4d92-ae9a-f7a47f842e5e",
"R": "e431f082-77cf-4302-b9df-1854f3f6ed6e",
"S": 12.5,
"P": 4.5,
"A": [
{ "D": "2025-01-01", "H": 8.0 },
{ "D": "2025-01-02", "H": 4.5 }
]
}
]
If totalsonly is true, the response is effectively the same shape without the A array:
[
{
"G": "90cb0f2f-0c1d-4f4e-9d01-1d9d95d9f0d8",
"T": "60e171be-1094-4d92-ae9a-f7a47f842e5e",
"R": "e431f082-77cf-4302-b9df-1854f3f6ed6e",
"S": 12.5,
"P": 4.5
}
]
Aggregation modes
The API supports three aggregation modes.
aggregationType = none(default): one row per resource-task pair.aggregationType = resource: rows are grouped by Bookable Resource ID.aggregationType = task: rows are grouped by project task ID.
Each row still includes the subtotal S, and P is added by summing protected actual hours for the group when enabled.
Implementation notes
The current implementation validates the project, resolves whether the project is MPP-backed, and then either:
- reads assignment data; or
- synthesizes schedule data for native Altus tasks.
For MPP data, the daily work values are derived from GetTimephasedWork, while protected actuals are derived from GetTimephasedActualWork and the task Date10 epoch. For native Altus schedules, the synthetic path is used and no protected actuals are generated.
This means the most important practical rule is:
Actuals are available only for MPP-backed projects before the Date10 epoch date and only when the caller asks for them via
includeprotectedactuals=true.