# Authentication

> How API keys and access tokens work together, what a key is allowed to do, and how to keep keys safe.

To call the API you need two things. An **API key** is created once in By Dawn and lives on your
server. An **access token** is what your code gets from that key and sends with every request.
This page explains how the two fit together, what a key may do, and how to look after keys.

The split exists for safety. A key lasts a long time, so it never travels with ordinary requests.
A token expires after about 15 minutes, so one that leaks into a log can't do much harm.

> **Which should I use?** For a script, a service or a CI job, use an **API key** and let your code swap it for tokens, as
> every recipe on this site does. Paste a token by hand only when you're trying calls in
> [Try it](https://docs.bydawn.com/developers/try-it/).

## Create a key

An organization owner or admin creates keys in By Dawn, under **Settings › Organization › Security**, on the
**API keys** card ([API keys](https://docs.bydawn.com/api-keys/) walks through it). Give the
key a name you'll recognise later, pick what it may do (see [Scopes](#scopes)), and choose when
it expires: in 30, 90 or 365 days, or never. By Dawn shows the key once, so copy it straight away. It
starts with `saw_live_`.

Keep the key on a server, in a secret store or a CI secret. Don't put it in a web page or a
mobile app, where anyone using them could read it. (Pasting your own key into
[Try it](https://docs.bydawn.com/developers/try-it/) on this site is fine, because it never leaves your browser.)

## Exchange a key for an access token

Your code sends the key to the accounts host and gets a token back:

```http
POST https://accounts.dev.sawrun.com/api/v1/auth/token/api-key
Content-Type: application/json

{ "apiKey": "saw_live_…" }
```

```json
{ "ok": true, "data": { "accessToken": "eyJ…", "tokenType": "Bearer", "expiresInSeconds": 900 } }
```

Then it sends the token with every request to the workspace host, in an `Authorization` header:

```http
GET https://workspace.dev.sawrun.com/api/v1/orgs/{orgId}/projects
Authorization: Bearer eyJ…
```

Keep using the same token until it's about to expire, then exchange again. Don't exchange before
every request: the exchange has its own, lower [rate limit](https://docs.bydawn.com/developers/pagination-and-rate-limits/#rate-limits).
A long-running integration should remember when its token expires (`expiresInSeconds` from now)
and swap a minute before then.

If a request comes back `401 unauthorized`, the token has expired or its key was revoked.
Exchange once more and repeat the request. If that fails too, stop, because the key itself no
longer works.

## Your organization ID

Every workspace path starts with `/orgs/{orgId}`. A key belongs to exactly one organization, and
you don't need to look its ID up: the access token carries it, in its `org_id` claim. The token
is a JWT, so you can read the claim by decoding its middle part:

quickstart.ts (excerpt):

```ts
// The token is a JWT; its `org_id` claim is the organization the key belongs to.
const claims = JSON.parse(atob(token.data.accessToken.split('.')[1]!.replace(/-/g, '+').replace(/_/g, '/')));
const orgId: string = claims.org_id;
```

Asking about any other organization gets `403 forbidden`.

## Scopes

When you create a key you choose its **Access**, and that decides what its tokens may do:

| Access in Settings | Scopes | What it allows |
|---|---|---|
| Board — read only | `tickets:read` | Read tickets, comments, projects, columns, labels, cycles, epics and the teams that work in a project |
| Board — read & write | `tickets:read`, `tickets:write` | Everything above, plus create, update, move and archive tickets, post comments, link tickets together, and create, update and close epics |
| Tests — submit results | `tests:write` | Send test runs from CI ([Send test results from CI](https://docs.bydawn.com/developers/tests-submit/)), and nothing else: it can't read or change tickets |
| Tests — read cases & runs | `tests:read` | Read a project's test case library, its runs and their results, and nothing on the board |
| Tests — read & write cases & runs | `tests:read`, `tests:write` | Everything above, plus create and edit test cases, folders and manual runs, [import cases](https://docs.bydawn.com/tests-migrate/), and send test runs from CI |

Separately from its **Access**, a key has a **Customers** access, which opens your organization's customers
([Customers API](https://docs.bydawn.com/developers/customers/)):

| Customers in Settings | Scopes | What it allows |
|---|---|---|
| None | none | Nothing about customers. This is the default |
| Read | `customers:read` | Read customers, their requests, and the customer statuses and tiers |
| Read & write | `customers:read`, `customers:write` | Everything above, plus create and update customers, and add, change and remove the requests on a ticket. Changing a ticket's requests also needs `tickets:read` on its project |

**Customers only** in **Access** makes a key with no board access, for a sync that only keeps your customer list up to
date.

Start with read only, and make a read & write key when your integration needs to change things. Give a CI job that
only sends test results a Tests key, so a leaked CI secret can't touch your board.
A request the key's scopes don't cover gets `403 forbidden`.

### What a key can't do

A key acts for your organization, not for a person, and it's kept away from a few things a
person should decide. So whatever its scopes, a key can't:

- **list tickets across the whole organization.** Pass a `projectId` when listing tickets. For the
  same reason it can't list a team's tickets across projects (`GET /teams/{teamId}/tickets`), so
  filter each project by `teamId` instead, and it must pass a `projectId` when listing epics
  (`GET /epics`).
- **list the organization's members.** `GET /users` answers `403 forbidden`, so assign tickets
  with user IDs you already have.
- **list the organization's teams.** `GET /orgs/{orgId}/teams` on the accounts host answers
  `403 forbidden`, so keep the team IDs you need in your configuration. A project's teams give you
  their IDs.
- **delete a ticket.** Archive it instead, with `POST /tickets/{ticketId}/archive`.
- **create labels.** That needs an organization admin, signed in.
- **attach a team to a project or detach one.** That's a project admin's job, signed in.
- **move a ticket into the column that starts an agent run** (the column with the `building`
  role). Starting an agent should be a person's call.
- **see restricted projects.** They don't appear in its lists, and their tickets are refused.
- **merge or delete a customer.** Both change other people's data at once, so they're an organization admin's,
  signed in.
- **change Customers settings or turn Customers on**, including statuses, tiers, the revenue unit and who sees
  revenue. Those are an organization admin's, signed in (see [Customers settings and privacy](https://docs.bydawn.com/customers-settings/)).
- **forget a requester, or export a customer's requests as CSV.** Both are about personal data, so a person does them
  in the app.

Anything a key creates, a ticket or a comment, shows the key as its author, not a person.

## Revoke and rotate keys

Revoke a key in **Settings › Organization › Security**. It stops working at once: it can't be exchanged any
more, and tokens already issued from it stop working within seconds. An expired key behaves the
same way.

To rotate a key without downtime, create the new key, switch your integration over, and only
then revoke the old one.

Source: https://docs.bydawn.com/developers/authentication/
