The Resource Forecast API lets authorised users create, update and delete a resource's monthly allocations against a project. You can submit a single record or a batch of records in one request.
It is the programmatic equivalent of the Bulk Edit → Resource Forecast spreadsheet, and it applies the same validations, so a payload behaves exactly as the equivalent spreadsheet row would.
This article explains who can use the API, what information each record needs, the rules that are applied, and how to interpret the results.
Who can use it
To submit resource forecasts you must:
Have the feature Allow Bulk Edit enabled for your organisation, and
Have the feature Show Resourcing enabled for your organisation, and
Hold one of these roles: Resourcing Access, Project Admin or App Admin, and
Be a User — the base role required for all API access.
If any of these conditions is not met, the request is rejected with a 403 Forbidden response and no records are saved.
Two further permissions are checked per record rather than at the endpoint, because each record names its own project:
Manage permission on the project — a record naming a project you cannot manage is skipped and the reason is reported against that row. Every other record in the same request still processes.
App Admin or Financial Administrator — needed only to write into closed months (months on or before the forecast/actuals lock date). Without it, those months are skipped and reported; the record's remaining months are still applied.
What you can do
Submit a single record – send one record and receive the outcome for that record.
Submit a batch of records – send up to 1000 records at once and receive a per-row outcome plus an overall summary.
Delete an allocation – remove an allocation and all of its monthly values.
Each record either creates a new allocation or updates an existing one. To update an existing allocation, include its Allocation Id. Leave the Allocation Id empty (or set it to 0) and the record is matched against existing allocations by the unique combination of resource + project + role + financial reference — an existing match is updated, anything else creates a new allocation.
Only the months you send are updated. Months absent from a record are left exactly as they are. Send an explicit
0to clear a month's allocation.
Information needed for each record
Each record is made up of the following fields, wrapped in a fields object.
Required
Every record must include all of the following:
Resource Ref – identifies the resource. Accepts the resource's external reference, user name, email address or principal GUID.
A way to identify the project – provide at least one of Project Ref, Project Id, Project Guid or Alternate Reference.
The months and their values – provide either Monthly Allocations (explicit month/value pairs), or a Date Range together with Allocations (values in month order). Not required when deleting.
Optional
These fields can be supplied to add more detail or to support specific scenarios:
Allocation Id – the identifier of an existing allocation you want to update. Omit it (or send
0) to create a new allocation or to match by the unique combination. Required when deleting.Role – the project role for the allocation. Required only when the Disable Force Resource Role To Project Role feature is active; otherwise a blank role falls back to the resource's profile role.
Skills – the resource's skill. Ignored when the Use Resource Skills feature is off. An unrecognised skill is dropped with a warning and the record still processes.
Community Financial Ref – the financial reference for the allocation. The field's label is configurable, so your organisation may show a different name. Part of the unique combination.
Allocation Unit –
HeadCount(the default) orHours. Case-insensitive. Hours are converted to an allocation fraction using the rate card's monthly hours before being stored.Delete Record – set to
trueto delete the allocation and its monthly values. Requires an Allocation Id.
How to express the months
There are two ways, and they can be combined.
1. Explicit months — monthlyAllocations names each month and its value. Give the month as either monthLabel (the readable MMM-yy form, e.g. "Jan-26" — the spreadsheet's own column-header format) or month (epoch milliseconds, for callers that already hold a date). If both are given, month wins.
2. A named month range — dateRange names one of the seven ranges the bulk-edit download offers, and allocations supplies the values in month order:
Date Range | Months covered |
|---|---|
| The current month and the following 11 |
| The configured financial year containing today |
| January–December of the current year |
| The financial year before this one |
| January–December of last year |
| The financial year after this one |
| January–December of next year |
allocations[0] is the first month of the range. A shorter array leaves the remaining months untouched; a longer one has its surplus ignored. Range names are case-insensitive and surrounding whitespace is ignored. Financial-year boundaries follow your organisation's configured financial year, not the calendar year.
When both are supplied, the explicit months are applied on top of the range — so use monthlyAllocations to extend a range with months it does not cover, or to override a month inside it. The same month named twice inside monthlyAllocations keeps the last value.
A month outside the range is indistinguishable from a typo. Any month you name is written, whatever range you asked for —
Feb-25whereFeb-26was meant is accepted just as readily and lands 12 months early. Check the months before sending.
Validation rules
Records are checked in two stages.
1. Basic (record) checks
These are applied first to every record:
A Resource Ref must be supplied.
The project must be identifiable by a Project Ref, Project Id, Project Guid or Alternate Reference.
Monthly Allocations, or a Date Range with Allocations, must be supplied — unless the record is a delete.
Date Range, when supplied, must be one of the seven supported ranges.
Every monthly entry must carry a resolvable month (
monthin epoch milliseconds, ormonthLabelinMMM-yyformat).Allocation Unit, when supplied, must be
HeadCountorHours.Delete Record requires an Allocation Id.
A record that fails any of these checks is marked invalid and is not processed further. The reason is returned against that record.
2. Business checks
Records that pass the basic checks are then validated against your organisation's data and rules, for example:
The resource reference must resolve to exactly one resource, and that resource must have a valid resource plan.
The project identifier(s) must resolve to exactly one project. Archived projects are never matched.
When several project identifiers are supplied they must all name the same project.
You must hold Manage permission on the project.
The project must not be a programme — a programme cannot hold financials, so allocate to its child projects instead.
When an Allocation Id is supplied it must exist, must not be derived from the project schedule, and its project and resource must match what was submitted.
Roles reserved as protected community roles (project manager, owner, executive, editors, viewers, business owner) cannot be assigned to new allocations or changed through this API.
A role change must not leave the resource with two allocations for the same role on one project.
A financial-reference change is rejected when it would duplicate an existing combination, when the allocation has forecasts in locked months, or when it has timesheets against it.
Monthly values must not be negative.
Months on or before the forecast/actuals lock date are skipped unless you are an App Admin or Financial Administrator.
A resource must not be over-allocated for a month, unless the Allow Resourcing Force Save feature is active.
A delete is rejected when actuals exist for the resource on that project, or when allocation values exist in months that are now closed.
Records that fail a business check are marked invalid and the specific reason is returned against the corresponding row. Records that pass all checks are applied.
Partial success is supported. When you submit a batch, valid records are applied even if other records in the same batch are invalid. You do not need to resubmit the whole batch to fix a single bad row.
Some messages are warnings, not rejections. An unrecognised skill, an ignored role change, an over-allocated month and a skipped closed month all return a message while the record itself still counts as valid. Read
validationMessageseven on rows whereisValidistrue.
Understanding the results
Single record
The response tells you whether the record was valid or invalid, and includes any validation messages explaining why it was rejected — or what was ignored.
Batch of records
The response includes an overall summary and a per-row breakdown:
Status – the overall outcome of the batch:
success– every record was valid and applied.partial– some records were valid and applied; others were invalid.failed– no records were valid; nothing was applied.
Total count – the number of records submitted.
Valid count – the number of records that passed validation and were applied.
Invalid count – the number of records that failed validation.
Per-row results – for each record, its row index, whether it was valid, and any validation messages.
Correlation Id – a reference for the batch, useful when reviewing or following up on a submission.
Tips for a clean submission
Always include a Resource Ref and at least one project identifier on every record.
Download the bulk-edit template first to get accurate Allocation Id values and the exact stored field values, so an update matches the record you intend.
Remember that only the months you send are changed. Send
0to clear a month; omit a month to leave it alone.Check the months before sending — any month that parses is written, whether or not it falls in the range you asked for.
Make sure the months you are writing to are still open, or that you hold App Admin / Financial Administrator to write into closed ones.
Set Allocation Unit to
Hoursonly when your values really are hours; the default is head count / FTE.To delete an allocation, send Delete Record together with its Allocation Id — a delete without one is rejected.
When submitting a batch, review the per-row results so you can correct and resubmit only the rows that failed.
Base URL
/rest/api/resourceforecast
Rate Limits
Endpoint | Limit |
|---|---|
POST single | 20 requests per 5 seconds |
POST bulk | 10 requests per 10 seconds |
The bulk endpoint accepts a maximum of 1000 records per call.
Rate limit status is returned in response headers:
Header | Description |
|---|---|
| Maximum allowed requests in the window |
| Remaining capacity |
| Seconds until the window resets |
Endpoints
Method | URL | Content-Type |
|---|---|---|
POST |
|
|
POST |
|
|
GET, PUT and DELETE are not supported on these routes and return 405 Method Not Allowed. Deletion is performed by posting a record with deleteRecord: true.
1. Create or Update a Single Record
Creates a new resource allocation, or updates the existing one matching the unique combination.
HTTP Example
Endpoint: POST /rest/api/resourceforecast
Request Body:
{
"fields": {
"resourceRef": "adam.jones",
"projectRef": "MM1000",
"role": "Developer",
"communityFinancialRef": "CC-1004",
"allocationUnit": "HeadCount",
"monthlyAllocations": [
{ "monthLabel": "Jan-26", "allocation": 0.5 },
{ "monthLabel": "Feb-26", "allocation": 0.5 },
{ "monthLabel": "Mar-26", "allocation": 0 }
]
}
}
cURL Example
curl -X POST "https://organisation.fluid.work/rest/api/resourceforecast?version=3.0" \
-H "Authorization: Bearer {your_api_token}" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"resourceRef": "adam.jones",
"projectRef": "MM1000",
"role": "Developer",
"monthlyAllocations": [
{ "monthLabel": "Jan-26", "allocation": 0.5 }
]
}
}'
Response — Valid record:
{
"fields": {
"rowIndex": 1,
"isValid": true,
"status": "valid",
"validationMessages": []
}
}
Response — Validation error:
{
"fields": {
"rowIndex": 1,
"isValid": false,
"status": "invalid",
"validationMessages": [
"Row 1: This row was skipped as you do not have permission to change the allocation for the project User Documentation."
]
}
}
Response (403 Forbidden):
{
"message": "You do not have the required permission to upload resource forecasts."
}
2. Create or Update Records in Bulk
Submits multiple records in a single request. Each record is independently validated and processed. Invalid rows do not prevent valid rows from being saved.
HTTP Example
Endpoint: POST /rest/api/resourceforecast/bulk
Request Body:
[
{
"fields": {
"resourceRef": "adam.jones",
"projectRef": "MM1000",
"role": "Developer",
"dateRange": "This Financial Year",
"allocations": [0.5, 0.5, 0.5, 0.25, 0.25, 0.25, 0, 0, 0, 0, 0, 0]
}
},
{
"fields": {
"resourceRef": "sarah.lee",
"alternateReference": "ALT-1003",
"role": "Business Analyst",
"allocationUnit": "Hours",
"monthlyAllocations": [
{ "month": 1767225600000, "allocation": 80 },
{ "monthLabel": "Feb-26", "allocation": 120 }
]
}
},
{
"fields": {
"allocationId": 4821,
"resourceRef": "ben.taylor",
"projectRef": "MM1000",
"deleteRecord": true
}
}
]
cURL Example
curl -X POST "https://organisation.fluid.work/rest/api/resourceforecast/bulk?version=3.0" \
-H "Authorization: Bearer {your_api_token}" \
-H "Content-Type: application/json" \
-d '[
{
"fields": {
"resourceRef": "adam.jones",
"projectRef": "MM1000",
"role": "Developer",
"dateRange": "This Financial Year",
"allocations": [0.5, 0.5, 0.5, 0.25, 0.25, 0.25, 0, 0, 0, 0, 0, 0]
}
},
{
"fields": {
"resourceRef": "sarah.lee",
"projectRef": "MM1003",
"role": "Business Analyst",
"dateRange": "Next 12 Months",
"allocations": [1, 1, 1]
}
}
]'
Response — All records accepted:
{
"status": "success",
"totalCount": 2,
"validCount": 2,
"invalidCount": 0,
"correlationId": "8f3c1d2e-6b4a-4c9d-9e21-0a7f5b8c1d34",
"results": [
{ "fields": { "rowIndex": 1, "isValid": true, "status": "valid", "validationMessages": [] } },
{ "fields": { "rowIndex": 2, "isValid": true, "status": "valid", "validationMessages": [] } }
]
}
Response — Partial success:
{
"status": "partial",
"totalCount": 3,
"validCount": 2,
"invalidCount": 1,
"correlationId": "8f3c1d2e-6b4a-4c9d-9e21-0a7f5b8c1d34",
"results": [
{
"fields": {
"rowIndex": 1,
"isValid": true,
"status": "valid",
"validationMessages": []
}
},
{
"fields": {
"rowIndex": 2,
"isValid": false,
"status": "invalid",
"validationMessages": [
"Row 2: This row was skipped as no project found matching the Project Ref 'MM9999'."
]
}
},
{
"fields": {
"rowIndex": 3,
"isValid": true,
"status": "valid",
"validationMessages": [
"Row 3: The 'Underwater Welding' skill specified is not recognised. This value has been ignored."
]
}
}
]
}
Response (400 Bad Request) — the body is malformed or could not be bound:
{
"message": "Invalid input for resource forecast bulk creation"
}
Response (403 Forbidden):
{
"message": "You do not have the required permission to upload resource forecasts."
}
Request Model
The top-level request object wraps a fields object. The single endpoint takes one object; the bulk endpoint takes an array of them.
{
"fields": { ... }
}
Field | Type | Required | Description |
|---|---|---|---|
| string | Yes | Resource identifier. Accepts the resource's external reference, user name, email address or principal GUID. |
| string | Yes* | Project external reference (e.g. |
| integer | Yes* | Project principal id. |
| string | Yes* | Project GUID. |
| string | Yes* | Project's alternate reference. Identifies the project on its own; when another identifier is also supplied the two must agree. The field's label is configurable, so messages name it as your organisation has it configured. |
| array | Yes** | Explicit month/value pairs — see below. |
| string | Yes** | A named month range. Used together with |
| decimal array | Yes** | Values in month order, aligned to the months of |
| integer | No | Existing allocation (community) id. Omit or send |
| string | No*** | Project role for the allocation. |
| string | No | Resource skill. Ignored when the Use Resource Skills feature is off. An unrecognised skill is dropped with a warning and the row still processes. |
| string | No | Financial reference for the allocation. The label is configurable. Part of the unique combination. |
| boolean | No | Deletes the allocation and its monthly values. Requires |
| string | No |
|
* At least one of projectRef, projectId, projectGuid or alternateReference is required.
** Supply either monthlyAllocations, or dateRange together with allocations. Both may be given — the explicit months are applied on top of the range. Not required when deleteRecord is true.
*** role is required only when the Disable Force Resource Role To Project Role feature is active. Otherwise a blank role falls back to the resource's profile role.
monthlyAllocations entries
Field | Type | Required | Description |
|---|---|---|---|
| string | One of the two | Month in |
| number | One of the two | Month as epoch milliseconds, normalised to the 1st of that month. The form to use from code. |
| decimal | Yes | The value for that month. |
If both are supplied on one entry, month wins.
Response Models
Returned by the bulk create endpoint.
Field | Type | Description |
|---|---|---|
| string | Overall batch status: |
| integer | Total number of records submitted. |
| integer | Number of records that passed validation and were applied. |
| integer | Number of records that failed validation. |
| string | Unique GUID assigned to this request for tracking and log correlation. |
| array | List of per-row results, one per submitted record, ordered by |
status values
Value | Meaning |
|---|---|
| All submitted records were valid and applied. |
| Some records were valid and applied; others failed validation. |
| No records were valid; nothing was applied. |
Per-row result
The single endpoint returns one of these objects on its own; the bulk endpoint returns them in results.
Field | Type | Description |
|---|---|---|
| integer | 1-based position of this record within the submitted list. |
| boolean |
|
| string |
|
| string[] | Validation messages. Empty when the record was applied with nothing ignored — but populated on valid rows too, for warnings such as an ignored skill, an ignored role change, a skipped closed month or an over-allocated month. |
Validation Rules
Validation runs in two phases: request-level (pre-processor) and processor-level.
Phase 1 — Request-Level Validation
Applied before the processor runs. A record that fails any of these checks is marked invalid and excluded from processor execution.
Rule | Error message |
|---|---|
|
|
None of |
|
Neither |
|
|
|
A monthly entry has no resolvable month |
|
|
|
|
|
|
|
Phase 2 — Processor-Level Validation
Applied for each record that passes Phase 1. These are the same rules the spreadsheet upload applies, and each message is prefixed with the row number.
Rule | Error message fragment |
|---|---|
Resource reference does not resolve to a resource | resource-not-found message from the shared loader |
No project matches the supplied identifiers |
|
More than one project matches |
|
Project is archived | Treated as not found — archived projects are excluded from every lookup |
Caller lacks manage permission on the project |
|
Project is a programme |
|
|
|
Allocation is derived from the project schedule |
|
Submitted project/resource does not match the existing allocation |
|
|
|
|
|
Role is a protected community role |
|
Role change on an existing record |
|
Role change would duplicate a role for the resource on that project |
|
Financial-reference change on a record with forecasts/actuals in locked months |
|
Financial-reference change on a record with timesheets |
|
The new combination already exists |
|
|
|
A monthly value is negative |
|
Month is on or before the lock date and the caller is not App Admin / Financial Administrator |
|
The month would over-allocate the resource |
|
Team placeholder allocation without the required feature |
|
Delete with no |
|
Delete of a protected role |
|
Delete where actuals exist |
|
Delete where allocations exist in closed months |
|
Messages that name the project identifiers describe only the identifier fields the record actually supplied — a record that sent projectRef and alternateReference is not told its projectId was wrong, and a record resolved on alternateReference alone never reports an empty Project Ref ''. The alternate reference always appears under its configured label.
Combination Matching for Updates
There are two ways a record targets an existing allocation.
With allocationId (greater than zero) — the processor loads that allocation and compares the submitted values against it. These fields act as an identity key:
Field |
|---|
Project |
Resource |
If either does not match, the record is rejected and the existing allocation is unchanged. Only the role and the monthly values may differ from the stored record.
Without allocationId — the record is matched by the unique combination:
Field |
|---|
Resource |
Project |
Role |
Community Financial Reference |
An existing allocation matching all four is updated. If nothing matches, a new allocation is created.
Processing Behaviour
Access check: the caller must satisfy the feature flags and roles above, or the whole request is rejected with
403 Forbiddenand nothing is saved.Request validation: each record is checked against the Phase 1 mandatory-field rules. Failures are recorded with their row index and excluded from processor execution.
Month resolution:
dateRangeis expanded into its months andallocationsmapped onto them in order;monthlyAllocationsis then applied on top.Processor execution: all valid records are processed against the Phase 2 rules. Records are processed independently, so several resources and projects may be mixed in one request.
Response assembly: per-row results and messages are collected, counted, and returned with a correlation id.
Only the months present in a record are written. Records that fail a rule leave the database unchanged; other records in the same request still process.
HTTP Status Codes
Code | Meaning |
|---|---|
| The request was processed. Inspect |
| The request body is malformed or could not be bound. |
| Not authenticated for API access. |
| The caller does not satisfy the feature flags / roles required for resource forecasts. |
|
|
| The payload was understood but could not be processed. |
| Rate limit reached. See the |
| Unexpected server error. |
Versioning
The API supports versioning via the version query parameter:
version=3orversion=3.0— V3 API (current)Omitted — resolves to the latest stable version (currently V3)
POST /rest/api/resourceforecast?version=3.0
Field Filtering
Use the fields query parameter to limit which fields appear in the response:
POST /rest/api/resourceforecast?version=3.0&fields=rowIndex,isValid,validationMessages
Uploading a file instead of JSON
POST /import/Resource%20Forecasts accepts an .xlsx or .csv file containing the same data. It is governed by a different set of gates, because it is a shared endpoint serving every import type:
Requirement | Notes |
|---|---|
Data Administrator or Financial Administrator | Enforced by the shared import controller for all import types. |
Show Resourcing feature + one of Resourcing Access / Project Admin / App Admin | Enforced by the Resource Forecast handler itself. Without it the whole file is rejected with "You do not have the required permission to upload resource forecasts." The shared controller's roles alone are not sufficient. |
Manage permission on the project | Per row, exactly as for the API. |
Note that the Allow Bulk Edit feature is not currently checked on this route, though it is on the API.
Best Practices
Download the template before bulk updates. Take the Resource Forecast bulk-edit download from the relevant project, programme or watchlist workspace to get current Allocation Id values and the exact stored field values. This ensures your updates target the records you intend.
Treat
allocationIdas an opaque identifier. Source it from the download; never construct it.For updates, change only the role and the monthly values. Any change to the project or resource on a record with
allocationId > 0fails the identity check and the record is rejected.Send only the months you mean to change. Omitted months are untouched; send an explicit
0to clear a month. This makes incremental updates safe and small.Verify your months. Any month that parses as
MMM-yyis written, whatever range you asked for — an off-by-one-year typo is silently accepted and lands 12 months away.Use the bulk endpoint for multi-row submissions. It processes all records in a single batch (up to 1000) and returns per-row results. Sending records individually across multiple single POST calls is less efficient and burns the tighter rate limit.
Check
isValidper row — and read the messages on valid rows too. Warnings for ignored skills, ignored role changes, skipped closed months and over-allocated months all arrive on rows that count as valid.Handle partial success. The
statusfield signals"success","partial"or"failed"— do not assume a200means everything was applied.Respect the lock date. Months on or before the forecast/actuals lock date are skipped unless you hold App Admin or Financial Administrator. Verify the lock date before submitting historical forecasts.
Allocate to projects, not programmes. A programme cannot hold financials. Target the programme's child projects instead — which is exactly what a download taken from a Program workspace returns.
Set
allocationUnitdeliberately. The default isHeadCount(FTE). Sending hours without settingallocationUnittoHoursrecords a wildly over-allocated month.Use field filtering to reduce response payload size when only specific fields are needed.
Keep the
correlationId. It is the reference for tracing a submission in the job logs when you need to follow one up.