Update and move a ticket
Use this to keep a ticket in step with the world outside By Dawn: raise its priority when an alert gets worse, add what you’ve learned to its description, or move it along the board. By the end the ticket will show your changes, and nothing else about it will have changed.
Before you start: you need a Board — read & write key (see
Scopes) and the ticket’s id, for example from
Create a ticket. Pass the ID as the recipe’s argument.
Read down the steps. The code on the right follows along and highlights the lines each one explains.
Swap your key for an access token
Section titled “Swap your key for an access token”Every recipe starts this way, as the Quickstart explains.
// 1. Exchange the API key for a short-lived access token. Every call after this goes to your// organization's URL with the token as a Bearer header.const exchange: Schemas['ApiKeyExchange'] = { apiKey };const { accessToken: token } = await call<Schemas['AccessTokenData']['data']>(`${ACCOUNTS_URL}/auth/token/api-key`, { method: 'POST', body: exchange });const api = `${WORKSPACE_URL}/orgs/${orgIdOf(token)}`;# 1. Exchange the API key for a short-lived access token. Every call after this goes to your# organization's URL with the token as a Bearer header.token = call(f"{ACCOUNTS_URL}/auth/token/api-key", "POST", {"apiKey": API_KEY})["accessToken"]api = f"{WORKSPACE_URL}/orgs/{org_id_of(token)}"// 1. Exchange the API key for a short-lived access token. Every call after this goes to your// organization's URL with the token as a Bearer header.String token = call("POST", accountsUrl + "/auth/token/api-key", null, Map.of("apiKey", apiKey), new TypeReference<Envelope<AccessToken>>() {}).accessToken();String api = workspaceUrl + "/orgs/" + orgIdOf(token);// 1. Exchange the API key for a short-lived access token.using var accounts = new HttpClient();var token = await ReadAsync<AccessTokenData>(await accounts.PostAsJsonAsync($"{accountsUrl}/auth/token/api-key", new { apiKey }));// Every call goes to your organization's URL with the token as a Bearer header.using var http = new HttpClient { BaseAddress = new Uri($"{workspaceUrl}/orgs/{OrgIdOf(token.AccessToken)}/") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);// 1. Exchange the API key for a short-lived access token. Every call after this goes to your// organization's URL with the token as a Bearer header.exchange, err := call[accessToken](http.MethodPost, accountsURL+"/auth/token/api-key", "", map[string]string{"apiKey": apiKey})check(err)token := exchange.AccessTokenapi := workspaceURL + "/orgs/" + orgIDOf(token)Terminal window # 1. Exchange the API key for a short-lived access token. jq reads the key from the environment, so# it never appears on a command line.token=$(jq -n '{apiKey: env.SAW_API_KEY}' |curl -sS "$ACCOUNTS_URL/auth/token/api-key" -H 'Content-Type: application/json' --data @- |jq -r "$DATA | .accessToken")# The token is a JWT; its `org_id` claim is the organization the key belongs to. Every call# after this goes to that organization's URL with the token as a Bearer header.org_id=$(jq -rR 'split(".")[1] | gsub("-"; "+") | gsub("_"; "/") | . + ("=" * ((4 - length % 4) % 4))| @base64d | fromjson | .org_id' <<<"$token")api="$WORKSPACE_URL/orgs/$org_id"Read the ticket as it is now
Section titled “Read the ticket as it is now”The recipe reads the ticket first, so it can show what changed, and so it can add to the description rather than replace it.
// 2. Read the ticket you named as it is now, so the output can show what changed.const ticketId = process.argv[2];if (!ticketId) throw new Error('Usage: node update-ticket.ts <ticketId>');const url = `${api}/tickets/${encodeURIComponent(ticketId)}`;const before = await call<Schemas['Ticket']>(url, { token });# 2. Read the ticket you named as it is now, so the output can show what changed.if len(sys.argv) < 2:sys.exit("Usage: python3 update_ticket.py <ticketId>")url = f"{api}/tickets/{urllib.parse.quote(sys.argv[1], safe='')}"before = call(url, token=token)// 2. Read the ticket you named as it is now, so the output can show what changed.String url = api + "/tickets/" + URLEncoder.encode(args[0], StandardCharsets.UTF_8);Ticket before = call("GET", url, token, null, new TypeReference<Envelope<Ticket>>() {});// 2. Read the ticket you named as it is now, so the output can show what changed.var before = await ReadAsync<Ticket>(await http.GetAsync($"tickets/{ticketId}"));// 2. Read the ticket you named as it is now, so the output can show what changed.if len(os.Args) < 2 {fail("Usage: go run update_ticket.go <ticketId>")}ticketURL := api + "/tickets/" + url.PathEscape(os.Args[1])before, err := call[ticket](http.MethodGet, ticketURL, token, nil)check(err)description := ""if before.Description != nil {description = *before.Description}Terminal window # 2. Read the ticket you named as it is now, so the output can show what changed.url="$api/tickets/$(jq -rn --arg id "$ticket_id" '$id | @uri')"before=$(curl -sS "$url" -H "Authorization: Bearer $token" | jq "$DATA")Send only what you want to change
Section titled “Send only what you want to change”PATCH /tickets/{ticketId}changes only the fields you send, and leaves the rest alone. So you don’t need to read a ticket before every update, only when the new value depends on the old one, as the description does here.// 3. Send only the fields you want to change.// Sending `statusId` moves the ticket to another board column. A move into a column with the// `building` role would start an agent run, so the API refuses it for an API key.const update: Schemas['TicketUpdate'] = {title: 'Updated by the update-ticket recipe',priority: 'HIGH',description: `${before.description ?? ''}\n\nUpdated through the SAW API.`.trim(),};const ticket = await call<Schemas['Ticket']>(url, { method: 'PATCH', body: update, token });console.log(`${ticket.identifier}: "${before.title}" → "${ticket.title}", priority ${before.priority} → ${ticket.priority}`);# 3. Send only the fields you want to change.# Sending `statusId` moves the ticket to another board column. A move into a column with the# `building` role would start an agent run, so the API refuses it for an API key.ticket = call(url,"PATCH",{"title": "Updated by the update-ticket recipe","priority": "HIGH","description": f"{before.get('description') or ''}\n\nUpdated through the SAW API.".strip(),},token,)print(f'{ticket["identifier"]}: "{before["title"]}" → "{ticket["title"]}", priority {before["priority"]} → {ticket["priority"]}')// 3. Send only the fields you want to change.// Sending `statusId` moves the ticket to another board column. A move into a column with the// `building` role would start an agent run, so the API refuses it for an API key.Ticket ticket = call("PATCH", url, token, Map.of("title", "Updated by the update-ticket recipe","priority", "HIGH","description", (Objects.requireNonNullElse(before.description(), "") + "\n\nUpdated through the SAW API.").strip()),new TypeReference<Envelope<Ticket>>() {});System.out.println(ticket.identifier() + ": \"" + before.title() + "\" → \"" + ticket.title()+ "\", priority " + before.priority() + " → " + ticket.priority());// 3. Send only the fields you want to change.// Sending `statusId` moves the ticket to another board column. A move into a column with the// `building` role would start an agent run, so the API refuses it for an API key.var ticket = await ReadAsync<Ticket>(await http.PatchAsJsonAsync($"tickets/{ticketId}", new{title = "Updated by the update-ticket recipe",priority = "HIGH",description = $"{before.Description}\n\nUpdated through the SAW API.".Trim(),}));Console.WriteLine($"{ticket.Identifier}: \"{before.Title}\" → \"{ticket.Title}\", priority {before.Priority} → {ticket.Priority}");// 3. Send only the fields you want to change.// Sending `statusId` moves the ticket to another board column. A move into a column with the// `building` role would start an agent run, so the API refuses it for an API key.updated, err := call[ticket](http.MethodPatch, ticketURL, token, map[string]string{"title": "Updated by the update-ticket recipe","priority": "HIGH","description": strings.TrimSpace(description + "\n\nUpdated through the SAW API."),})check(err)fmt.Printf("%s: \"%s\" → \"%s\", priority %s → %s\n", updated.Identifier, before.Title, updated.Title, before.Priority, updated.Priority)Terminal window # 3. Send only the fields you want to change.# Sending `statusId` moves the ticket to another board column. A move into a column with the# `building` role would start an agent run, so the API refuses it for an API key.ticket=$(jq '{title: "Updated by the update-ticket recipe",priority: "HIGH",description: ((.description // "") + "\n\nUpdated through the SAW API." | sub("^\\s+"; "") | sub("\\s+$"; ""))}' <<<"$before" |curl -sS -X PATCH "$url" -H "Authorization: Bearer $token" -H 'Content-Type: application/json' --data @- |jq "$DATA")jq -rn --argjson before "$before" --argjson ticket "$ticket" \'"\($ticket.identifier): \"\($before.title)\" → \"\($ticket.title)\", priority \($before.priority) → \($ticket.priority)"'
Run it in your browser
Section titled “Run it in your browser”Run the TypeScript recipe from this page. It changes the ticket’s title, priority and description. A ticket you created here is filled in for you. See Try it in your browser.
Try it with your organization
How this worksConnect once and run this recipe live against the By Dawn API.
Your key stays in this tab and goes only to By Dawn. Use a read-only key in a test organization.
update-ticket.tsThe same code CI runs, in your browserupdate-ticket.ts…Things to know
Section titled “Things to know”- Lists are replaced, not merged.
labelIdsandrepoIdsreplace the whole list, so to add a label, send the IDs it already has plus the new one. - Leave out,
nullor a value. For fields likecycleIdandestimate, leaving the field out keeps it,nullclears it, and a value sets it. - Moving between columns. Send a new
statusIdto move the ticket. The one move a key can’t make is into the column that starts By Dawn’s agent (see what a key can’t do). - Archiving.
POST /tickets/{ticketId}/archivetakes a ticket off the board, and/unarchivebrings it back.