This describes the current version of the public ZenHub API. If you have any questions or feedback, please contact support.
The root endpoint for the public API is https://api.zenhub.com/. For ZenHub Enterprise, refer to the instructions in https://<zenhub_enterprise_host>/setup/howto/api.
All requests to the API need an API token. Generate a token in the API Tokens section of your ZenHub Dashboard. The token is sent in the X-Authentication-Token header. For example, using curl it’d be:
curl -H 'X-Authentication-Token: TOKEN' URLAlternatively, you can choose to send the token in the URL using the access_token query string attribute. To do so, add ?access_token=TOKEN to any URL.
- Each user may only have one token, so generating a new token will invalidate previously created tokens.
- For ZenHub Enterprise users, please follow the instructions in
https://<zenhub_enterprise_host>/setup/howto/api
We allow a maximum of 100 requests per minute to our API. All requests responses include the following headers related to this limitation.
| Header | Description |
|---|---|
X-RateLimit-Limit |
Total number of requests allowed before the reset time |
X-RateLimit-Used |
Number of requests sent in the current cycle. Will be set to 0 at the reset time. |
X-RateLimit-Reset |
Time in UTC epoch seconds when the usage gets reset. |
To avoid time differences between your computer and our servers, we suggest to use the Date header in the response to know exactly when the limit is reset.
The ZenHub API can return the following errors:
| Status Code | Description |
|---|---|
401 |
The token is not valid. See Authentication. |
403 |
Reached request limit to the API. See API Limits. |
404 |
Not found. |
repo_idis the ID of the repository, not its full name. For example, the ID of theZenHubIO/APIrepository is47655910. To find out the ID of your repository, use GitHub’s API, or copy it from the URL of the Board (for this repo, the Board URL is https://github.com/ZenHubIO/API#boards?repos=47655910).
Get the data for a specific issue.
GET /p1/repositories/:repo_id/issues/:issue_number
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
issue_number |
Number |
Required |
{
"estimate": {
"value": 8
},
"plus_ones": [
{
"user_id": 16717,
"created_at": "2015-12-11T18:43:22.296Z"
}
],
"pipeline": {
"name": "In Progress"
},
"is_epic": true
}- Closed issues might take up to one minute to show up in the Closed Pipeline. Similarly, reopened issues might take up to one minute to show in the correct Pipeline.
Get the events for an issue.
GET /p1/repositories/:repo_id/issues/:issue_number/events
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
issue_number |
Number |
Required |
[
{
"user_id": 16717,
"type": "estimateIssue",
"created_at": "2015-12-11T19:43:22.296Z",
"from_estimate": {
"value": 8
}
},
{
"user_id": 16717,
"type": "estimateIssue",
"created_at": "2015-12-11T18:43:22.296Z",
"from_estimate": {
"value": 4
},
"to_estimate": {
"value": 8
}
},
{
"user_id": 16717,
"type": "estimateIssue",
"created_at": "2015-12-11T13:43:22.296Z",
"to_estimate": {
"value": 4
}
},
{
"user_id": 16717,
"type": "transferIssue",
"created_at": "2015-12-11T12:43:22.296Z",
"from_pipeline": {
"name": "Backlog"
},
"to_pipeline": {
"name": "In progress"
}
},
{
"user_id": 16717,
"type": "transferIssue",
"created_at": "2015-12-11T11:43:22.296Z",
"to_pipeline": {
"name": "Backlog"
}
}
]- Returns issue events, sorted by creation time, most recent first.
- Each event contains the User ID of the user who performed the change, the Creation Date of the event, and the event Type.
- Type can be either
estimateIssueortransferIssue. The values before and after the event are included in the event data.
Moves an issue between the Pipelines in your repository.
POST /p1/repositories/:repo_id/issues/:issue_number/moves
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
issue_number |
Number |
Required |
| Name | Type | Comments |
|---|---|---|
pipeline_id |
String |
Required |
position |
String or Number |
Required |
pipeline_idis the ID for one of the Pipelines in your repository (i.e: In Progress, Done, QA). In order to obtain this ID, you can use the Get Board Data for a Repository` endpoint.positioncan be specified astoporbottom, or a0-based position in the Pipeline such as1, which would be the second position in the Pipeline.
{
"pipeline_id": "58bf13aba426771426665e60",
"position": "top"
}Status 200 for a successful move. No response body.
PUT /p1/repositories/:repo_id/issues/:issue_number/estimate
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
issue_number |
Number |
Required |
| Name | Type | Comments |
|---|---|---|
estimate |
Number |
Required, number representing estimate value |
{ "estimate": 15 }{ "estimate": 15 }- Get Epics for a Repository
- Get Epic Data
- Convert an Epic to an Issue
- Convert an Issue to Epic
- Add or Remove Issues from an Epic
Get all Epics for a repository
GET /p1/repositories/:repo_id/epics
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
{
"epic_issues": [
{
"issue_number": 3953,
"repo_id": 1234567,
"issue_url": "https://github.com/RepoOwner/RepoName/issues/3953"
},
{
"issue_number": 1342,
"repo_id": 1234567,
"issue_url": "https://github.com/RepoOwner/RepoName/issues/1342"
},
]
}- The endpoint returns an array of the repository’s Epics. The issue number, repository ID, and GitHub issue URL is provided for each Epic.
- If an issue is only an issue belonging to an Epic (and not a parent Epic), it is not considered an Epic and won’t be included in the return array.
Get the data for an Epic issue.
GET /p1/repositories/:repo_id/epics/:epic_id
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
epic_id |
Number |
Required, Github issue number |
epic_idis the GitHub issue number. You may fetch the list of Epics usingGet Epics for a repositoryendpoint.
{
"total_epic_estimates": { "value": 60 },
"estimate": { "value": 10 },
"pipeline": { "name": "Backlog" },
"issues": [
{
"issue_number": 3161,
"is_epic": true,
"repo_id": 1099029,
"estimate": { "value": 40 },
"pipeline": { "name": "New Issues" }
},
{
"issue_number": 2,
"is_epic": false,
"repo_id": 1234567,
"estimate": { "value": 10 },
"pipeline": { "name": "New Issues" }
},
{
"issue_number": 1,
"is_epic": false,
"repo_id": 1234567
},
{
"issue_number": 6,
"is_epic": false,
"repo_id": 1234567
},
{
"issue_number": 7,
"is_epic": true,
"repo_id": 9876543
}
]
}The endpoint returns:
- the total Epic Estimate value (the sum of all the Estimates of Issues contained within the Epic, as well as the Estimate of the Epic itself)
- the Estimate of the Epic
- the name of the Pipeline the Epic is in
- issues belonging to the Epic
For each issue belonging to the Epic:
- issue number
- repo ID
- Estimate value
is_epicflag (trueorfalse)- if the issue is from the same repository as the Epic, the ZenHub Board’s Pipeline name (from the repo the Epic is in) is attached.
Converts an Epic back to a regular issue.
POST /p1/repositories/:repo_id/epics/:issue_number/convert_to_issue
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
issue_number |
Number |
Required, the number of the issue to be converted |
200if the issue was converted to Epic successfully
Does not return any body in the response.
Converts an issue to an Epic, along with any issues that should be part of it.
POST /p1/repositories/:repo_id/issues/:issue_number/convert_to_epic
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
issue_number |
Number |
Required |
| Name | Type | Comments |
|---|---|---|
issues |
[{repo_id: Number, issue_number: Number}] |
Required, array of Objects with repo_id and issue_number |
{
"issues": [
{ "repo_id": 13550592, "issue_number": 3 },
{ "repo_id": 13550592, "issue_number": 1 }
]
}Does not return any body in the response.
200if the issue was converted to Epic successfully400if the supplied issue is already an Epic
Bulk add or remove issues to an Epic. The result returns which issue was added or removed from the Epic.
POST /p1/repositories/:repo_id/epics/:issue_number/update_issues
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
issue_number |
Number |
Required |
| Name | Type | Comments |
|---|---|---|
remove_issues |
[{repo_id: Number, issue_number: Number}] |
Required, array of Objects with repo_id and issue_number |
add_issues |
[{repo_id: Number, issue_number: Number}] |
Required, array of Objects with repo_id and issue_number |
{
"remove_issues": [
{ "repo_id": 13550592, "issue_number": 3 }
],
"add_issues": [
{ "repo_id": 13550592, "issue_number": 2 },
{ "repo_id": 13550592, "issue_number": 1 }
]
}remove_issuesis an array that indicates with issues we want to remove from the specified Epic. They should be specified as an array containing objects with the issue’srepo_idandissue_number.add_issuesis an array that indicates with issues we want to add to the specified Epic. They should be specified as an array containing objects with the issue’srepo_idandissue_number.
{
"removed_issues":[
{ "repo_id": 3887883, "issue_number": 3 }
],
"added_issues":[
{ "repo_id": 3887883, "issue_number": 2 },
{ "repo_id": 3887883, "issue_number": 1 }
]
}removed_issuesshows which issues were removed in this operation.add_issuesshows which issues were added in this operation.- Returns a
404if the Epic doesn’t exist
GET /p1/repositories/:repo_id/board
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
{
"pipelines": [
{
"id": "595d430add03f01d32460080",
"name": "New Issues",
"issues": [
{
"issue_number": 279,
"estimate": { "value": 40 },
"position": 0,
"is_epic": true
},
{
"issue_number": 142,
"is_epic": false
}
]
},
{
"id": "595d430add03f01d32460081",
"name": "Backlog",
"issues": [
{
"issue_number": 303,
"estimate": { "value": 40 },
"position": 3,
"is_epic": false
}
]
},
{
"id": "595d430add03f01d32460082",
"name": "To Do",
"issues": [
{
"issue_number": 380,
"estimate": { "value": 1 },
"position": 0,
"is_epic": true
},
{
"issue_number": 284,
"position": 2,
"is_epic": false
},
{
"issue_number": 329,
"estimate": { "value": 8 },
"position": 7,
"is_epic": false
}
]
}
]
}- The endpoint returns the Board’s pipelines, plus the issues contained within each Pipeline. It returns the issue number of each issue, their position in the Board, the
is_epicflag (trueorfalse), and its Estimate (if set). - Even if the issues are returned in the right order, the position can’t be guessed from its index. Note that some issues won’t have position – this is because they have not been prioritized on your Board.
- The Board returned by the endpoint doesn’t include closed issues. To get closed issues for a repository, you can use the GitHub API. Reopened issues might take up to one minute to appear in the correct Pipeline.
POST /p1/repositories/:repo_id/milestones/:milestone_number/start_date
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
milestone_number |
Number |
Required |
| Name | Type | Comments |
|---|---|---|
start_date |
ISO8601 date string | Required |
{ "start_date": "2010-11-13T01:38:56.842Z" }{ "start_date": "2010-11-13T01:38:56.842Z" }GET /p1/repositories/:repo_id/milestones/:milestone_number/start_date
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
milestone_number |
Number |
Required |
{ "start_date": "2010-11-13T01:38:56.842Z" }- Create a Release Report
- Get a Release Report
- Get Release Reports for a Repository
- Edit a Release Report
- Add Workspaces to a Release Report
- Remove Workspaces from a Release Report
POST /p1/repositories/:repo_id/reports/release
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
| Name | Type | Comments |
|---|---|---|
title |
String |
Required |
description |
String |
Optional |
start_date |
ISO8601 date string | Optional |
desired_end_date |
ISO8601 date string | Optional |
repositories |
[Number] |
Optional |
{
"title": "Great title",
"description": "Amazing description",
"start_date": "2007-01-01T00:00:00Z",
"desired_end_date": "2007-01-01T00:00:00Z",
"repositories": [
103707262
]
}{
"release_id": "59dff4f508399a35a276a1ea",
"title": "Great title",
"description": "Amazing description",
"start_date": "2007-01-01T00:00:00.000Z",
"desired_end_date": "2007-01-01T00:00:00.000Z",
"created_at": "2017-10-12T23:04:21.795Z",
"closed_at": null,
"state": "open",
"repositories": [
103707262
]
}- The endpoint takes a
repo_idparam in the URL. This is the minimum requirement for associating a release with a Board - Additional repository IDs can be passed in the body
repositoriesparameter - Any Boards not associated with the URL
repo_idparameter, but associated with repositories in the request bodyrepositoriesparameter will also be associated to the Release Report. - The user creating the release requires push permission to the repositories in the request.
GET /p1/reports/release/:release_id
| Name | Type | Comments |
|---|---|---|
release_id |
String |
Required |
{
"release_id": "59d3cd520a430a6344fd3bdb",
"title": "Test release",
"description": "",
"start_date": "2017-10-01T19:00:00.000Z",
"desired_end_date": "2017-10-03T19:00:00.000Z",
"created_at": "2017-10-03T17:48:02.701Z",
"closed_at": null,
"state": "open",
"repositories": [
105683718
]
}GET /p1/repositories/:repo_id/reports/releases
| Name | Type | Comments |
|---|---|---|
repo_id |
Number |
Required |
[
{
"release_id": "59cbf2fde010f7a5207406e8",
"title": "Great title for release 1",
"description": "Great description for release",
"start_date": "2000-10-10T00:00:00.000Z",
"desired_end_date": "2010-10-10T00:00:00.000Z",
"created_at": "2017-09-27T18:50:37.418Z",
"closed_at": null,
"state": "open"
},
{
"release_id": "59cbf2fde010f7a5207406e8",
"title": "Great title for release 2",
"description": "Great description for release",
"start_date": "2000-10-10T00:00:00.000Z",
"desired_end_date": "2010-10-10T00:00:00.000Z",
"created_at": "2017-09-27T18:50:37.418Z",
"closed_at": null,
"state": "open"
}
]PATCH /p1/reports/release/:release_id
| Name | Type | Comments |
|---|---|---|
release_id |
String |
Required |
| Name | Type | Comments |
|---|---|---|
title |
String |
Required |
description |
String |
Optional |
start_date |
ISO8601 date string | Optional |
desired_end_date |
ISO8601 date string | Optional |
state |
String |
Optional, open or closed |
{
"title": "Amazing title",
"description": "Amazing description",
"start_date": "2007-01-01T00:00:00Z",
"desired_end_date": "2007-01-01T00:00:00Z",
"state": "closed"
}{
"release_id": "59d3d6438b3f16667f9e7174",
"title": "Amazing title",
"description": "Amazing description",
"start_date": "2007-01-01T00:00:00.000Z",
"desired_end_date": "2007-01-01T00:00:00.000Z",
"created_at": "2017-10-03T18:26:11.700Z",
"closed_at": "2017-10-03T18:26:11.700Z",
"state": "closed",
"repositories": [
105683567,
105683718
]
}PATCH /p1/reports/release/:release_id/workspaces/add
| Name | Type | Comments |
|---|---|---|
release_id |
String |
Required |
| Name | Type | Comments |
|---|---|---|
repositories |
[Number] |
Required, an array of repo IDs |
{ "repositories": [ 103707262 ] }{
"release_id": "59d3cd520a430a6344fd3bdb",
"title": "Test release",
"description": "",
"start_date": "2017-10-01T19:00:00.000Z",
"desired_end_date": "2017-10-03T19:00:00.000Z",
"created_at": "2017-10-03T17:48:02.701Z",
"closed_at": null,
"state": "open",
"repositories": [
103707262,
105683718
]
}PATCH /p1/reports/release/:release_id/workspaces/remove
| Name | Type | Comments |
|---|---|---|
release_id |
String |
Required |
| Name | Type | Comments |
|---|---|---|
repositories |
[Number] |
Required, array of repo IDs |
{ "repositories": [ 103707262 ] }{
"release_id": "59d3cd520a430a6344fd3bdb",
"title": "Test release",
"description": "",
"start_date": "2017-10-01T19:00:00.000Z",
"desired_end_date": "2017-10-03T19:00:00.000Z",
"created_at": "2017-10-03T17:48:02.701Z",
"closed_at": null,
"state": "open",
"repositories": [
105683718
]
}GET /p1/reports/release/:release_id/issues
| Name | Type | Comments |
|---|---|---|
release_id |
String |
Required |
[
{ "repo_id": 103707262, "issue_number": 2 },
{ "repo_id": 103707262, "issue_number": 3 }
]PATCH /p1/reports/release/:release_id/issues
| Name | Type | Comments |
|---|---|---|
release_id |
String |
Required |
| Name | Type | Comments |
|---|---|---|
add_issues |
[{repo_id: Number, issue_number: Number}] |
Required, array of Objects with repo_id and issue_number |
remove_issues |
[{repo_id: Number, issue_number: Number}] |
Required, array of Objects with repo_id and issue_number |
- Both the
add_issuesandremove_issueskeys are required, but can be an empty array when not used
{
"add_issues": [
{ "repo_id": 103707262, "issue_number": 3 }
],
"remove_issues": []
}{
"added": [
{ "repo_id": 103707262, "issue_number": 3 }
],
"removed": []
}- Adding and removing issues can be done in the same request by populating with the
add_issuesandremove_issueskeys.
You can use our webhooks to fetch or store your ZenHub data, in real time, across services like Slack, Gitter, Spark, HipChat, or something custom!
To set up an integration, head on over to our Dashboard, navigate to your organization, and select the Slack & Integrations tab. From there, you may choose one of the 5 services (Slack, HipChat, Gitter, Spark, or Custom).
For instructions, you'll notice the How to create a webhook link changes dynamically based on the service you select. Simply choose a repository with which to connect, add an optional description, paste your webhook, and click "Add" to save your new integration.
Our custom webhook sends a POST request to your webhook for multiple events that occur on your ZenHub board:
{
"type": "issue_transfer",
"github_url": "https://github.com/ZenHubIO/support/issues/618",
"organization": "ZenHubHQ",
"repo": "support",
"user_name": "ZenHubIO",
"issue_number": "618",
"issue_title": "ZenHub Change Log",
"to_pipeline_name": "New Issues",
"from_pipeline_name": "Discussion"
}{
"type": "estimate_set",
"github_url": "https://github.com/ZenHubIO/support/issues/618",
"organization": "ZenHubHQ",
"repo": "support",
"user_name": "ZenHubIO",
"issue_number": "618",
"issue_title": "ZenHub Change Log",
"estimate": "8"
}{
"type": "estimate_cleared",
"github_url": "https://github.com/ZenHubIO/support/issues/618",
"organization": "ZenHubHQ",
"repo": "support",
"user_name": "ZenHubIO",
"issue_number": "618",
"issue_title": "ZenHub Change Log",
}{
"type": "issue_reprioritized",
"github_url": "https://github.com/ZenHubIO/support/issues/618",
"organization": "ZenHubHQ",
"repo": "support",
"user_name": "ZenHubIO",
"issue_number": "618",
"issue_title": "ZenHub Change Log",
"to_pipeline_name": "Backlog",
"from_position": "4",
"to_position": "0"
}As an example, here's a simple Node/Express app that would be able receive the webhooks (using ngrok):
var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var app = express();
http.createServer(app).listen('6000', function() {
console.log('Listening on 6000');
});
app.use(bodyParser());
app.post('*', function(req, res) {
console.dir(req.body);
});We’d love to hear from you. If you have any questions, concerns, or ideas related to the ZenHub API, open an issue in our Support repo or find us on Twitter.
