# Redirects en sitemap

Deze twee endpoints helpen een storefront zijn zoekpositie te houden wanneer de merchant iets hernoemt. Ze horen bij het platform, niet bij één module: elke module die content een adres geeft, draagt eraan bij. Catalogus-content gebruikt paden van een vaste vorm: een product staat op `/products/{slug}` en een categorie op `/categories/{slug}`. Contentrecords (Objecten) staan op het adresvoorvoegsel van het type voor de taal plus de recordslug, bijvoorbeeld `/diensten/keukenmontage`. Gebruikt je storefront andere URL's, map deze paden dan op je eigen routes.

De catalogus levert sitemap-items voor producten en categorieën, en Objecten levert één item per gepubliceerd recordadres (zie [GET /v1/api/sitemap](#get-v1apisitemap)). Wanneer de merchant de slug van een product, categorie of record wijzigt, legt Borkol een permanente redirect vast van het oude pad naar het nieuwe; voor een record gebeurt dat alleen als het type voor die taal een niet-leeg adresvoorvoegsel heeft. Ketens worden samengevouwen: `a` hernoemen naar `b` en daarna `b` naar `c` laat zowel `/products/a` als `/products/b` rechtstreeks naar `/products/c` wijzen. Een pad dat weer live wordt (een nieuw product neemt de oude slug) stopt met redirecten.

Beide endpoints nemen een tenant-API-key met één van de scopes `catalog:read`, `commerce:read`, `tenant:read` of `objects:read`, meegestuurd als `Authorization: Bearer {api_key}`. Ze vereisen de catalog-module niet: een winkel die alleen Objecten gebruikt kan ze ook aanroepen. De gedeelde tenant-rate-limit van 120 requests per minuut geldt.

## GET /v1/api/redirects

Zoekt de redirect op voor één storefront-pad. Roep het aan wanneer een product-, categorie- of recordrequest op je storefront anders een 404 zou geven, en stuur de bezoeker door met de teruggegeven `status`-code.

De lookup haalt één afsluitende slash weg (`/products/old-chair/` matcht `/products/old-chair`; het rootpad `/` blijft ongemoeid) en is hoofdlettergevoelig. Alleen exacte paden matchen: querystrings horen niet bij het pad.

### Queryparameters

| Naam | Type | Verplicht | Beschrijving |
| --- | --- | --- | --- |
| path | string | Ja | Het storefront-pad om op te zoeken, bijvoorbeeld `/products/old-chair` of `/diensten/oude-slug`. Maximaal 512 tekens. |

```bash title="Request"
curl "https://api.borkol.com/v1/api/redirects?path=%2Fproducts%2Fold-chair" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json"
```

```javascript title="Request (JS)"
const res = await fetch(
  "https://api.borkol.com/v1/api/redirects?" +
    new URLSearchParams({ path: "/products/old-chair" }),
  {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: "application/json",
    },
  }
);
if (res.status === 404) {
  // No redirect: render your own not-found page.
} else {
  const { data } = await res.json();
  // Redirect the visitor to data.to_path with data.status.
}
```

```json title="Response"
{
  "data": {
    "from_path": "/products/old-chair",
    "to_path": "/products/oak-dining-chair",
    "status": 301
  }
}
```

### Responsevelden

| Naam | Type | Verplicht | Beschrijving |
| --- | --- | --- | --- |
| from_path | string | altijd | Het opgeslagen oude pad (zonder afsluitende slash). |
| to_path | string | altijd | Het pad waarheen je de bezoeker stuurt. Redirects worden vastgelegd ongeacht de status van het product, de categorie of het record, en archiveren of depubliceren verwijdert er geen, dus `to_path` kan zelf onbeschikbaar zijn; controleer het voordat je doorstuurt. |
| status | integer | altijd | HTTP-status om mee te redirecten. Op dit moment altijd `301` (permanent). |

### Fouten

| Status | Body | Oorzaak |
| --- | --- | --- |
| 401 | `{"message":"Unauthenticated."}` | Ontbrekende of ongeldige API-key. |
| 403 | `{"message":"Invalid ability provided."}` | Key heeft geen van de scopes `catalog:read`, `commerce:read`, `tenant:read` of `objects:read`. |
| 403 | `{"message":"Tenant suspended.","code":"tenant_suspended"}` | Tenant-account is opgeschort. |
| 404 | `{"message":"..."}` | Er bestaat geen redirect voor dit pad. |
| 422 | `{"message":"...","errors":{"path":["..."]}}` | `path` ontbreekt, is geen string of is langer dan 512 tekens. |
| 429 | `{"message":"Too Many Attempts."}` | Rate-limit van 120 requests per minuut overschreden. |

## GET /v1/api/sitemap

Lijst elk live pad van de voor de winkel ingeschakelde modules op, klaar om als `sitemap.xml` te renderen. Een uitgeschakelde module draagt niets bij, dus een winkel zonder catalog-module krijgt geen product- of categorie-items (de respons is dan een lege `data`-array, geen fout). De catalog-module levert alle producten met status `active` en alle actieve categorieën, eerst de producten, dan de categorieën, elke groep gesorteerd op slug. De Objects-module levert één item per publiek zichtbaar record, taal en adres, voor objecttypes met een niet-leeg adresvoorvoegsel voor die taal; types zonder voorvoegsels hebben geen publieke pagina's en blijven weg, net als concepten en ingeplande records. De respons is niet gepagineerd.

### Parameters

Geen.

```bash title="Request"
curl "https://api.borkol.com/v1/api/sitemap" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json"
```

```json title="Response"
{
  "data": [
    {
      "type": "product",
      "path": "/products/oak-dining-chair",
      "lastmod": "2026-10-11T09:14:33+00:00"
    },
    {
      "type": "category",
      "path": "/categories/chairs",
      "lastmod": "2026-10-02T15:20:11+00:00"
    },
    {
      "type": "object_record",
      "object_type": "service",
      "locale": "nl",
      "path": "/diensten/keukenmontage",
      "lastmod": "2026-10-09T12:41:07+00:00",
      "alternates": {
        "en": "/services/kitchen-fitting",
        "nl": "/diensten/keukenmontage"
      }
    }
  ]
}
```

### Responsevelden

Elk item in `data`:

| Naam | Type | Verplicht | Beschrijving |
| --- | --- | --- | --- |
| type | string | altijd | `product`, `category` of `object_record`. |
| path | string | altijd | Storefront-pad: `/products/{slug}`, `/categories/{slug}`, of voor een record het adresvoorvoegsel van het type voor de taal plus het adres van het record, bijvoorbeeld `/services/kitchen-fitting`. |
| lastmod | string (ISO 8601) of null | altijd | Wanneer het product, de categorie of het record voor het laatst is gewijzigd. |
| object_type | string | alleen `object_record` | Sleutel van het objecttype van het record. |
| locale | string | alleen `object_record` | De taal van het adres van dit item. |
| alternates | object | alleen `object_record` | Taaltag naar pad, voor elke ingeschakelde taal waarin het record een openbaar adres heeft (inclusief dat van dit item zelf). Gebruik het voor `hreflang`-links. |

### Fouten

| Status | Body | Oorzaak |
| --- | --- | --- |
| 401 | `{"message":"Unauthenticated."}` | Ontbrekende of ongeldige API-key. |
| 403 | `{"message":"Invalid ability provided."}` | Key heeft geen van de scopes `catalog:read`, `commerce:read`, `tenant:read` of `objects:read`. |
| 403 | `{"message":"Tenant suspended.","code":"tenant_suspended"}` | Tenant-account is opgeschort. |
| 429 | `{"message":"Too Many Attempts."}` | Rate-limit van 120 requests per minuut overschreden. |
