# Sign-in and the dashboard

Passwordless sign-in, every control on the account page, and what rotating or cancelling a key actually does.

**What this covers:** Signing in; Every control on the dashboard; Rotating a key; Cancelling.

**Assumed knowledge:** Nothing.

**Canonical HTML:** https://jobopportunitiesapi.org/docs/account/sign-in  
**Machine-readable index:** https://jobopportunitiesapi.org/docs/ai/index.md  
**Last verified:** 2026-08-22  
**Superseded by:** the live API at https://api.jobopportunitiesapi.org and its spec at https://jobopportunitiesapi.org/openapi.json — where this file and the API disagree, the API is right.

---

<a id="signin-magic-link"></a>

## 1. Signing in

Enter an email at /login, follow the link it sends. There is no password to choose, forget or leak.

### 1.1 In depth

There is no separate sign-up. The first time you give an address, an account exists; after that the same flow signs you in. You do not need to have bought anything to have an account, and creating one is how you get the free key.

There is no password store and no session table. The link carries a one-time token; following it exchanges that token for a **signed** session value in a cookie, which the server verifies cryptographically rather than by looking it up. So there is nothing in a database that could be stolen and replayed as a password, and nothing to reset.

> **The link is one-time and short-lived** — Following it twice signs you in once. If it has expired, ask for another — the form is the same one. Mail clients that pre-fetch links are the usual reason a link appears to be already used.

### 1.2 Exact contract

| Step | URL | What happens |
| --- | --- | --- |
| 1 | `/login` | You enter an email. The page posts to its own `/api/auth/request`. |
| 2 | `POST /public/auth/request` | The API mints a one-time token and mails the link. Nothing is stored against the address until you follow it. |
| 3 | the link in the email | Lands on `/auth/verify`. |
| 4 | `/auth/verify` | Exchanges the token, sets the `joa_session` cookie, redirects to `/dashboard`. |
| 5 | `/dashboard` | Reads the cookie, calls `GET /public/auth/keys` with it, renders your keys. |

- **Cookie** — `joa_session`, signed, 30 days. HTTP-only.
- **Sign out** — Clears the cookie. The **Sign out** control is at the top right of the dashboard.
- **A cookie that no longer verifies** — Falls through to the signed-out view rather than rendering an empty account page — an empty page would look like your keys had been deleted.
- **Checking one key without signing in** — The signed-out dashboard has a **Check a key** box: paste a secret and it reports that key's status only.

> **The session is not an API key** — The session cookie authenticates the **website**; the API key authenticates the **API**. Key management endpoints require the session, and a leaked API key therefore cannot be used to rotate away from you.

**See also**

