# List a project's expense budgets

Operation ID: `getExpenseBudgets`

Group: PSA › Expenses

[Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getExpenseBudgets)

`GET /psa/expenses/budgets`

Read-only: Reads and returns; changes nothing.

Lists the expense budgets defined for a PSA project. Send the project's numeric psaProjectId in the query string. You need view access to that project.

This endpoint does not follow the standard collection rules in Pagination and querying.

- Page with `pageSize` (default 50, maximum 200, minimum 1) and `cursor`, not `page[size]` and `page[cursor]`.
- The response contains the records in `data.items` and the next page's cursor in `data.pagination.nextCursor`.
- Send `psaProjectId`, which is required. To get only the default budget, send `isDefault=true`.
- There is no `sort` or `fields`.

This endpoint ignores any query parameter it does not declare, and returns no error. A request that follows the standard rules therefore gets the first page, unfiltered, every time.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. |
| `psaProjectId` | query | number | yes | The id of the PSA project. It is the numeric key, not the UUID that GET psa/projects/:id takes. Example: `32341`. |
| `isDefault` | query | boolean | no | When true, return only the project's default catch-all budget. When false or omitted, return all of the project's budgets. False does not filter to non-default budgets. |
| `cursor` | query | string | no | The cursor from the previous response's data.pagination.nextCursor. Omit on the first request. |
| `pageSize` | query | number | no | The number of budgets per page, between 1 and 200. Defaults to 50. A value above 200 is treated as 200, not rejected. Example: `50`. |

## Response

`200` Success

Under `data` (getExpenseBudgetsOutput):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | array<object> | yes | The project's budgets on this page. An empty array when the project has no budgets. Example: `[{"id":501,"name":"Travel","budget":"10000.00","currency":"USD","isDefault":false,"billable":true,"lastModifiedDate":"2026-07-02T09:14:22.000Z"},{"id":502,"name":"General","budget":"5000.00","currency":"USD","isDefault":true,"lastModifiedDate":"2026-06-18T11:02:47.000Z"}]`. |
| `items[].id` | number | yes | The unique identifier for the budget. |
| `items[].name` | string | yes | The display name of the budget. |
| `items[].budget` | decimal | no | The budget amount, in the currency given by currency. Absent when the budget has no amount. |
| `items[].currency` | string | no | The currency of budget. Format ISO 4217, for example 'USD'. Absent when the budget has no amount. |
| `items[].isDefault` | boolean | yes | Whether this is the project's default catch-all budget. |
| `items[].lastModifiedDate` | date-time | no | The date and time the budget definition last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. |
| `items[].billable` | boolean | no | Whether expenses against this budget are billable to the customer. |
| `items[].productId` | number | no | The id of the product tied to this budget. It is a numeric key, not the UUID that GET accounting/products/:id takes. Absent when the budget is not tied to a product. |
| `items[].productLineId` | number | no | The id of the product line tied to this budget. It is a numeric key, not the UUID that GET accounting/product-lines/:id takes. Absent when the budget is not tied to a product line. |
| `items[].salesOrderId` | number | no | The id of the sales order this budget bills against. It is a numeric key, not the UUID that GET accounting/sales-orders/:id takes. |
| `pagination` | object | yes | The pagination cursor for this page. Example: `{"hasMore":false}`. |
| `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as the cursor query parameter. Absent on the last page. |
| `pagination.hasMore` | boolean | yes | True when more budgets remain beyond this page. |

```json
{
  "data": {
    "items": [
      {
        "id": 501,
        "name": "Travel",
        "budget": "10000.00",
        "currency": "USD",
        "isDefault": false,
        "billable": true,
        "lastModifiedDate": "2026-07-02T09:14:22.000Z"
      },
      {
        "id": 502,
        "name": "General",
        "budget": "5000.00",
        "currency": "USD",
        "isDefault": true,
        "lastModifiedDate": "2026-06-18T11:02:47.000Z"
      }
    ],
    "pagination": {
      "hasMore": false
    }
  },
  "diagnostics": [
    {
      "code": "<code>",
      "message": "<message>",
      "severity": "error",
      "path": [
        "<path>"
      ],
      "details": "<details>"
    }
  ]
}
```

## Statuses

- `400` — Invalid request or business failure
- `401` — The request carries no valid access token.
- `403` — Not authorized
- `404` — Not found
- `429` — Too many requests from this client address. The response has no body and no `Retry-After` header.
- `500` — Internal server error
