# GET /nav/v1

Status: current

The shared navigation menu rendered by every internal tool. Read-only and identical for every caller.

### Response body

| Field | Type | Required | Default | Note |
|---|---|---|---|---|
| `version` | 1 | required | — |  |
| `items` | array of NavLink \| NavGroup | required | — |  |

### `NavLink` — A single navigation link.

| Field | Type | Required | Default | Note |
|---|---|---|---|---|
| `type` | "link" | required | — |  |
| `id` | string | required | — |  |
| `label` | string | required | — |  |
| `href` | string | required | — |  |
| `icon` | string | optional | — |  |
| `target` | "_self" \| "_blank" | optional | — | Absent means "_self". Served verbatim — not defaulted server-side. |

### `NavGroup` — A labelled group of links, rendered as a single level of nesting.

| Field | Type | Required | Default | Note |
|---|---|---|---|---|
| `type` | "group" | required | — |  |
| `id` | string | required | — |  |
| `label` | string | required | — |  |
| `icon` | string | optional | — |  |
| `items` | array of NavLink | required | — | Links only — groups cannot nest groups. |

## Example response

```json
{
  "version": 1,
  "items": [
    {
      "type": "link",
      "id": "utm-builder",
      "label": "UTM link builder",
      "href": "https://utm.basworld.online",
      "icon": "link",
      "target": "_self"
    },
    {
      "type": "group",
      "id": "marketing",
      "label": "Marketing",
      "icon": "megaphone",
      "items": [
        {
          "type": "link",
          "id": "feed-monitor",
          "label": "Feed monitor",
          "href": "https://feeds.basworld.online",
          "icon": "rss",
          "target": "_self"
        },
        {
          "type": "link",
          "id": "ads-dashboard",
          "label": "Ads dashboard",
          "href": "https://ads.basworld.online",
          "target": "_blank"
        }
      ]
    }
  ]
}
```

## Consumer notes

- One level of nesting: a group's items are links only, never groups.
- id values are a public contract — never rename or reuse one; retire it and add a new id instead.
- Order is exactly as written in the response. Do not sort items client-side.
- Links with target "_blank" must be rendered with rel="noopener noreferrer".
- Forward compatibility: skip node types you don't recognise and skip malformed nodes; only treat the whole menu as unavailable if nothing valid survives.

## How to implement this endpoint

1. Fetch `GET https://api.basbuiting.nl/nav/v1` cross-origin, with a short
   timeout. If your tool's hostname isn't already allowed, see
   `docs/adding-a-tool.md` in this repo.
2. Validate the response. Either copy this endpoint's `schema.ts` from the
   `shared-endpoints` repo, or fetch `/nav/v1/schema.json` and generate
   types/validation for your own stack from the JSON Schema.
3. Cache the last good response in `localStorage` (or equivalent) under a
   key like `"nav-v1"`, and render that immediately on load while
   re-fetching in the background.
4. Forward compatibility: skip fields/node types you don't recognise and
   skip malformed entries — only fall back to your local default if nothing
   valid survives. Never treat an unknown field as an error.
5. Do not sort or reorder the response — order is meaningful and exactly as
   returned.
6. See "Consumer notes" above for rules specific to this endpoint.

Minimal fetch-validate-cache sketch:

```ts
async function fetchNavResponse() {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 2000);
  try {
    const res = await fetch("https://api.basbuiting.nl/nav/v1", { signal: controller.signal });
    if (!res.ok) throw new Error(`${res.status}`);
    const data = await res.json(); // validate with the schema before trusting it
    localStorage.setItem("nav-v1", JSON.stringify(data));
    return data;
  } catch {
    const cached = localStorage.getItem("nav-v1");
    return cached ? JSON.parse(cached) : null; // or your local fallback
  } finally {
    clearTimeout(timeout);
  }
}
```

---

Machine-readable schema: /nav/v1/schema.json
Human-readable docs: /
