# Quickstart

# Quickstart

This guide takes you from a token to a working call. Every example is shown in
**curl**, **Python** and **TypeScript** — pick your tab and paste.

## Before you start

You'll need:

- An AirTrack **premium** subscription on the owning account (premium is checked on
  every call).
- A **connection token** (`aapk_…`). See [Authentication](/authentication) for the
  full pairing flow; the short version is to redeem a pairing code created in the
  AirTrack app:

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

Requests go to one base URL per environment:

| Environment | Base URL |
| ----------- | -------- |
| Production  | `https://api.airawarelabs.com` |
| Development | `https://api.airawarelabs-dev.dev` |

## 1. Set up an authenticated client

Every request carries your token in the `Authorization` header. Set it up once and
reuse it for every call below.

<CodeTabs>

```bash title="curl"
export AIRTRACK_BASE="https://api.airawarelabs.com"
export AIRTRACK_TOKEN="aapk_pdn_…"
```

```python title="Python"
import requests

BASE = "https://api.airawarelabs.com"
session = requests.Session()
session.headers["Authorization"] = "Bearer aapk_pdn_…"
```

```typescript title="TypeScript"
const BASE = "https://api.airawarelabs.com";
const TOKEN = process.env.AIRTRACK_TOKEN!; // aapk_pdn_…

async function airtrack(path: string, body?: unknown) {
  const res = await fetch(`${BASE}${path}`, {
    method: body ? "POST" : "GET",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json();
}
```

</CodeTabs>

## 2. Score a location

Get the instantaneous **Clean Air Score** (100 = cleanest, 0 = worst) for a point.
Requires the `aq:read` scope; costs **1 credit**.

<CodeTabs>

```bash title="curl"
curl -X POST "$AIRTRACK_BASE/v1/airquality/score" \
  -H "Authorization: Bearer $AIRTRACK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "lat": 51.5074, "lon": -0.1278 }'
```

```python title="Python"
r = session.post(f"{BASE}/v1/airquality/score", json={"lat": 51.5074, "lon": -0.1278})
print(r.json())  # {'score': 82, 'ttl': 3600}
```

```typescript title="TypeScript"
const score = await airtrack("/v1/airquality/score", { lat: 51.5074, lon: -0.1278 });
console.log(score); // { score: 82, ttl: 3600 }
```

</CodeTabs>

```json title="Response"
{ "score": 82, "ttl": 3600 }
```

`ttl` is the number of seconds until it's worth polling again — the underlying data
updates hourly, so there's no value in polling faster.

## 3. Forecast a day

Hourly Clean Air Scores and pollutant concentrations for a date at a location.
Requires `aq:read`; costs **2 credits**. Omit the coordinates to use the caller's
approximate IP location.

<CodeTabs>

```bash title="curl"
curl -X POST "$AIRTRACK_BASE/v1/airquality/forecast" \
  -H "Authorization: Bearer $AIRTRACK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "latitude": 51.5074,
        "longitude": -0.1278,
        "date": "2026-07-08",
        "timezone": "Europe/London"
      }'
```

```python title="Python"
r = session.post(
    f"{BASE}/v1/airquality/forecast",
    json={
        "latitude": 51.5074,
        "longitude": -0.1278,
        "date": "2026-07-08",
        "timezone": "Europe/London",
    },
)
print(r.json())
```

```typescript title="TypeScript"
const forecast = await airtrack("/v1/airquality/forecast", {
  latitude: 51.5074,
  longitude: -0.1278,
  date: "2026-07-08",
  timezone: "Europe/London",
});
console.log(forecast);
```

</CodeTabs>

```json title="Response"
{
  "location": "London, UK",
  "timestamps": [1751972400, 1751976000],
  "measures": {
    "clean_air_score": [82.0, 79.0],
    "pm25": [8.1, 9.4]
  }
}
```

`timestamps` is a shared time axis (epoch seconds); each list in `measures` runs
parallel to it.

