Skip to main content

Moving from REST to GraphQL

With the introduction of Zenhub's GraphQL API, you now have the ability to access many of the same features we use to build Zenhub. We know that many people will want to make the switch from the REST API to the new GraphQL API, so we've put together a guide to make this easier.

Getting Set Up

To start making the switch from the REST API to the GraphQL API, take a look at the Getting Started document to learn about setting up your Personal API Key and what URL the GraphQL API lives at. Once you have a Personal API Key set up, continue reading this guide.

If you're new to the world of GraphQL, visit the official GraphQL Docs to learn more about how to interact with GraphQL APIs.

Updating REST Endpoints

The bulk of the work around moving from the REST API to the GraphQL API will be migrating API calls and handling the different data structures. With the GraphQL API, you'll have much more flexibility around what data you ask for. We've generated a list below that matches REST endpoints to their equivalents in the GraphQL API. You can use this as a starting point to figure out what the equivalent query or mutation may look like. We highly recommend using our Explorer to not only see what the results of these calls look like, but to fine-tune these calls and fetch the exact data that you need.

Issues

Get Issue Data
# Sample variables:
# {
#   "workspaceId": "some_workspace_id",
#   "repositoryGhId": 1234,
#   "issueNumber": 1
# }

query getIssueInfo($repositoryGhId: Int!, $issueNumber: Int!, $workspaceId: ID!) {
  issueByInfo(repositoryGhId: $repositoryGhId, issueNumber: $issueNumber) {
    id
    repository {
      id
      ghId
    }
    number
    title
    body
    state
    pipelineIssue(workspaceId: $workspaceId) {
      priority {
        id
        name
        color
      }
      pipeline {
        id
        name
      }
    }
    estimate {
      value
    }
    sprints(first: 10) {
      nodes {
        id
        name
      }
    }
    labels(first: 10) {
      nodes {
        id
        name
        color
      }
    }
  }
}

Get Issue Events
# Sample variables:
# {
#   "repositoryGhId": 1234,
#   "issueNumber": 1
# }

query getTimelineItems($repositoryGhId: Int!, $issueNumber: Int!) {
  issueByInfo(repositoryGhId: $repositoryGhId, issueNumber: $issueNumber) {
    id
    timelineItems(first: 50) {
      nodes {
        type: key
        id
        data
        createdAt
      }
    }
  }
}

Move an Issue Between Pipelines
# Sample variables:
# {
#   "workspaceId": "some_workspace_id",
#   "moveIssueInput": {
#     "pipelineId": "some_pipeline_id",
#     "issueId": "some_issue_id",
#     "position": 0
#   }
# }

mutation moveIssue($moveIssueInput: MoveIssueInput!, $workspaceId: ID!) {
  moveIssue(input: $moveIssueInput) {
    issue {
      id
      pipelineIssue(workspaceId: $workspaceId) {
        priority {
          id
          name
          color
        }
        pipeline {
          id
        }
      }
    }
  }
}

Move an Issue Between Pipelines in the Oldest Workspace

This is legacy functionality that no longer has a place in Zenhub. The idea of treating the oldest workspace as special is no longer a thing. If this is functionality you still wish to use, you may fetch the oldest workspace for a given Repository in one query (see "Get the Oldest Zenhub Board for a Repo" for how), then use those details to move an issue with the mutation under "Move an Issue Between Pipelines".

Set Issue Estimate
# Sample variables:
# {
#   "setEstimateInput": {
#     "issueId": "some_issue_id",
#     "value": 3
#   }
# }

mutation setEstimate($setEstimateInput: SetEstimateInput!) {
  setEstimate(input: $setEstimateInput) {
    issue {
      id
      estimate {
        value
      }
    }
  }
}

Epics

Get Epics for a Repository
# Sample variables:
# {
#   "workspaceId": "some_workspace_id",
#   "repositoryGhIds": [1234]
# }

query getEpicsForRepository($workspaceId: ID!, $repositoryGhIds: [Int!]) {
  workspace(id: $workspaceId) {
    id
    epics(repositoryGhIds: $repositoryGhIds, first: 50) {
      nodes {
        id
        issue {
          id
          title
          number
          htmlUrl
          repository {
            id
            name
          }
        }
      }
    }
  }
}

