Skip to content
Docs
About By Dawn Contact supportOpen By Dawn
  1. Work with tickets
  2. Group work into epics

Group work into epics

Use this when your integration works with bigger pieces of work than a ticket: opening an epic for a feature planned in another tool, or reporting which teams are behind each epic and how far it’s got. By the end you’ll know how to list and create a project’s epics, give one a lead team, close it, read its progress and its contributing teams, and file and list its tickets.

An epic is a deliverable, such as a feature or a migration, that takes several tickets to finish. It belongs to one project, and each ticket sits in at most one epic, named by the ticket’s epicId. In the app, epics live under Epics & Milestones (see Epics and milestones).

An epic names the teams behind it in two ways:

  • Its lead team (leadTeamId) is the one team answerable for the epic. You choose it.
  • Its contributing teams (contributingTeamIds) are the teams that own its tickets. By Dawn works them out from the tickets, so you can’t set them.

The API names a team by its ID, team-…, as it does everywhere. To show a team’s name, look it up in your organization’s list of teams (see Put names to team IDs).

GET /epics lists a project’s epics, oldest first:

GET /epics?projectId=project-3a86…
{
"ok": true,
"data": [
{
"id": "epic-4c2a…",
"projectId": "project-3a86…",
"name": "Board export",
"description": "Let teams take a snapshot of the board out of SAW, as CSV and as a shareable link.",
"color": "#5B6EE8",
"targetDate": "2026-11-06",
"progress": { "totalTickets": 6, "completedTickets": 2 },
"leadTeamId": "team-1f4e…",
"contributingTeamIds": ["team-1f4e…", "team-8c3a…"],
"createdAt": "2026-10-02T09:12:40Z",
"updatedAt": "2026-10-14T16:05:13Z"
},
{
"id": "epic-9e1b…",
"projectId": "project-3a86…",
"name": "Sign in with SSO",
"color": "#2FA89C",
"closedAt": "2026-10-09T11:30:00Z",
"progress": { "totalTickets": 4, "completedTickets": 4 },
"contributingTeamIds": ["team-8c3a…"],
"createdAt": "2026-09-15T08:00:21Z",
"updatedAt": "2026-10-09T11:30:00Z"
}
]
}

Done epics are listed too, so check closedAt: an open epic has none. A field with no value is left out rather than sent as null, which is why the second epic has no description, targetDate or leadTeamId. An empty list means the project has no epics yet.

An API key reads one project at a time, so it must pass projectId, and without it the list is 403 forbidden. With a signed-in member’s token, leaving projectId out lists the epics of every project you can read.

To read one epic, call GET /epics/{epicId}. It answers the same fields.

Send projectId and name with POST /epics. Everything else is optional:

{ "projectId": "project-3a86…", "name": "Board export", "targetDate": "2026-11-06", "leadTeamId": "team-1f4e…" }
  • description: what the epic delivers, in Markdown.
  • color: one of the 12 colors of By Dawn’s palette, by its hex, such as #5B6EE8. Leave it out and By Dawn picks the one that stands out most from the project’s open epics.
  • targetDate: when the epic should be done, as a date (2026-11-06).
  • leadTeamId: the team leading the epic (see Choose the lead team).

You get the new epic back. It has no tickets yet, so its progress is zero and it has no contributing teams:

{
"ok": true,
"data": {
"id": "epic-4c2a…",
"projectId": "project-3a86…",
"name": "Board export",
"color": "#5B6EE8",
"targetDate": "2026-11-06",
"progress": { "totalTickets": 0, "completedTickets": 0 },
"leadTeamId": "team-1f4e…",
"contributingTeamIds": [],
"createdAt": "2026-10-02T09:12:40Z",
"updatedAt": "2026-10-02T09:12:40Z"
}
}

The name can’t be blank and can be at most 200 characters. A project that doesn’t exist in your organization is refused with 404 invalid_project, and nothing is created.

The lead team must be a team of your organization that isn’t archived. Unlike a ticket’s team, it doesn’t have to work in the epic’s project, so a team can lead an epic in a project it doesn’t otherwise work in. Any other team is refused with 422:

{ "ok": false, "error": "invalid_team", "message": "the team is not a live team of this organization" }

Branch on the invalid_team slug, never on the message (see Responses and errors).

To hand the epic to another team, send the new leadTeamId with PATCH /epics/{epicId}, or null to leave it with no lead team:

{ "leadTeamId": "team-8c3a…" }

When an organization admin archives a team, By Dawn clears it from every epic it led, so those epics come back without a leadTeamId.

PATCH /epics/{epicId} changes only the fields you send, and you get the whole epic back:

{ "name": "Board export and sharing", "targetDate": "2026-11-20" }

Send null to clear description, targetDate or leadTeamId. An epic always has a color, so clearing color makes By Dawn pick one again, the one that stands out most from the project’s other open epics. The name can’t be cleared.

Send closed: true when the work is done. By Dawn marks the epic done and stamps closedAt:

{ "closed": true }

Send closed: false to reopen it, and closedAt is left out again. Progress reaching 100% doesn’t close an epic, because more work may still be filed under it, so your integration or a person has to. If the epic’s tickets were launched in the app, closing it also ends the launch.

Read who’s working on it and how far it’s got

Section titled “Read who’s working on it and how far it’s got”

Two fields are worked out from the epic’s tickets, and both leave out archived tickets and tickets waiting in triage:

  • progress counts the epic’s tickets (totalTickets) and how many of them are done (completedTickets).
  • contributingTeamIds lists the teams that own those tickets, sorted. A ticket with no team adds none.

You can’t set contributingTeamIds. To change it, change the teamId of the epic’s tickets (see Move a ticket to another team). The lead team is set separately, so it’s only among the contributing teams if it owns at least one of the tickets.

Send epicId with POST /tickets to file a new ticket into an epic:

{ "projectId": "project-3a86…", "title": "Export the board as CSV", "epicId": "epic-4c2a…" }

To move an existing ticket, send epicId with PATCH /tickets/{ticketId}, or null to take it out of its epic. The epic must be in the ticket’s project, or the request is refused with 422 invalid_epic. Moving a ticket to another epic also takes it out of its milestone, because a milestone belongs to one epic.

Filter GET /tickets by epicId:

GET /tickets?projectId=project-3a86…&epicId=epic-4c2a…

epicId=none lists the tickets in no epic, which is useful for finding work that hasn’t been planned in yet. The rest of the list’s filters, paging and sorting work as usual (see List and filter).

There’s no route that lists a team’s epics. List the project’s epics instead, and filter them in your code:

  • The epics a team leads have its ID in leadTeamId.
  • The epics a team works on have its ID in contributingTeamIds.

For a team that works in several projects, list each project in turn. With a signed-in member’s token you can leave projectId out and filter every project’s epics at once.

A key with Board — read only can list and read epics, and a key with Board — read & write can also create and change them (see Scopes). Every call is checked against the epic’s project, so a key can’t read or change the epics of a restricted project, or create one there: the request is 403 forbidden.

Status Slug When
404 epic_not_found The epic in the path doesn’t exist in your organization.
404 invalid_project POST /epics names a project that doesn’t exist in your organization.
422 invalid_team The lead team isn’t a team of your organization, or it’s archived.
422 validation_error The name is blank or over 200 characters, or the color isn’t one of the palette’s. message says which.
422 invalid_epic A ticket’s epicId names an epic in another project.
403 forbidden A key listed epics without projectId, used a project it can’t see, or wrote without Board — read & write.

None of these is worth retrying unchanged. Responses and errors explains the envelope and the slugs.

  • Do store an epic’s ID, not its name. People rename epics.
  • Do close an epic when its work is done. It stays open at 100% until someone does.
  • Don’t try to set an epic’s contributing teams. Change its tickets’ teamId instead.
  • Don’t expect the lead team to be among the contributing teams. It’s only there if it owns one of the tickets.
  • Deleting an epic. Close it instead, or delete it in the app (see Delete an epic).
  • An epic’s milestones. Tickets carry their milestoneId, but milestones are created and changed in the app.
  • Launching an epic’s tickets, and an epic’s activity. Both are in the app (see Launch an epic).