# Every Procore budget code error on a change event line item

> Wbs Code not found, budget_code is missing, must be an integer. Every response Procore gives a change event line item, including two that return 200 and file blank.

Source: https://fieldstub.com/docs/procore-budget-code-errors/  ·  Updated 2026-08-29
Every row posted against a live Procore sandbox on 2026-08-29, change event 447023

If you are posting change event line items into Procore and getting a `400` about a budget code, there are only four errors you can be looking at, and each one means something different. This page has all of them with the exact response body.

The more important half is at the bottom: **two malformed budget codes do not error at all.** They return `200`, the line item is created, and the money lands on a code nobody reviews. If your integration is quiet and your costs are wrong, that is what happened.

## The full matrix

One valid control and five ways to get it wrong, all posted to `POST /rest/v1.0/change_events/{id}/line_items?project_id={project_id}`.

| What you send as budget_code | Status | Response |
|---|---|---|
| `{ "id": 1870754 }` *(a real WBS code)* | **200** | Created, filed correctly. This is the only form that works. |
| `{ "id": 99999999 }` | 400 | `{"errors":{"base":["Wbs Code with ID 99999999 not found"]}}` |
| `{ "id": "not-a-number" }` | 400 | `{"errors":{"params":{"budget_code":{"id":["must be an integer"]}}}}` |
| field omitted entirely | 400 | `{"errors":{"params":{"budget_code":["is missing"]}}}` |
| `{ "segment_items": [...] }` | **200** | **Created, filed blank.** No error, no warning. |
| `{}` *(empty object)* | **200** | **Created, filed blank.** No error, no warning. |

> Read the last two rows again. Procore rejects a budget code that is **absent** and a budget code whose id is **wrong**, but accepts a budget code that is **empty** or **shaped differently**. The validation checks that the field is there and that an id, if present, resolves. It does not check that you actually named a code.

## What each error means

**`budget_code: ["is missing"]`**: you omitted the field. Line items will not be created without it, which is the one place Procore is strict. A bare `cost_code_id` gets the same error, because a cost code is not a budget code.

**`budget_code: {id: ["must be an integer"]}`**: you sent a string. Usually a code read out of a spreadsheet or a query parameter that never got cast.

**`base: ["Wbs Code with ID 99999999 not found"]`**: the most useful error Procore returns, because it is the only place in the whole exchange that names the entity out loud. The field is called `budget_code` and it wants a **WBS code** id. Those are the same thing under two names, and the error is where you find out.

That last one is also what you get if you post a **budget line item** id, which is a different object with its own ids. It looks like a plausible number and it is not the right one.

## The two that do not error

Both of these were created successfully and both landed on the project blank WBS code, id `1870469`, `flat_code: ""`:

```
"budget_code": { "segment_items": [ { "segment_id": 1, "segment_item_id": 2 } ] }
"budget_code": { }
```

The `segment_items` form is the one people reach for, because it mirrors how the work breakdown structure is actually shaped and it is what the WBS endpoints hand you. It is accepted and thrown away.

The empty object is worse, because it is what a serialiser produces when the code you meant to attach was `null`. Nothing in your code is obviously broken. Nothing in the response says anything is wrong. The line item exists, the amount is right, and the cost is filed where nobody looks.

**An integration built on either form passes its own tests.** It writes clean `200`s in production for months, and the first person to notice is whoever reconciles the budget.

![A cost table from the Fieldstub review queue. Five rows of labor, equipment and materials, each showing the named WBS code it will be filed against rather than a blank one: 03-300.L Cast-in-Place Concrete Labor, 03-300.E Equipment, 03-300.M Materials. The rows total $2,112.00 above a line reading "Where each line will be filed in Procore."](https://fieldstub.com/assets/shots/INB-04.png)

The reason a person is in this loop at all. A line with no code cannot be approved by accident, because the API it is going to will take one without complaining.

And the other half of that: a line the router could not place does not quietly go anywhere.

![A ticket in the Fieldstub review queue with one labor line showing "Not routed (do not file)" in its budget code column while the equipment and materials lines carry real codes. A warning below reads "1 line not on a budget code. Procore will accept a blank code with no warning. Tick the box if you really mean to file them blank," with an unticked checkbox labelled "File blank anyway".](https://fieldstub.com/assets/shots/INB-05.png)

The blank code is still reachable, because sometimes it is genuinely what somebody means. It just cannot happen without a person ticking a box that says so.

## How to get a real WBS code id

List them for the project:

```
GET /rest/v1.0/projects/{project_id}/work_breakdown_structure/wbs_codes
```

Each has an `id` and a `flat_code` that reads as cost code plus cost type letter, like `01-000.E`. The letters are **L**abor, **M**aterials, **E**quipment, **O**ther. Post the `id`, not the `flat_code`.

The catch, and it is the one that makes this hard rather than tedious: **a WBS code only exists for a cost code and cost type combination that is already in the budget.** Extra work is unplanned by definition, so the code you need is routinely the one that does not exist yet. [Procore budget codes and the blank code trap](https://fieldstub.com/docs/procore-budget-codes/) covers what to do about that.

## Checking whether it already happened to you

Read your line items back and look at the budget code you got, not the one you sent:

```
GET /rest/v1.0/change_events/{id}/line_items?project_id={project_id}
```

Any line where `budget_code.flat_code` is an empty string is filed blank. If you are seeing your own project blank code id repeated across line items that were meant to be different, that is this bug and not a data problem.

> Assert on the response, not the status. A test that checks for `200` passes on both of the broken forms. A test that checks the returned `budget_code.id` matches the one you sent catches it the first time.

## Questions

### What does "Wbs Code with ID not found" mean in Procore?

The id you sent in budget_code does not match any WBS code on that project. Procore calls the same object a budget code in the field name and a WBS code in the error. List them at /rest/v1.0/projects/{project_id}/work_breakdown_structure/wbs_codes and post the id from there.

### Why is Procore saying budget_code is missing when I sent a cost code?

A cost code is not a budget code. Line items need a WBS code id, which represents a cost code and cost type combination that exists in the project budget. Sending cost_code_id returns the same missing error.

### Why did my change event line item get a blank budget code with no error?

You sent either the segment_items form or an empty object. Both return 200, create the line item, and file it on the project blank WBS code. Verified against a Procore sandbox on 29 August 2026.

### Does Procore validate that a budget code was actually chosen?

No. It validates that the field is present and that an id, if you send one, resolves to a real WBS code. An empty object satisfies both checks and means nothing, so it passes.

## Related

- https://fieldstub.com/docs/procore-budget-codes.md
- https://fieldstub.com/docs/procore-change-events-api.md

---

Fieldstub captures extra construction work from people with no Procore seat and files it into Procore as a Change Event. https://fieldstub.com/
