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.
What an epic is
Section titled “What an epic is”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).
List a project’s epics
Section titled “List a project’s epics”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.
Create an epic
Section titled “Create an epic”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.
Choose the lead team
Section titled “Choose the lead team”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.
Change an epic
Section titled “Change an epic”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.
Close and reopen an epic
Section titled “Close and reopen an epic”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:
progresscounts the epic’s tickets (totalTickets) and how many of them are done (completedTickets).contributingTeamIdslists 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.
File tickets into an epic
Section titled “File tickets into an epic”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.
List an epic’s tickets
Section titled “List an epic’s tickets”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).
Find a team’s epics
Section titled “Find a team’s epics”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.
What an API key can do
Section titled “What an API key can do”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.
Errors
Section titled “Errors”| 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 and don’t
Section titled “Do and don’t”- 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’
teamIdinstead. - Don’t expect the lead team to be among the contributing teams. It’s only there if it owns one of the tickets.
What isn’t in the API yet
Section titled “What isn’t in the API yet”- 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).
Where to go next
Section titled “Where to go next”- Epics and milestones: what an epic is in the app, with its milestones and launches.
- Organize work by team: a project’s teams, and a ticket’s
teamId. - List and filter: the other filters
GET /ticketstakes. - The API reference lists the epic routes under Epics.