Get Epic Data
# Sample variables:
# {
#   "workspaceId": "some_workspace_id",
#   "epicIds": ["some_epic_id"]
# }

query getEpicsById($workspaceId: ID!, $epicIds: [ID!]) {
  workspace(id: $workspaceId) {
    id
    epics(ids: $epicIds) {
      nodes {
        id
        issue {
          id
          title
          number
          htmlUrl
          repository {
            id
            name
          }
        }
      }
    }
  }
}

Convert an Epic to an Issue
# Sample variables:
# {
#   "deleteEpicByIssueInfoInput": {
#     "issue": {
#       "repositoryGhId": 1234,
#       "issueNumber": 1
#     }
#   }
# }

mutation convertEpicToIssue($deleteEpicByIssueInfoInput: DeleteEpicByIssueInfoInput!) {
  deleteEpicByIssueInfo(input: $deleteEpicByIssueInfoInput) {
    epic {
      id
      issue {
        id
      }
    }
  }
}

Convert an Issue to an Epic
# Sample variables:
# {
#   "createEpicFromIssueInput": {
#     "issueId": "some_issue_id",
#     "epicChildIds": []
#   }
# }

mutation convertIssueToEpic($createEpicFromIssueInput: CreateEpicFromIssueInput!) {
  createEpicFromIssue(input: $createEpicFromIssueInput) {
    epic {
      id
      issue {
        id
      }
    }
  }
}

Add Issues to an Epic
# Sample variables:
# {
#   "addIssuesToEpicsInput": {
#     "issueIds": ["some_issue_id"],
#     "epicIds": ["some_epic_id"]
#   }
# }

mutation addIssuesToEpics($addIssuesToEpicsInput: AddIssuesToEpicsInput!) {
  epics {
    id
    childIssues(first: 50) {
      nodes {
        id
      }
    }
  }
}

Remove Issues from an Epic
# Sample variables:
# {
#   "removeIssuesFromEpicsInput": {
#     "issueIds": ["some_issue_id"],
#     "epicIds": ["some_epic_id"]
#   }
# }

mutation removeIssuesFromEpics($removeIssuesFromEpicsInput: RemoveIssuesFromEpicsInput!) {
  removeIssuesFromEpics(input: $removeIssuesFromEpicsInput) {
    epics {
      id
      childIssues(first: 50) {
        nodes {
          id
        }
      }
    }
  }
}

Workspace

Get Zenhub Workspaces for a Repo
# Sample variables:
# {
#   repositoryGhId: 1234
# }

query getWorkspacesByRepo($repositoryGhId: Int!) {
  repositoryByGhId(ghId: $repositoryGhId) {
    id
    workspacesConnection(first: 50) {
      nodes {
        id
        name
        description
        repositoriesConnection(first: 50) {
          nodes {
            id
            ghId
            name
          }
        }
      }
    }
  }
}

Get a Zenhub Board for a Repo

While it's possible to request the entire Board in one query, we don't recommend doing this as you could run into rate limiting issues. We also enforce you to specify a page size for fields that return a list, so if you have more results than can fit on a single page, you'll have to deal with pagination. Instead, we'd recommend splitting this up into the following queries which have the added bonus of allowing you to supply filters which can be used to limit the results to only issues that match the provided filters.

First, query for the pipelines that belong to a given workspace

# Sample variables:
# {
#   "workspaceId": "some_workspace_id"
# }

query getPipelinesForWorkspace($workspaceId: ID!) {
  workspace(id: $workspaceId) {
    id
    pipelinesConnection(first: 50) {
      nodes {
        id
        name
      }
    }
  }
}

Next, send 1 query per pipeline that returns the issues that belong to that pipeline. The following sample query works for any pipeline that is not the closed pipeline. Querying for issues in the closed pipeline will require you to use the searchClosedIssues query.

# Sample variables:
# {
#   "pipelineId": "some_pipeline_id",
#   "filters": {}
# }

query getIssuesByPipeline($pipelineId: ID!, $filters: IssueSearchFiltersInput!) {
  searchIssuesByPipeline(pipelineId: $pipelineId, filters: $filters, first: 50) {
    nodes {
      id
      ghId
      number
      title
      body
    }
  }
}

