# List positions

Operation ID: `psaPositions`

Group: PSA › Positions

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

`GET /psa/positions`

Read-only: Reads and returns; changes nothing.

Lists positions. The psaProjectId and the other reference ids on a position are numeric ids, not UUIDs, so you can't use them to read the project or any other record they point to.

Positions can't be filtered by name or description, and there is no search endpoint for positions.

## 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`. |

## Query surface

- `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like
- `psaProjectId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between
- `billable` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between
- `unitOfMeasure` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like
- `hourlyBillRateCurrency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like
- `hourlyCostRateCurrency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like
- `invoiced` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between
- `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between
- Orderable: id, psaProjectId, unitOfMeasure, lastModifiedDate
- Selectable: id, psaProjectId, name, description, salesOrderProductLineId, salesOrderProductLineRateCardEntryId, productId, productLineId, productLineRateCardEntryId, billable, unitOfMeasure, hourlyBillRate, hourlyBillRateCurrency, hourlyCostRate, hourlyCostRateCurrency, hoursBudget, invoiced, hrPositionId, skillLevelId, lastModifiedDate
- `page[size]` — Page size
- `page[cursor]` — Opaque continuation cursor

## Response

`200` Success

Under `data`, an array of psaPositionsRow:

| Field | Type | Required | Filter | Sort | Description |
| --- | --- | --- | --- | --- | --- |
| `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the position. Example: `"7c2e91a4-58fd-4b30-9e17-3a6d05fb824c"`. |
| `psaProjectId` | number | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The id of the PSA project this position belongs to. It is a numeric key, not the UUID that GET psa/projects/:id takes. Example: `32341`. |
| `name` | string \| null | no | no | no | The display name of the position. Example: `"Senior Consultant"`. |
| `description` | string \| null | no | no | no | A free-form description of the position's responsibilities. Example: `"Leads the delivery workstream on site."`. |
| `salesOrderProductLineId` | number \| null | no | no | no | The id of the sales order product line this position fulfills. It is a numeric key. There is no endpoint for it. |
| `salesOrderProductLineRateCardEntryId` | number \| null | no | no | no | The id of the sales order product line rate card entry used for billing. It is a numeric key. There is no endpoint for it. |
| `productId` | number \| null | no | no | no | The id of the product this position is billed against. It is a numeric key, not the UUID that GET accounting/products/:id takes. |
| `productLineId` | number \| null | no | no | no | The id of the product line this position is billed against. It is a numeric key, not the UUID that GET accounting/product-lines/:id takes. |
| `productLineRateCardEntryId` | number \| null | no | no | no | The id of the product line rate card entry. It is a numeric key. There is no endpoint for it. |
| `billable` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether work in this position is billable to the customer. Example: `true`. |
| `unitOfMeasure` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unit the stored bill rate, cost rate and budget are expressed in. It is a member of the UnitOfMeasureType enum, in practice 'hour' or 'day' for a position, with 'hour' the default. Example: `"hour"`. |
| `hourlyBillRate` | decimal \| null | no | no | no | The bill rate for this position, as an hourly amount, in the currency given by hourlyBillRateCurrency. Example: `"185.00"`. |
| `hourlyBillRateCurrency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The currency of hourlyBillRate. Format ISO 4217, for example 'USD'. Example: `"USD"`. |
| `hourlyCostRate` | decimal \| null | no | no | no | The cost rate for this position, as an hourly amount, in the currency given by hourlyCostRateCurrency. Example: `"110.00"`. |
| `hourlyCostRateCurrency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The currency of hourlyCostRate. Format ISO 4217, for example 'USD'. Example: `"USD"`. |
| `hoursBudget` | decimal \| null | no | no | no | The budgeted hours for this position. It is a quantity in hours and has no currency. Example: `"120.00"`. |
| `invoiced` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether work in this position has been invoiced. Example: `false`. |
| `hrPositionId` | number \| null | no | no | no | The id of the HR position this PSA position maps to. It is a numeric key, not the UUID that GET people/positions/:id takes. |
| `skillLevelId` | number \| null | no | no | no | The id of the skill level (position seniority) this PSA position maps to. It is a numeric key. There is no endpoint for it. |
| `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the position record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Example: `"2026-08-14T11:20:31.004Z"`. |

```json
{
  "data": [
    {
      "id": "7c2e91a4-58fd-4b30-9e17-3a6d05fb824c",
      "psaProjectId": 32341,
      "name": "Senior Consultant",
      "description": "Leads the delivery workstream on site.",
      "salesOrderProductLineId": 0,
      "salesOrderProductLineRateCardEntryId": 0,
      "productId": 0,
      "productLineId": 0,
      "productLineRateCardEntryId": 0,
      "billable": true,
      "unitOfMeasure": "hour",
      "hourlyBillRate": "185.00",
      "hourlyBillRateCurrency": "USD",
      "hourlyCostRate": "110.00",
      "hourlyCostRateCurrency": "USD",
      "hoursBudget": "120.00",
      "invoiced": false,
      "hrPositionId": 0,
      "skillLevelId": 0,
      "lastModifiedDate": "2026-08-14T11:20:31.004Z"
    }
  ],
  "pagination": {
    "hasMore": true,
    "nextCursor": "<nextCursor>"
  }
}
```

## 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
