# Areas API

Geographic hierarchy and postcode lookup.

Base URL path: `/api`

Send `Accept: application/json` on all requests.

## Hierarchy

```
Country → Region → County → Town → Postcode
```

## Resource fields

Shared on every resource (postcodes use `code` instead of `name`):

| Field | Type | Notes |
|-------|------|--------|
| `name` / `code` | string | Display name, or postcode code |
| `slug` | string | Unique URL-safe identifier |
| `latitude` | number | Optional WGS84 latitude |
| `longitude` | number | Optional WGS84 longitude |
| `eastings` | integer | Optional OSGB eastings |
| `northings` | integer | Optional OSGB northings |

Towns also include:

| Field | Type | Notes |
|-------|------|--------|
| `nearest_postcode` | string | Optional nearest postcode |

Postcodes also include:

| Field | Values |
|-------|--------|
| `status` | `live`, `terminated`, `unknown` |

## Authentication

| Action | Auth |
|--------|------|
| List, show, nearby | None (public) |
| Create, update, delete | Bearer token — `Authorization: Bearer YOUR_TOKEN` |

## Read endpoints

### List (paginated, 50 per page)

```
GET /api/countries
GET /api/regions?country_id=
GET /api/counties?region_id=
GET /api/towns?county_id=&name=
GET /api/postcodes?town_id=&status=
```

Optional filters narrow lists by parent id (or `status` for postcodes). See [Search towns by name](#search-towns-by-name) for the towns `name` parameter.

### Show by slug

```
GET /api/countries/{slug}
GET /api/regions/{slug}
GET /api/counties/{slug}
GET /api/towns/{slug}
GET /api/postcodes/{slug}
```

Town slugs are `{town-name}-{county-name}` (for example `waterton-aberdeenshire`). Prefer [name search](#search-towns-by-name) when you only know the town name.

### Example: list countries

```bash
curl -H "Accept: application/json" \
  "https://example.com/api/countries"
```

### Example: fetch a postcode

```bash
curl -H "Accept: application/json" \
  "https://example.com/api/postcodes/sw1a-1aa"
```

## Search towns by name

Town display names are not unique across the UK (for example several places named Newton). Slugs encode both town and county (`newton-lancashire`), so clients should search by `name` instead of guessing the slug.

```
GET /api/towns?name={query}
GET /api/towns?name={query}&county_id={id}
```

| Param | Required | Notes |
|-------|----------|--------|
| `name` | yes (for search) | Partial, case-insensitive match against the town `name` field |
| `county_id` | no | Limit results to one county |
| page / pagination | no | Standard list pagination (50 per page) |

Results are ordered alphabetically by `name`.

### Example

```bash
curl -H "Accept: application/json" \
  "https://example.com/api/towns?name=Waterton"
```

Example response:

```json
{
  "data": [
    {
      "id": 27643,
      "county_id": 88,
      "name": "Waterton",
      "slug": "waterton-aberdeenshire",
      "nearest_postcode": "AB419RY",
      "latitude": 57.3650017,
      "longitude": -2.0432,
      "eastings": null,
      "northings": null
    }
  ],
  "links": {},
  "meta": {}
}
```

Combine with a county when the name is ambiguous:

```bash
curl -H "Accept: application/json" \
  "https://example.com/api/towns?name=Newton&county_id=12"
```

## Nearby postcodes

Find postcodes within a radius of a point. **Maximum radius is 20km.**

```
GET /api/postcodes/nearby?lat={lat}&lng={lng}&radius_km={km}&status={status}
```

| Param | Required | Default | Notes |
|-------|----------|---------|--------|
| `lat` | yes | — | -90 to 90 |
| `lng` | yes | — | -180 to 180 |
| `radius_km` | no | `20` | Between `0.1` and `20` |
| `status` | no | `live` | `live`, `terminated`, or `unknown` |

Distance uses Haversine on latitude/longitude. Results include `distance_km`, ordered nearest first.

```bash
curl -H "Accept: application/json" \
  "https://example.com/api/postcodes/nearby?lat=51.5014&lng=-0.1419&radius_km=5"
```

Example response:

```json
{
  "data": [
    {
      "id": 1,
      "town_id": 1,
      "code": "SW1A 1AA",
      "slug": "sw1a-1aa",
      "status": "live",
      "latitude": 51.5014,
      "longitude": -0.1419,
      "eastings": 529154,
      "northings": 179756,
      "distance_km": 0.0
    }
  ]
}
```

`radius_km` above 20 returns `422`.

## Write endpoints

Require a Sanctum API token.

```
POST   /api/countries
PUT    /api/countries/{id}
DELETE /api/countries/{id}

POST   /api/regions
PUT    /api/regions/{id}
DELETE /api/regions/{id}

POST   /api/counties
PUT    /api/counties/{id}
DELETE /api/counties/{id}

POST   /api/towns
PUT    /api/towns/{id}
DELETE /api/towns/{id}

POST   /api/postcodes
PUT    /api/postcodes/{id}
DELETE /api/postcodes/{id}
```

Show uses `{slug}`; update and delete use numeric `{id}`.

### Example: create a postcode

```bash
curl -X POST https://example.com/api/postcodes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "town_id": 1,
    "code": "SW1A 1AA",
    "slug": "sw1a-1aa",
    "status": "live",
    "latitude": 51.5014,
    "longitude": -0.1419,
    "eastings": 529154,
    "northings": 179756
  }'
```

## Response shape

Payloads are wrapped under `data`. List endpoints use pagination (`data`, `links`, `meta`). Create returns `201`. Delete returns `204`.