If you really want to try and fetch the board at once, here's the query:

# Sample variables:
# {
#   "workspaceId": "some_workspace_id"
# }

query getBoardInfoForWorkspace($workspaceId: ID!) {
  workspace(id: $workspaceId) {
    id
    pipelinesConnection(first: 25) {
      nodes {
        id
        name
        issues(first: 100) {
          nodes {
            id
            title
            number
            estimate {
              value
            }
          }
        }
      }
    }
  }
}

Get the Oldest Zenhub Board for a Repo

To fetch the oldest workspace for a given Repository, you may simply ask for the first workspace connection as part of the Repository query. By default, they are ordered such that the oldest workspace is returned first.

# Sample variables:
# {
#   "repositoryGhId": 1234
# }

query getOldestWorkspace($repositoryGhId: Int!) {
  repository(ghId: $repositoryGhId) {
    workspacesConnection(first:1) {
      nodes {
        id
        name
        createdAt
        pipelinesConnection(first: 25) {
          nodes {
            id
            name
          }
        }
      }
    }
  }
}

Milestones

Milestones are being deprecated. We recommend using Sprints instead. Head on over to our Explorer to begin digging into how Sprints can be used today.

Dependencies

Get Dependencies for a Repository

The following query is slightly different from the original REST endpoint, but we believe should function well in its place and is actually a little more powerful. Rather than asking for the dependencies that belong to a specific repository, you instead ask for dependencies that are part of a certain workspace. This can be used to return all blocked and blocking issue pairs that are part of the specified workspace. If you would like to restrict this to a certain repository in the workspace, you may provide that repository's GitHub ID as a filter value to this query.

# Sample variables:
# {
#   "workspaceId": "some_workspace_id",
#   "repositoryGhIds": [1234]
# }

query getWorkspaceDependencies($workspaceId: ID!, $repositoryGhIds: [Int!]) {
  workspace(id: $workspaceId) {
    issueDependencies(first: 50, repositoryGhIds: $repositoryGhIds) {
      nodes {
        id
        updatedAt
        blockedIssue {
          id
          number
          repository {
            id
            ghId
          }
        }
        blockingIssue {
          id
          number
          repository {
            id
            ghId
          }
        }
      }
    }
  }
}

Create a Dependency
# Sample variables:
# {
#   "createIssueDependencyInput": {
#     "blockedIssue": {
#       "repositoryGhId": 1234,
#       "issueNumber": 1
#     },
#     "blockingIssue": {
#       "repositoryGhId": 1234,
#       "issueNumber": 2
#     }
#   }
# }

mutation createIssueDependency($createIssueDependencyInput: CreateIssueDependencyInput!) {
  createIssueDependency(input: $createIssueDependencyInput) {
    issueDependency {
      blockedIssue {
        id
        number
        title
        blockingIssues(first: 25) {
          nodes {
            id
            number
            title
          }
        }
      }
      blockingIssue {
        id
        number
        title
        blockedIssues(first: 25) {
          nodes {
            id
            number
            title
          }
        }
      }
    }
  }
}
Remove a Dependency
# Sample variables:
# {
#   "deleteIssueDependencyInput": {
#     "blockedIssue": {
#       "repositoryGhId": 92617791,
#       "issueNumber": 81
#     },
#     "blockingIssue": {
#       "repositoryGhId": 92617791,
#       "issueNumber": 1
#     }
#   }
# }

mutation removeIssueDependency($deleteIssueDependencyInput: DeleteIssueDependencyInput!) {
  deleteIssueDependency(input: $deleteIssueDependencyInput) {
    issueDependency {
      blockedIssue {
        id
        number
        title
        blockingIssues(first: 25) {
          nodes {
            id
            number
            title
          }
        }
      }
      blockingIssue {
        id
        number
        title
        blockedIssues(first: 25) {
          nodes {
            id
            number
            title
          }
        }
      }
    }
  }
}

Release Reports

Create a Release Report
# Sample variables:
# {
#   "createReleaseInput": {
#     "release": {
#       "title": "Example Release Report",
#       "description": "A release report description",
#       "startOn": "2022-01-01T04:14:53.800Z",
#       "endOn": "2022-04-01T04:14:53.800Z",
#       "repositoryGhIds": [1234]
#     }
#   }
# }

