# Reading data

How to ask the API for exactly the items you want. Choose fields, follow related items, filter, sort and page through long lists. Every list in the API works the same way, so the examples here use To-dos but apply to Rocks, Issues, Headlines and the rest.

URL: https://www.success.co/docs/guides/api-reading-data  
Published: 2026-11-12

## Choose your fields

In GraphQL you list the fields you want, and you get exactly those back. This asks for every To-do with its name and due date:

```graphql
query {
  todos {
    nodes {
      id
      name
      dueDate
    }
  }
}
```

Ask for less and requests get faster. Ask for more by adding field names; the [Data model](https://www.success.co/docs/guides/api-data-model) guide lists the useful ones for each object.

## Follow related items

Related items are fields too, so one request can bring back a To-do with its team, its owner and its status name:

```graphql
query {
  todos(first: 20) {
    nodes {
      name
      dueDate
      team { name }
      user { firstName lastName email }
      todoStatus { name }
    }
  }
}
```

It works in the other direction as well. Start from a team and list its people:

```graphql
query {
  teams {
    nodes {
      name
      usersOnTeams(filter: { stateId: { equalTo: "ACTIVE" } }) {
        nodes {
          user { firstName lastName jobTitle }
        }
      }
    }
  }
}
```

Lists inside an item (like `usersOnTeams` above) take the same filtering, sorting and paging options as top-level lists.

## Get one item

Singular names take an `id` and return that item, or `null` if it doesn't exist or you can't see it:

```graphql
query {
  rock(id: "8e5b2c1a-4d3f-4b6a-9c7e-1f2a3b4c5d6e") {
    name
    dueDate
    rockStatus { name }
    milestones { nodes { name dueDate milestoneStatusId } }
  }
}
```

## Use variables

Rather than pasting values into the query text, pass them as variables. It avoids escaping problems and lets you reuse the same query:

```graphql
query TodosForUser($userId: UUID!) {
  todos(filter: { userId: { equalTo: $userId }, stateId: { equalTo: "ACTIVE" } }) {
    nodes { id name dueDate }
  }
}
```

Send the variables next to the query in the request body:

```json
{
  "query": "query TodosForUser($userId: UUID!) { todos(filter: { userId: { equalTo: $userId }, stateId: { equalTo: \"ACTIVE\" } }) { nodes { id name dueDate } } }",
  "variables": { "userId": "5f2d8c4b-1a3e-4c7d-b9f0-2e6a7d8c9b10" }
}
```

## Filter

Pass `filter` to keep only the items that match. Each field takes one or more operators, and everything you list must match:

```graphql
query {
  todos(
    filter: {
      stateId: { equalTo: "ACTIVE" }
      todoStatusId: { equalTo: "TODO" }
      dueDate: { lessThan: "2026-12-01" }
    }
  ) {
    nodes { name dueDate team { name } }
  }
}
```

The operators you'll use most:

| Operator | Matches when the field | Example |
| --- | --- | --- |
| `equalTo` | is exactly the value | `stateId: { equalTo: "ACTIVE" }` |
| `notEqualTo` | is anything but the value | `todoStatusId: { notEqualTo: "COMPLETE" }` |
| `in` | is one of a list | `rockStatusId: { in: ["ONTRACK", "OFFTRACK"] }` |
| `notIn` | is none of a list | `type: { notIn: ["private"] }` |
| `isNull` | is empty (`true`) or set (`false`) | `dueDate: { isNull: false }` |
| `lessThan`, `lessThanOrEqualTo` | is before or below the value | `dueDate: { lessThan: "2026-12-01" }` |
| `greaterThan`, `greaterThanOrEqualTo` | is after or above the value | `updatedAt: { greaterThan: "2026-11-01T00:00:00Z" }` |
| `includesInsensitive` | contains the text | ignoring case |
| `startsWithInsensitive` | starts with the text | ignoring case |

Case-sensitive versions (`includes`, `startsWith`, `endsWith`) and their opposites (`notIncludes` and so on) work too.

To combine conditions in other ways, use `or`, `and` and `not`. This finds open To-dos that are either overdue or have no due date:

```graphql
query {
  todos(
    filter: {
      stateId: { equalTo: "ACTIVE" }
      todoStatusId: { equalTo: "TODO" }
      or: [
        { dueDate: { lessThan: "2026-11-12" } }
        { dueDate: { isNull: true } }
      ]
    }
  ) {
    nodes { name dueDate }
  }
}
```

Filter on the item's own fields, including the ID fields that point at related items (`teamId`, `userId`, `meetingId`). To find Rocks for one team, filter on its ID; [Data model](https://www.success.co/docs/guides/api-data-model#rocks-and-milestones) shows how.

For simple exact matches there's also `condition`, a shorter form of `equalTo`: `todos(condition: { teamId: "...", stateId: "ACTIVE" })`.

> **Note:** **Always filter out deleted items.** Deleting something in Success.co marks it with `stateId: "DELETED"` rather than removing it, so lists include deleted items unless you add `stateId: { equalTo: "ACTIVE" }`. Rocks also have `isArchived`, and Scorecard measurables have `dataFieldStatusId` (`ACTIVE` or `ARCHIVED`), for items people have archived rather than deleted.

## Sort

`orderBy` takes one or more sort orders, named after the field in capitals with `_ASC` or `_DESC`:

```graphql
query {
  issues(
    filter: { stateId: { equalTo: "ACTIVE" }, issueStatusId: { equalTo: "TODO" } }
    orderBy: [PRIORITY_NO_ASC, CREATED_AT_DESC]
  ) {
    nodes { number name priorityNo createdAt }
  }
}
```

Without `orderBy`, items come back in ID order, which isn't meaningful. Set it whenever order matters, and always when you page.

## Page through long lists

Lists return everything that matches unless you ask for less. For anything that could grow (To-dos, Issues, Scorecard values), fetch it in pages of a few hundred.

The simplest way is `first` and `offset`, plus `totalCount` to know when to stop:

```graphql
query TodosPage($offset: Int!) {
  todos(first: 200, offset: $offset, orderBy: CREATED_AT_ASC) {
    totalCount
    nodes { id name }
  }
}
```

For large or changing lists, cursors are more reliable, as items added mid-way can't shift the pages. Ask for `pageInfo`, then pass `endCursor` back as `after` until `hasNextPage` is `false`:

```graphql
query TodosPage($after: Cursor) {
  todos(first: 200, after: $after, orderBy: CREATED_AT_ASC) {
    pageInfo { hasNextPage endCursor }
    nodes { id name }
  }
}
```

A loop in JavaScript that collects every page:

```js
async function allTodos(gql) {
  const todos = []
  let after = null
  do {
    const data = await gql(QUERY, { after })
    todos.push(...data.todos.nodes)
    after = data.todos.pageInfo.hasNextPage ? data.todos.pageInfo.endCursor : null
  } while (after)
  return todos
}
```

`gql` here is a small helper that posts the query and variables, as in [Getting started](https://www.success.co/docs/guides/api-getting-started#make-your-first-request), and returns `data`.

## Count without fetching

Ask for `totalCount` and no `nodes` to count cheaply:

```graphql
query {
  openIssues: issues(filter: { stateId: { equalTo: "ACTIVE" }, issueStatusId: { equalTo: "TODO" } }) {
    totalCount
  }
  solvedIssues: issues(filter: { stateId: { equalTo: "ACTIVE" }, issueStatusId: { equalTo: "COMPLETE" } }) {
    totalCount
  }
}
```

`openIssues:` and `solvedIssues:` are aliases. They let you ask for the same list twice with different filters in one request, and name each answer.
