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.
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:
query {
todos {
nodes {
id
name
dueDate
}
}
}
Ask for less and requests get faster. Ask for more by adding field names; the 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:
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:
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:
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:
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:
{
"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:
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:
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 shows how.
For simple exact matches there's also condition, a shorter form of equalTo: todos(condition: { teamId: "...", stateId: "ACTIVE" }).
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:
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:
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:
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:
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, and returns data.
Count without fetching
Ask for totalCount and no nodes to count cheaply:
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.