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:

GraphQL
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.

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:

OperatorMatches when the fieldExample
equalTois exactly the valuestateId: { equalTo: "ACTIVE" }
notEqualTois anything but the valuetodoStatusId: { notEqualTo: "COMPLETE" }
inis one of a listrockStatusId: { in: ["ONTRACK", "OFFTRACK"] }
notInis none of a listtype: { notIn: ["private"] }
isNullis empty (true) or set (false)dueDate: { isNull: false }
lessThan, lessThanOrEqualTois before or below the valuedueDate: { lessThan: "2026-12-01" }
greaterThan, greaterThanOrEqualTois after or above the valueupdatedAt: { greaterThan: "2026-11-01T00:00:00Z" }
includesInsensitivecontains the textignoring case
startsWithInsensitivestarts with the textignoring 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 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:

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:

JavaScript
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:

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.