# What is a token?

A few lines of text, signed by a service the API already trusts. The API checks the text. It never has to trust whoever is carrying it.

This lesson builds on the one before it. If you arrived here first and a term is unfamiliar, [lesson 1](https://whosonfirst.ai/learn/what-the-api-sees/) is where they start, and each is in the [glossary](https://whosonfirst.ai/glossary/).

## A key card, not a passport

A hotel key card is a fair picture of a token. The front desk checks who you are, once, and hands you a card. The card opens your room and the gym, in this hotel, until checkout. The door never asks for your passport. It checks the card.

An **access token** is the card. The front desk is called an **authorization server**: the one service that checks who people are, asks what they agree to, and issues tokens. The door is the API.

That is as far as the picture needs to go. What makes a token work is a signature.

## What is written on it

Many access tokens are **JWTs** (said “jots”): three chunks of text joined by dots. You have probably seen one. Not every token is one, and the end of this lesson comes back to that. This lesson uses a JWT because you can read what it says.

_A JWT in three parts: a header, the claims, and a signature over both._

The first two chunks are not encrypted. They are JSON, encoded so that they survive a trip through an HTTP header. Decode the middle one and you get a handful of lines, each called a **claim**:

| In the token | What it says | Which question |
| - | - | - |
| `"iss": "https://auth.example"` | The **issuer**. The authorization server at auth.example signed this. | Who said they could |
| `"sub": "alice"` | The **subject**. It is about Alice. | For whom |
| `"client_id": "calendar-assistant"` | It was issued to this app. | Who is acting |
| `"aud": "https://calendar.example"` | The **audience**. It is meant for the calendar API and for nothing else. | Who said they could |
| `"scope": "calendar.read"` | The **scope**. It allows reading the calendar. | Who said they could |
| `"exp": 1794305400` | It stops working at 10:10, ten minutes after it was issued. The number is seconds since 1970. | Who said they could |

A common mix-up

“A token is encrypted, so it is safe to put things in it and safe to leave it lying around.”

A JWT like this one is signed, not encrypted. Anyone who holds one can read every claim. Decode the example above and see, on your own machine: a real token pasted into a website has been given to that website. The signature stops a token being changed. It does nothing to stop it being read, or used.

The third chunk is the signature. The authorization server computes it from the first two chunks with a private key that never leaves it, and publishes the matching public key. Anyone can check the signature. Only the authorization server can make one.

## Try to break it

An API believes nothing in a token until it has checked it. There are five checks. The first button below sends the token as it was issued, and each of the other five does something to it. Before you press one of those, guess which check will catch it. One of them gets past all five.

What happens to the token: **Nothing: sent as issued**

_Agent, then a request, with the token, to Calendar API_

The token’s claims

```json
{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}
```

What the API compares them with

```yaml
this API:  calendar.example
time now:  10:04
request:   GET /events?day=tomorrow
```

| The API’s five checks, in order | | |
| - | - | - |
| From an issuer I trust? | Yes | `iss` is auth.example, which this API is set up to trust. |
| Signature intact? | Yes | It verifies with auth.example’s public key. Nothing has changed since it was signed. |
| Addressed to me? | Yes | `aud` is calendar.example. That is this API. |
| Still in date? | Yes | `exp` is 10:10. It is 10:04. |
| Covers this request? | Yes | The request reads events, and `scope` allows `calendar.read`. |

All five pass, so the API believes the claims: these are Alice’s events, for the app she approved. **It did not have to ask anyone at the moment of the request, and it did not have to trust the agent.** The signature is the proof.

What happens to the token: **Someone edits it**

_Agent, then a request, with the token changed to allow writing, to Calendar API_

The token’s claims

```json
{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.write",
  "exp": 1794305400
}
```

What the API compares them with

```yaml
this API:  calendar.example
time now:  10:04
request:   DELETE /events/42
```

| The API’s five checks, in order | | |
| - | - | - |
| From an issuer I trust? | Yes | `iss` still says auth.example. |
| Signature intact? | No | The claims no longer match the signature. Only auth.example’s private key could sign the new version. |
| Addressed to me? | Skipped | Not reached. |
| Still in date? | Skipped | Not reached. |
| Covers this request? | Skipped | Not reached. |

Rejected with `401`. **Anyone can read a token. Only the service that signs it can change one.** Nothing in a token that fails this check is believed, so the API stops here.

What happens to the token: **It is sent to another API**

_Agent, then a request, with the calendar token, to Mail API_

The token’s claims

```json
{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}
```

What the API compares them with

```yaml
this API:  mail.example
time now:  10:04
request:   GET /messages
```

| The API’s five checks, in order | | |
| - | - | - |
| From an issuer I trust? | Yes | The mail API trusts auth.example too. |
| Signature intact? | Yes | Unchanged since it was signed. |
| Addressed to me? | No | `aud` is calendar.example. This is mail.example. |
| Still in date? | Skipped | Not reached. |
| Covers this request? | Skipped | Not reached. |

Rejected. A token is addressed to one API, and every API has to read the address. **If the mail API skipped this check, any service that was ever handed a token could turn around and use it somewhere else.**

What happens to the token: **It is used an hour later**

_Agent, then a request, with the token, at 11:04, to Calendar API_

The token’s claims

```json
{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}
```

What the API compares them with

```yaml
this API:  calendar.example
time now:  11:04
request:   GET /events?day=tomorrow
```

| The API’s five checks, in order | | |
| - | - | - |
| From an issuer I trust? | Yes | `iss` is auth.example. |
| Signature intact? | Yes | Unchanged since it was signed. |
| Addressed to me? | Yes | `aud` is calendar.example. |
| Still in date? | No | `exp` was 10:10. It is 11:04. |
| Covers this request? | Skipped | Not reached. |

Rejected with `401`. Access tokens are short-lived on purpose, often a few minutes. The agent can get a fresh one without sending Alice through sign-in again; the next lesson shows how.

What happens to the token: **The agent tries to delete an event**

_Agent, then a request to delete an event, with the token, to Calendar API_

The token’s claims

```json
{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}
```

What the API compares them with

```yaml
this API:  calendar.example
time now:  10:04
request:   DELETE /events/42
```

| The API’s five checks, in order | | |
| - | - | - |
| From an issuer I trust? | Yes | `iss` is auth.example. |
| Signature intact? | Yes | Unchanged since it was signed. |
| Addressed to me? | Yes | `aud` is calendar.example. |
| Still in date? | Yes | `exp` is 10:10. It is 10:04. |
| Covers this request? | No | Deleting needs `calendar.write`. Alice approved `calendar.read`. |

Refused with `403`. The token is genuine, and it does not cover this. **It does not matter why the agent tried**: a bug, a bad instruction, or something it read on a web page. The limit holds.

What happens to the token: **Someone else sends a copy**

_Someone else, then a request, with the copied token, to Calendar API_

The token’s claims

```json
{
  "iss": "https://auth.example",
  "sub": "alice",
  "client_id": "calendar-assistant",
  "aud": "https://calendar.example",
  "scope": "calendar.read",
  "exp": 1794305400
}
```

What the API compares them with

```yaml
this API:  calendar.example
time now:  10:04
request:   GET /events?day=tomorrow
```

| The API’s five checks, in order | | |
| - | - | - |
| From an issuer I trust? | Yes | `iss` is auth.example. |
| Signature intact? | Yes | Unchanged since it was signed. |
| Addressed to me? | Yes | `aud` is calendar.example. |
| Still in date? | Yes | `exp` is 10:10. It is 10:04. |
| Covers this request? | Yes | `scope` allows `calendar.read`. |

All five pass, and the API answers. **Nothing in this token says who is allowed to send it.** A token like this is called a _bearer_ token: it works for whoever holds it, like cash. A short life limits how long a stolen copy is useful. The final lesson shows how to make a copy worth far less.

| What happens to the token | From an issuer I trust? | Signature intact? | Addressed to me? | Still in date? | Covers this request? |
| - | - | - | - | - | - |
| Nothing: sent as issued | Yes | Yes | Yes | Yes | Yes |
| Someone edits it | Yes | No | Skipped | Skipped | Skipped |
| It is sent to another API | Yes | Yes | No | Skipped | Skipped |
| It is used an hour later | Yes | Yes | Yes | No | Skipped |
| The agent tries to delete an event | Yes | Yes | Yes | Yes | No |
| Someone else sends a copy | Yes | Yes | Yes | Yes | Yes |

**Do not write the signature check yourself.** Use a maintained library, and tell it in your own configuration which issuer, which audience, which keys and which signing method to accept. Never take those from the token: one that brings its own key, says where to fetch one, or says it needs no signature must be refused. Tell it which kind of token to accept as well. The same service signs other things, such as the ID token an app gets at sign-in, and a check that passes anything it signed would let one of those be used as an access token. A token in this format says what it is in its header: `at+jwt`, which may also be written `application/at+jwt`. The API must require one of the two. The last of the five, whether the token covers this request, is yours to write: only your code knows what each request needs. Decoding a token is not checking it.

## When the API says no

The refusals in the figure are of two kinds, worth telling apart because they send you to different places.

- **401** means the API did not accept the token at all. One of the first four checks failed, or no token arrived. If sign-in has just succeeded and the first call still fails, a common cause is the audience: the token was issued for a different address from the one the API knows itself by. Fix what the token is requested for, and never switch the audience check off to make the error go away. Check, too, that the token is being sent.
- **403** means the token is good and does not cover this request. Retrying will not help. The agent needs a wider grant, and that means asking whoever can give one.

## What a valid token does not prove

The last button is the uncomfortable one. A token records what was granted. It does not record who is allowed to carry it. A copy taken from a log file, a debugging session or an agent’s memory works exactly as well as the original, until it expires.

That is the trade a **bearer token** makes: simple to use, and only as safe as the places it passes through. It is why tokens are short-lived, why each is addressed to one API, and why it matters so much where an agent keeps them.

A common mix-up

“If the signature checks out, the token is good.”

A valid signature means the token is genuine. It does not mean the token is for this API, still in date, or wide enough for this request, and it says nothing about who is sending it. The signature is the second of five checks, not the last.

One more thing you will run into. Not every token can be read. Some are random strings that mean nothing by themselves, and the API asks the authorization server what each one stands for. Most of the checks then happen there, and comparing the scope with the request stays with the API. Asking is also how an API learns that a token was withdrawn early: one that only checks the token itself goes on accepting a withdrawn token until it expires.

Which kind a token is lies between the authorization server and the API, and they can change it at any time. So your agent sends a token on as it received it, and never depends on reading one.

## Check yourself

Answer before you read on. Nothing is recorded.

**1. An API receives a token. The issuer is trusted, the signature is intact and it has not expired, but `aud` names a different API. What should it do?**

- Accept it. Everything that matters checked out.

  **Not quite.** The address matters as much as the signature. An API that accepts tokens meant for other services lets anyone who receives a token reuse it here.

- Accept it, but only for read requests.

  **Not quite.** There is no partial credit. A token for a different audience says nothing about what its holder may do here.

- Reject it. The token was not issued for this API.

  **Right.** A token is addressed to one API. Checking `aud` is what stops a token issued for one service from being replayed at another.

**2. Before sending the token, the agent changes `calendar.read` to `calendar.write` in the claims. What happens?**

- The API rejects it. The signature no longer matches.

  **Right.** The signature covers every claim. Change one character and it fails, and only the issuer holds the key that could sign the new version.

- The API accepts it, because the token came from a trusted issuer.

  **Not quite.** The issuer signed the original claims, not the edited ones. The signature no longer matches.

- The authorization server notices and revokes the token.

  **Not quite.** The authorization server is not involved at this point. The API detects the change itself, by checking the signature.

**3. Why do access tokens usually last minutes and not months?**

- Because the issuer’s signing key changes every few minutes.

  **Not quite.** Signing keys are replaced far less often than that. The expiry is a choice the issuer makes for each token.

- Because a copy works for whoever holds it, until it expires.

  **Right.** A bearer token cannot tell its rightful holder from a thief. Expiry is the simplest limit on what a stolen copy is worth.

- To make users sign in more often.

  **Not quite.** The user is usually not asked again. With a refresh token the agent renews on its own, which the next lesson covers.

## What to keep

- A token is a statement signed by an **authorization server**: who it is about, which app it was given to, which API it is for, what it allows, and until when.
- The API checks five things before believing any of it: issuer, signature, audience, expiry and scope.
- **Audience** is the check that gets forgotten. A token is addressed to one API.
- A valid token proves what was granted. It does not prove who is sending it: a plain token is a **bearer token**.

## The names for these

The rest of this guide, and every specification, uses these terms. Each links to its entry in the [glossary](https://whosonfirst.ai/glossary/).

| In this lesson | Its name | Defined as |
| - | - | - |
| The service that checks who people are and issues tokens | [Authorization server](https://whosonfirst.ai/glossary/#authorization-server) | The system that authenticates users and clients, records consent, and issues tokens. |
| The token an API accepts | [Access token](https://whosonfirst.ai/glossary/#access-token) | A credential an API accepts as proof that a request is authorized. Short-lived, addressed to one audience, and limited to particular scopes. |
| One line inside a token | [Claim](https://whosonfirst.ai/glossary/#claim) | One named statement inside a token, such as `sub` (who it is about) or `exp` (when it expires). The issuer’s signature covers all of them. |
| Who signed it | [Issuer](https://whosonfirst.ai/glossary/#issuer) | The identifier of the system that issued a token. Verifiers check it against the issuers they trust. |
| Who it is about | [Subject](https://whosonfirst.ai/glossary/#subject) | Who or what a token is about: the person an agent is acting for, or the agent itself when it acts alone. |
| Which API it is for | [Audience](https://whosonfirst.ai/glossary/#audience) | The recipient a token is intended for. A resource must reject tokens whose audience is someone else; this is the main defense against a token being replayed at a different service. |
| What it allows | [Scope](https://whosonfirst.ai/glossary/#scope) | A name for a permission that a token carries, such as `calendar.read`. Scopes are coarse on purpose: they name kinds of access, not single actions. |
| Three chunks of text joined by dots | [JWT](https://whosonfirst.ai/glossary/#jwt) | A compact set of claims, usually signed and written as three chunks of text joined by dots. The common format for access tokens and ID tokens. |
| A token that works for whoever holds it | [Bearer token](https://whosonfirst.ai/glossary/#bearer-token) | A token that works for whoever presents it, like cash. A sender-constrained token is the kind that does not. |

## Go deeper

The same ground at full depth, when you want it. None of it is needed for the next lesson.

- [What is in a token](https://whosonfirst.ai/concepts/#tokens) Nine credentials you will meet, claim by claim.
- [JWT Profile for Access Tokens](https://whosonfirst.ai/standards/#rfc9068) The specification the example token follows, RFC 9068.

[← Lesson 1 What does the API actually see?](https://whosonfirst.ai/learn/what-the-api-sees/) [Lesson 3 → How does an agent get a token for a user?](https://whosonfirst.ai/learn/getting-a-token/)

---

From Who’s on First, a field guide to agent authentication and authorization. This page: https://whosonfirst.ai/learn/tokens/

Standards status last reviewed November 2026. Drafts move quickly: check the linked source before you build on one.
