# Authentication

# Authentication

Every request to the AirTrack API is authenticated with a **bearer token**:

```http
Authorization: Bearer aapk_pdn_…
```

A credential is a **connection**. It carries a set of **scopes** (the capabilities
it's allowed to use) and is owned by either a **user** or a **partner**. How you
obtain a token depends on what you're building — but once you have one, every call
works the same way.

## Which integration are you building?

{/* Icons are Lucide (https://lucide.dev, ISC/MIT), inlined as monochrome SVG. */}

<div className="aal-features">
  <a className="aal-card" href="#user-owned-connections">
    <span className="aal-icon">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <rect width="16" height="16" x="4" y="4" rx="2" />
        <rect width="6" height="6" x="9" y="9" rx="1" />
        <path d="M15 2v2" /><path d="M15 20v2" /><path d="M2 15h2" /><path d="M2 9h2" />
        <path d="M20 15h2" /><path d="M20 9h2" /><path d="M9 2v2" /><path d="M9 20v2" />
      </svg>
    </span>
    <h3>Connected device</h3>
    <p>
      A device that reads air quality for its owner — a display, wearable or
      sensor. The user pairs it from the AirTrack app.
    </p>
  </a>
  <a className="aal-card" href="#user-owned-connections">
    <span className="aal-icon">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
        <circle cx="12" cy="7" r="4" />
      </svg>
    </span>
    <h3>Per-user integration</h3>
    <p>
      A service that enriches a specific user's data on their behalf. Paired by
      that user, the same way as a device.
    </p>
  </a>
  <a className="aal-card" href="#partner-connections">
    <span className="aal-icon">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z" />
        <path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2" />
        <path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2" />
        <path d="M10 6h4" /><path d="M10 10h4" /><path d="M10 14h4" /><path d="M10 18h4" />
      </svg>
    </span>
    <h3>B2B partner</h3>
    <p>
      A commercial integration scoring routes or enriching data at scale, under
      a partner agreement.
    </p>
  </a>
</div>

A **connected device** and a **per-user integration** both obtain a token through
the [pairing-code flow](#user-owned-connections). A **B2B partner** is
[issued a token directly](#partner-connections).

## Getting a token

### User-owned connections

For devices and per-user integrations. The owning user pairs your client from the
AirTrack app; the pairing grants it a set of scopes fixed by the server. This requires
the user to have an active AirTrack **premium** subscription.

1. **The user creates a pairing code.** In the AirTrack app, the user generates a
   6-digit code granting the standard integration scopes — `aq:read`, `aq:routes`
   and `routes:plan`. The code is valid for **60 seconds** and can be used once.
2. **The user enters the code into your client.**
3. **Your client redeems the code for a token.** No authentication is required —
   the code is the proof. Supply a `name` to identify the connection:

   ```bash
   curl -X POST https://api.airawarelabs.com/v1/connections/token \
     -H "Content-Type: application/json" \
     -d '{ "code": "048213", "name": "Living-room display" }'
   # → { "connection_id": "…", "name": "Living-room display",
   #     "scopes": ["aq:read", "aq:routes", "routes:plan"], "token": "aapk_pdn_…" }
   ```

4. **Store the token securely** and use it for every subsequent call.

The scopes are fixed by the server when the code is minted — a client cannot request
or escalate its own scopes. Premium is re-checked on every call that computes air quality or
submits a route plan, so those start returning `403` if the user's subscription lapses.
Reading your quota and polling for a plan result are not premium-gated — a lapsed user can
still collect a result they already paid for.

See [`POST /v1/connections/token`](/api/connections#redeem-pairing-code) in the API
reference.

### Partner connections

For B2B integrations — scoring routes or enriching data at scale — tokens are
issued directly under a partner agreement rather than through pairing. Get in touch
at [support@airawarelabs.com](mailto:support@airawarelabs.com) to set up a partner
account. Your token carries the scopes agreed for your integration and stays valid
while your partner account is active.

## Authenticating requests

Send the token as a bearer header on **every** request:

```bash
curl -X POST https://api.airawarelabs.com/v1/airquality/score \
  -H "Authorization: Bearer aapk_pdn_…" \
  -H "Content-Type: application/json" \
  -d '{ "lat": 51.5, "lon": -0.12 }'
# → { "score": 82, "ttl": 3600 }   # Clean Air Score: 100 = cleanest, 0 = worst
```

Each endpoint requires a specific scope. If your token lacks it, the call returns
`403`. Tokens are environment-specific — the segment after `aapk_` (`pdn`, `tst`)
tells you which environment issued it.

## Scopes

Scopes name **capabilities**, not consumers. They're fixed when the connection is
created — by the server at pairing, or with a partner token — and can't be changed by
the client.

| Scope | Allows |
|-------|--------|
| `aq:read` | Read air quality for points and forecasts (current and historical). |
| `aq:routes` | Enrich a route you supply with per-waypoint air quality. |
| `routes:plan` | Plan lower-exposure routes between two points. |

## Token security

- The raw token is shown **once**, when the connection is created — store it
  securely and never expose it in client-side web code. We keep only a hash.
- **Revoking** a connection (from the AirTrack app) invalidates its token
  immediately — the next call returns `401`.
- To **rotate** a token, revoke the connection and pair again.

## Error responses

The two you'll meet most often when getting authentication right:

| Status | Meaning |
|--------|---------|
| `401` | Missing, unknown or revoked token; or an invalid/expired pairing code. |
| `403` | Your token isn't permitted to perform that operation — usually a missing scope; on the endpoints that compute air quality or submit a plan, also a lapsed premium plan. Causes vary per endpoint; see its own description. |

See [Errors](/errors) for the full status-code reference, the error envelope, and a
robust retry pattern for rate limits.
