Resource

REST API for Resource Forecast Bulk Edit Create/Update

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 0 to 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 UnitHeadCount (the default) or Hours. Case-insensitive. Hours are converted to an allocation fraction using the rate card's monthly hours before being stored.

  • Delete Record – set to true to 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 monthsmonthlyAllocations 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 rangedateRange names one of the seven ranges the bulk-edit download offers, and allocations supplies the values in month order:

Date Range

Months covered

Next 12 Months

The current month and the following 11

This Financial Year

The configured financial year containing today

This Calendar Year

January–December of the current year

Previous Financial Year

The financial year before this one

Previous Calendar Year

January–December of last year

Next Financial Year

The financial year after this one

Next Calendar Year

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-25 where Feb-26 was 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 (month in epoch milliseconds, or monthLabel in MMM-yy format).

  • Allocation Unit, when supplied, must be HeadCount or Hours.

  • 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 validationMessages even on rows where isValid is true.

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 0 to 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 Hours only 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

RateLimit-Limit

Maximum allowed requests in the window

RateLimit-Remaining

Remaining capacity

RateLimit-Reset

Seconds until the window resets

Endpoints

Method

URL

Content-Type

POST

{rooturl}/rest/api/resourceforecast

application/json

POST

{rooturl}/rest/api/resourceforecast/bulk

application/json

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

resourceRef

string

Yes

Resource identifier. Accepts the resource's external reference, user name, email address or principal GUID.

projectRef

string

Yes*

Project external reference (e.g. "MM1000"). A project GUID is also accepted here.

projectId

integer

Yes*

Project principal id.

projectGuid

string

Yes*

Project GUID.

alternateReference

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.

monthlyAllocations

array

Yes**

Explicit month/value pairs — see below.

dateRange

string

Yes**

A named month range. Used together with allocations.

allocations

decimal array

Yes**

Values in month order, aligned to the months of dateRange. null entries are skipped.

allocationId

integer

No

Existing allocation (community) id. Omit or send 0 to create, or to match by unique combination. Must not be negative. Required when deleteRecord is true.

role

string

No***

Project role for the allocation.

skills

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.

communityFinancialRef

string

No

Financial reference for the allocation. The label is configurable. Part of the unique combination.

deleteRecord

boolean

No

Deletes the allocation and its monthly values. Requires allocationId.

allocationUnit

string

No

"HeadCount" (default) or "Hours". Case-insensitive.

* 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

monthLabel

string

One of the two

Month in MMM-yy format, e.g. "Jan-26" — the spreadsheet's own column-header format. A trailing unit ("Jan-26 Hours") is tolerated. Two-digit years pivot at 2049, so "Jan-50" means 1950; use month for dates beyond that.

month

number

One of the two

Month as epoch milliseconds, normalised to the 1st of that month. The form to use from code.

allocation

decimal

Yes

The value for that month. 0 clears the month's allocation. null skips the entry.

If both are supplied on one entry, month wins.

Response Models

Returned by the bulk create endpoint.

Field

Type

Description

status

string

Overall batch status: "success", "partial" or "failed".

totalCount

integer

Total number of records submitted.

validCount

integer

Number of records that passed validation and were applied.

invalidCount

integer

Number of records that failed validation.

correlationId

string

Unique GUID assigned to this request for tracking and log correlation.

results

array

List of per-row results, one per submitted record, ordered by rowIndex.

status values

Value

Meaning

"success"

All submitted records were valid and applied.

"partial"

Some records were valid and applied; others failed validation.

"failed"

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

rowIndex

integer

1-based position of this record within the submitted list.

isValid

boolean

true if the record passed all validations and was applied.

status

string

"valid" or "invalid".

validationMessages

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

resourceRef is missing

ResourceRef is required

None of projectRef, projectId, projectGuid or alternateReference is provided

ProjectRef, ProjectId, ProjectGuid, or AlternateReference is required

Neither monthlyAllocations nor dateRange + allocations is provided (and not a delete)

MonthlyAllocations, or DateRange with Allocations, is required

dateRange is not one of the seven supported ranges

DateRange must be one of: Next 12 Months, This Financial Year, This Calendar Year, Previous Financial Year, Previous Calendar Year, Next Financial Year, Next Calendar Year

A monthly entry has no resolvable month

Each monthly allocation requires either Month (epoch milliseconds) or MonthLabel in MMM-yy format

allocationUnit is neither HeadCount nor Hours

AllocationUnit must be either HeadCount or Hours

deleteRecord is true with no allocationId

DeleteRecord requires an AllocationId

fields is absent

Fields cannot be null

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

This row was skipped as no project found matching the … / … as no projects could be found with the … specified in the file