## 4. Enrich a route

Score a route you supply — per-waypoint measures plus a route-level exposure summary.
Requires the `aq:routes` scope; costs **1 credit per waypoint**. Provide a `time` on
every waypoint (a recorded track), or omit them all and set a `start_time` to have
the times estimated from the activity's speed.

<CodeTabs>

```bash title="curl"
curl -X POST "$AIRTRACK_BASE/v1/airquality/routes" \
  -H "Authorization: Bearer $AIRTRACK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "waypoints": [
          { "lat": 51.5074, "lon": -0.1278, "time": "2026-07-08T08:00:00Z" },
          { "lat": 51.5121, "lon": -0.1236, "time": "2026-07-08T08:12:00Z" }
        ],
        "activity_type": "walking",
        "environment_type": "outdoor"
      }'
```

```python title="Python"
r = session.post(
    f"{BASE}/v1/airquality/routes",
    json={
        "waypoints": [
            {"lat": 51.5074, "lon": -0.1278, "time": "2026-07-08T08:00:00Z"},
            {"lat": 51.5121, "lon": -0.1236, "time": "2026-07-08T08:12:00Z"},
        ],
        "activity_type": "walking",
        "environment_type": "outdoor",
    },
)
print(r.json())
```

```typescript title="TypeScript"
const enriched = await airtrack("/v1/airquality/routes", {
  waypoints: [
    { lat: 51.5074, lon: -0.1278, time: "2026-07-08T08:00:00Z" },
    { lat: 51.5121, lon: -0.1236, time: "2026-07-08T08:12:00Z" },
  ],
  activity_type: "walking",
  environment_type: "outdoor",
});
console.log(enriched);
```

</CodeTabs>

```json title="Response"
{
  "waypoints": [[51.5074, -0.1278], [51.5121, -0.1236]],
  "measures": {
    "clean_air_score": [82.0, 78.0],
    "pm25": [8.1, 9.6]
  },
  "summary": {
    "clean_air_score": { "score": 80.0, "band": "5", "description": "Excellent", "colour": "#00C853" },
    "inhaled_dose": 38.4,
    "average_pollutants": { "pm25": 8.85 }
  }
}
```

To **plan** a lower-exposure route (rather than score one you already have), see
[`POST /v1/routes`](/api/route-planning) in the API reference — it's an asynchronous
submit-then-poll workflow.

## 5. Check your remaining credits

Endpoints that compute air quality draw from one shared monthly **credit pool**, with a
smaller daily burst cap — see [Usage & credits](/credits) for the full cost table.
Reading your balance is itself free, as are redeeming a pairing code and polling for a
route-plan result. Any valid token works, no particular scope required.

<CodeTabs>

```bash title="curl"
curl "$AIRTRACK_BASE/v1/quota" \
  -H "Authorization: Bearer $AIRTRACK_TOKEN"
```

```python title="Python"
r = session.get(f"{BASE}/v1/quota")
print(r.json())
```

```typescript title="TypeScript"
const quota = await airtrack("/v1/quota");
console.log(quota);
```

</CodeTabs>

```json title="Response"
{
  "used": 128,
  "limit": 1000,
  "remaining": 872,
  "daily_used": 40,
  "daily_limit": 100,
  "daily_remaining": 60
}
```

## Handling errors

Every handled error returns a JSON body with a single `detail` string, and rate
limits come back as `429` with a `Retry-After` header. See [Errors](/errors) for the
full status-code reference and a robust retry pattern in Python and TypeScript.

## Next steps

- Read the [Authentication](/authentication) guide for the full credential and scope
  model.
- Browse the complete [API Reference](/api) for every endpoint, field and example.
- Point your tooling or AI assistant at <a href="/openapi.json" target="_blank" rel="noopener noreferrer"><code>/openapi.json</code></a> or <a href="/llms-full.txt" target="_blank" rel="noopener noreferrer"><code>/llms-full.txt</code></a>.