- [Every control on the dashboard](./account-sign-in.md#dashboard-tour)
- [Creating, naming, rotating and revoking](./api-authentication.md#auth-keys-lifecycle)
- [The account surfaces](./website-pages.md#site-account-pages)

<a id="dashboard-tour"></a>

## 2. Every control on the dashboard

One card per key, showing plan, status, dates, records used, request limits and entitlements — with rotate and cancel on the card itself.

### 2.1 In depth

| Row on the card | What it means |
| --- | --- |
| **Secret** | Your key's name (if you gave one) and its non-secret prefix, plus a badge: `LIVE`, or the status. |
| **Plan** | The plan's display name, read from the live plans table rather than a hard-coded map. |
| **Status** | Active, Awaiting payment, Cancelled, Expired or Revoked. |
| **Issued** | When the key was created. |
| **Renews / Access until / Expires** | Driven by whether the plan recurs. A subscription says *Renews*; a cancelled one says *Access until*; a non-recurring plan says *Expires*. |
| **Last used** | When a request last arrived on this key. |
| **Records this month** | Used of allowance — **the number that matters**. `unlimited in <CC>` on a country-locked key; `unmetered` on a grandfathered one. |
| **Includes** | `delta feed`, `bulk export`, or `REST endpoints` when neither. |
| **Request limits** | Per day and per minute, labelled as burst protection so it is not mistaken for the licence. |
| **Used today** | Requests today, against the per-day ceiling. |

Both the records row and the request-limits row carry a **raise this ↓** link to the upgrade ladder further down the page, and a rate-limit error message links straight to the same anchor.

### 2.2 Exact contract

- **Create a free key** — Shown when the account has no keys. Creates an Explore key; the secret is displayed once.
- **Rotate secret** — On every usable key. Issues a new secret and keeps the key's identity, plan, meter and history.
- **Cancel** — On recurring keys only. Ends the subscription at the end of the paid period — see [Cancelling](#cancel-subscription).
- **Upgrade ladder** — At `#upgrade`. Measured against the best plan you hold, not the first key in the list, so holding Explore and Growth does not offer you Growth again.
- **Upgrade banner** — Above the fold, on free plans only. It exists because the first upgrade button used to sit more than a screen down.
- **Billing** — Links to `/contact?topic=billing` for invoices and billing questions.
- **Sign out** — Top right. Clears the session cookie.

> **Awaiting payment** — A key created by a checkout that has not completed shows `past_due` / *Awaiting payment*, with a line saying it switches on as soon as payment completes. It is not an error and it needs no action beyond finishing the payment.

**See also**

- [Rotating a key](./account-sign-in.md#key-rotation)
- [Cancelling](./account-sign-in.md#cancel-subscription)
- [Checkout](./account-billing.md#checkout-flow)

<a id="key-rotation"></a>

## 3. Rotating a key

Issues a new secret for the same key. Plan, meter, usage history and entitlements all stay attached — it is not the same as creating a new key.

### 3.1 In depth

Rotate when a secret has leaked, or when you have lost it. Because secrets are stored as SHA-256 hashes, nobody can recover the old one for you, and rotation is the supported answer rather than a support email.

The distinction from creating a new key matters for the meter: a new key starts a fresh monthly allowance and a fresh usage history, while a rotated key keeps both. If you want a clean environment, create; if you want the same entitlement with a different secret, rotate.

> **Rotation is immediate** — The old secret stops working the moment the new one is issued. Deploy the new secret first, or accept a gap. There is no overlap window.

### 3.2 Exact contract

Rotation happens through the website session (`POST /public/auth/keys/{id}/rotate`), not through the API key — so an attacker holding a leaked key cannot rotate it away from you. Revocation (`.../revoke`) is permanent, and the key row is kept rather than deleted because usage attached to it has to stay auditable.

A revoked or rotated-away secret fails exactly like one that never existed: 401, same body. Probing cannot distinguish “was valid once” from “never was”.

**See also**

- [Creating, naming, rotating and revoking](./api-authentication.md#auth-keys-lifecycle)
- [What a rejected key looks like](./api-authentication.md#auth-failure-modes)

<a id="cancel-subscription"></a>

## 4. Cancelling

One control, on the key's own card. No email, no notice period — access continues to the end of the period you paid for.

### 4.1 In depth

Cancelling stops the renewal; it does not cut off the key you have already paid for. The card then shows **Access until** with the date, and the key keeps working until then. There is a resume control if you change your mind before it lapses.

Cancelling is not the same as revoking. Revoking kills the secret immediately; cancelling ends the billing relationship at the period boundary and leaves the key usable in the meantime.

### 4.2 Exact contract

| Action | Effect on the secret | Effect on billing | Where |
| --- | --- | --- | --- |
| Cancel | Keeps working until `period_end` | No further charges | The key's card on `/dashboard` |
| Resume | Unchanged | Renewal restarts | Same card, after cancelling |
| Rotate | Old secret dies immediately, new one issued | None | Same card |
| Revoke | Dies immediately and permanently | Does not itself stop a subscription — cancel as well | Same card |

For an invoice, a credit note, or anything the dashboard does not cover, [/contact?topic=billing](https://jobopportunitiesapi.org/contact?topic=billing) reaches a person. See [Billing](./account-billing.md).

**See also**

- [Invoices and credit notes](./account-billing.md#invoices)
- [Every control on the dashboard](./account-sign-in.md#dashboard-tour)
- [Checkout](./account-billing.md#checkout-flow)

---

## Where to go next

This file is part of **Account & billing**. Others in the same group:

- [Plans](./account-plans.md) — What each plan includes, rendered from the live price list, and what actually distinguishes them — which is records and entitlements, not features.
- [The record meter](./account-record-meter.md) — The licence is on rows delivered, not on requests made. This is the most commonly misunderstood mechanic here, and it changes how you write your client.
- [Billing, invoices and status](./account-billing.md) — What happens between clicking buy and the key switching on, where the invoice comes from, and how to tell whether the data behind the API is current.

Always useful:

- [index.md](./index.md) — the map of every file here
- [BUILD-A-SITE.md](./BUILD-A-SITE.md) — the paste-whole brief for building against this API
- [quickstart.md](./quickstart.md) — zero to a first authenticated response
- [api-errors.md](./api-errors.md) — every status code and whether to retry it
