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.

note

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