More than one project matches

This row was skipped as more than one project with the same …

Project is archived

Treated as not found — archived projects are excluded from every lookup

Caller lacks manage permission on the project

This row was skipped as you do not have permission to change the allocation for the project {name}.

Project is a programme

This row was skipped as financials cannot be uploaded against a {Programme}.

allocationId matches no existing record

This row was skipped as the Allocation ID value does not match any existing records.

Allocation is derived from the project schedule

This row was skipped as its derived from the project schedule.

Submitted project/resource does not match the existing allocation

This row was skipped as the Project and/or the Resource value(s) do not match the existing record for Allocation ID {id}. Only the resource role and the monthly allocation can be changed on existing records.

role is required but blank

This row was skipped as a value for the Role column is required.

role is not a recognised project role

The row was skipped as the '{role}' role specified is not recognised.

Role is a protected community role

This row was skipped because forecasts cannot be assigned to new users with protected roles such as ({roles}).

Role change on an existing record

Role for existing record cannot be changed. This value has been ignored. (warning)

Role change would duplicate a role for the resource on that project

This row was skipped as updating the role to {role} would result in the user having multiple entries for the same role.

Financial-reference change on a record with forecasts/actuals in locked months

This row was skipped because {FinancialRef} changes are not allowed for records with forecasts and/or actuals in locked months.

Financial-reference change on a record with timesheets

This row was skipped as {FinancialRef} for existing record cannot be changed as it has timesheet against it.

The new combination already exists

This row for resource {ref} was not updated as an existing record with the role '{role}' and {FinancialRef} '{value}' already exists.

skills is not a recognised skill

The '{skill}' skill specified is not recognised. This value has been ignored. (warning)

A monthly value is negative

This row was skipped as the allocation value for month {MMM-yy} should be a positive number.

Month is on or before the lock date and the caller is not App Admin / Financial Administrator

Skipped month {MMM-yy} as it is not possible to update the allocation value for a month that has now been closed. (warning)

The month would over-allocate the resource

The allocation value for {MMM-yy} was ignored as resources cannot be over-allocated … (warning; suppressed when Allow Resourcing Force Save is active)

Team placeholder allocation without the required feature

This row was skipped as {Team} allocation is not allowed.

Delete with no allocationId

This row was skipped as you cannot delete a record for which Allocation ID is not specified.

Delete of a protected role

This row cannot be deleted as role({role}) is protected.

Delete where actuals exist

This allocation entry cannot be deleted as Actuals amount exist for this record.

Delete where allocations exist in closed months

This allocation record cannot be deleted as allocation values exist for this record for months that are now closed. Set the monthly allocation to 0 if you want to remove the allocation for the open 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

  1. Access check: the caller must satisfy the feature flags and roles above, or the whole request is rejected with 403 Forbidden and nothing is saved.

  2. 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.

  3. Month resolution: dateRange is expanded into its months and allocations mapped onto them in order; monthlyAllocations is then applied on top.

  4. 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.

  5. 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

200 OK

The request was processed. Inspect status and the per-record results — a failed status is still returned as 200.

400 Bad Request

The request body is malformed or could not be bound.

401 Unauthorized

Not authenticated for API access.

403 Forbidden

The caller does not satisfy the feature flags / roles required for resource forecasts.

405 Method Not Allowed

GET, PUT and DELETE are not supported on these routes.

422 Unprocessable Entity

The payload was understood but could not be processed.

429 Too Many Requests

Rate limit reached. See the RateLimit-* response headers.

500 Internal Server Error

Unexpected server error.

Versioning

The API supports versioning via the version query parameter:

  • version=3 or version=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

  1. 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.

  2. Treat allocationId as an opaque identifier. Source it from the download; never construct it.

  3. For updates, change only the role and the monthly values. Any change to the project or resource on a record with allocationId > 0 fails the identity check and the record is rejected.

  4. Send only the months you mean to change. Omitted months are untouched; send an explicit 0 to clear a month. This makes incremental updates safe and small.

  5. Verify your months. Any month that parses as MMM-yy is written, whatever range you asked for — an off-by-one-year typo is silently accepted and lands 12 months away.

  6. 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.

  7. Check isValid per 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.

  8. Handle partial success. The status field signals "success", "partial" or "failed" — do not assume a 200 means everything was applied.

  9. 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.

  10. 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.

  11. Set allocationUnit deliberately. The default is HeadCount (FTE). Sending hours without setting allocationUnit to Hours records a wildly over-allocated month.

  12. Use field filtering to reduce response payload size when only specific fields are needed.

  13. Keep the correlationId. It is the reference for tracing a submission in the job logs when you need to follow one up.

Was this article helpful?