mutation createReleaseReport($createReleaseInput: CreateReleaseInput!) {
  createRelease(input: $createReleaseInput) {
    release {
      id
      title
      description
      startOn
      endOn
      issues(first: 50) {
        nodes {
          id
        }
      }
    }
  }
}

Get a Release Report
# Sample variables:
# {
#   "reportId": "some_report_id"
# }

query getReleaseReport($reportId: ID!) {
  release(id: $reportId) {
    id
    title
    description
    startOn
    endOn
    issues(first: 50) {
      nodes {
        id
        title
        number
      }
    }
  }
}
Get Release Reports for a Repository
# Sample variables:
# {
#   "repositoryGhId": 1234
# }

query getRepositoryReleaseReports($repositoryGhId: Int!) {
  repositoryByGhId(ghId: $repositoryGhId) {
    id
    releases(first: 50) {
      nodes {
        id
        title
        description
        startOn
        endOn
        issues(first: 50) {
          nodes {
            id
            title
            number
          }
        }
      }
    }
  }
}
Edit a Release Report
# Sample variables:
# {
#   "updateReleaseInput": {
#     "releaseId": "some_release_id",
#     "release": {
#       "title": "Example Release Report",
#       "description": "A release report description",
#       "startOn": "2022-01-01T04:14:53.800Z",
#       "endOn": "2022-04-01T04:14:53.800Z",
#     }
#   }
# }
mutation updateReleaseReport($updateReleaseInput: UpdateReleaseInput!) {
updateRelease(input: $updateReleaseInput) {
  release {
    id
    title
    description
    startOn
    endOn
  }
}
}

Add a Repository for a Release Report
# Sample variables:
# {
#   "addRepositoriesToReleaseInput": {
#     "releaseId": "some_releaseId",
#     "repositoryGhIds": [1234]
#   }
# }

mutation addRepositoriesToReleaseReport($addRepositoriesToReleaseInput: AddRepositoriesToReleaseInput!) {
  addRepositoriesToRelease(input: $addRepositoriesToReleaseInput) {
    release {
      id
      repositories(first: 50) {
        nodes {
          id
          ghId
        }
      }
    }
  }
}
Remove a Repository for a Release Report
# Sample variables:
# {
#   "removeRepositoriesFromReleaseInput": {
#     "releaseId": "some_release_id",
#     "repositoryGhIds": [1234]
#   }
# }

mutation removeRepositoriesFromReleaseReport($removeRepositoriesFromReleaseInput: RemoveRepositoriesFromReleaseInput!) {
  removeRepositoriesFromRelease(input: $removeRepositoriesFromReleaseInput) {
    release {
      id
      repositories(first: 50) {
        nodes {
          id
          ghId
        }
      }
    }
  }
}

Release Report Issues

Get All Issues in a Release Report
# Sample variables:
# {
#   "reportId": "some_report_id"
# }

query getReleaseReportIssues($reportId: ID!) {
  release(id: $reportId) {
    id
    issues(first: 50) {
      nodes {
        id
        title
        number
      }
    }
  }
}
Add Issues to a Release Report
# Sample variables:
# {
#   "addIssuesToReleasesInput": {
#     "issueIds": ["some_issue_id"],
#     "releaseIds": ["some_release_id"]
#   }
# }

mutation addIssuesToRelease($addIssuesToReleasesInput: AddIssuesToReleasesInput!) {
  addIssuesToReleases(input: $addIssuesToReleasesInput) {
    releases {
      id
      issues(first: 50) {
        nodes {
          id
        }
      }
    }
  }
}
Remove Issues from a Release Report
# Sample variables:
# {
#   "removeIssuesFromReleasesInput": {
#     "issueIds": [],
#     "releaseIds": []
#   }
# }

mutation removeIssuesFromReleases($removeIssuesFromReleasesInput: RemoveIssuesFromReleasesInput!) {
  removeIssuesFromReleases(input: $removeIssuesFromReleasesInput) {
    releases {
      id
      issues(first: 50) {
        nodes {
          id
        }
      }
    }
  }
}