Getting Started
Redirects and sitemap
Resolve renamed paths to their new address, and list every live path of the enabled modules for a sitemap.
View as MarkdownThese two endpoints help a storefront keep its search ranking when the merchant renames things. They belong to the platform, not to one module: any module that gives content an address contributes to them. Catalog content uses paths of a fixed shape: a product lives at /products/{slug} and a category at /categories/{slug}. Content records (Objects) live at the type's address prefix for the language plus the record slug, for example /services/kitchen-fitting. If your storefront uses different URLs, map these paths onto your own routes.
The catalog contributes sitemap entries for products and categories, and Objects contributes one entry per published record address (see GET /v1/api/sitemap). When the merchant changes a product, category or record slug, Borkol records a permanent redirect from the old path to the new one; for a record this happens only when the type has a non-empty address prefix for that language. Chains collapse: renaming a to b and then b to c leaves both /products/a and /products/b pointing straight at /products/c. A path that becomes live again (a new product takes the old slug) stops redirecting.
Both endpoints take a tenant API key carrying any one of the catalog:read, commerce:read, tenant:read or objects:read scopes, sent as Authorization: Bearer {api_key}. They do not require the catalog module: a store that only uses Objects can call them too. The shared tenant rate limit of 120 requests per minute applies.
These two endpoints help a storefront keep its search ranking when the merchant renames things. They belong to the platform, not to one module: any module that gives content an address contributes to them. Catalog content uses paths of a fixed shape: a product lives at /products/{slug} and a category at /categories/{slug}. Content records (Objects) live at the type's address prefix for the language plus the record slug, for example /services/kitchen-fitting. If your storefront uses different URLs, map these paths onto your own routes.
The catalog contributes sitemap entries for products and categories, and Objects contributes one entry per published record address (see GET /v1/api/sitemap). When the merchant changes a product, category or record slug, Borkol records a permanent redirect from the old path to the new one; for a record this happens only when the type has a non-empty address prefix for that language. Chains collapse: renaming a to b and then b to c leaves both /products/a and /products/b pointing straight at /products/c. A path that becomes live again (a new product takes the old slug) stops redirecting.
Both endpoints take a tenant API key carrying any one of the catalog:read, commerce:read, tenant:read or objects:read scopes, sent as Authorization: Bearer {api_key}. They do not require the catalog module: a store that only uses Objects can call them too. The shared tenant rate limit of 120 requests per minute applies.
GET /v1/api/redirects
Looks up the redirect for one storefront path. Call it when a product, category or record request on your storefront would otherwise answer 404, and send the visitor on with the returned status code.
The lookup strips one trailing slash (/products/old-chair/ matches /products/old-chair; the root path / is left alone) and is case-sensitive. Only exact paths match: query strings are not part of the path.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| path | string | Yes | The storefront path to look up, for example /products/old-chair or /services/old-slug. At most 512 characters. |
Response fields
| Name | Type | Required | Description |
|---|---|---|---|
| from_path | string | always | The stored old path (without a trailing slash). |
| to_path | string | always | The path to send the visitor to. Redirects are recorded whatever the status of the product, category or record, and archiving or unpublishing does not remove one, so to_path itself may be unavailable; check it before redirecting. |
| status | integer | always | HTTP status to redirect with. Currently always 301 (permanent). |
Errors
| Status | Body | Cause |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Missing or invalid API key. |
| 403 | {"message":"Invalid ability provided."} | Key carries none of the catalog:read, commerce:read, tenant:read or objects:read scopes. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Tenant account is suspended. |
| 404 | {"message":"..."} | No redirect exists for this path. |
| 422 | {"message":"...","errors":{"path":["..."]}} | path is missing, not a string, or longer than 512 characters. |
| 429 | {"message":"Too Many Attempts."} | Rate limit of 120 requests per minute exceeded. |
curl "https://api.borkol.com/v1/api/redirects?path=%2Fproducts%2Fold-chair" \
-H "Authorization: Bearer {api_key}" \
-H "Accept: application/json"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.
}{
"data": {
"from_path": "/products/old-chair",
"to_path": "/products/oak-dining-chair",
"status": 301
}
}GET /v1/api/redirects
Looks up the redirect for one storefront path. Call it when a product, category or record request on your storefront would otherwise answer 404, and send the visitor on with the returned status code.
The lookup strips one trailing slash (/products/old-chair/ matches /products/old-chair; the root path / is left alone) and is case-sensitive. Only exact paths match: query strings are not part of the path.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| path | string | Yes | The storefront path to look up, for example /products/old-chair or /services/old-slug. At most 512 characters. |
curl "https://api.borkol.com/v1/api/redirects?path=%2Fproducts%2Fold-chair" \
-H "Authorization: Bearer {api_key}" \
-H "Accept: application/json"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.
}{
"data": {
"from_path": "/products/old-chair",
"to_path": "/products/oak-dining-chair",
"status": 301
}
}Response fields
| Name | Type | Required | Description |
|---|---|---|---|
| from_path | string | always | The stored old path (without a trailing slash). |
| to_path | string | always | The path to send the visitor to. Redirects are recorded whatever the status of the product, category or record, and archiving or unpublishing does not remove one, so to_path itself may be unavailable; check it before redirecting. |
| status | integer | always | HTTP status to redirect with. Currently always 301 (permanent). |
Errors
| Status | Body | Cause |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Missing or invalid API key. |
| 403 | {"message":"Invalid ability provided."} | Key carries none of the catalog:read, commerce:read, tenant:read or objects:read scopes. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Tenant account is suspended. |
| 404 | {"message":"..."} | No redirect exists for this path. |
| 422 | {"message":"...","errors":{"path":["..."]}} | path is missing, not a string, or longer than 512 characters. |
| 429 | {"message":"Too Many Attempts."} | Rate limit of 120 requests per minute exceeded. |
GET /v1/api/sitemap
Lists every live path of the modules enabled for the store, ready to render as a sitemap.xml. A module that is disabled contributes nothing, so a store without the catalog module gets no product or category entries (the response is then an empty data array, not an error). The catalog module contributes all products with status active and all categories that are active, products first, then categories, each group ordered by slug. The Objects module contributes one entry per publicly visible record, language and address, for object types that have a non-empty address prefix for that language; types without prefixes have no public pages and are left out, and so are drafts and scheduled records. The response is not paginated.
Parameters
None.
Response fields
Each entry in data:
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | always | product, category or object_record. |
| path | string | always | Storefront path: /products/{slug}, /categories/{slug}, or for a record the type's address prefix for the language plus the record's address, for example /services/kitchen-fitting. |
| lastmod | string (ISO 8601) or null | always | When the product, category or record was last updated. |
| object_type | string | object_record only | Key of the record's object type. |
| locale | string | object_record only | The language of this entry's address. |
| alternates | object | object_record only | Language tag to path, for every enabled language in which the record has a public address (including this entry's own). Use it for hreflang links. |
Errors
| Status | Body | Cause |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Missing or invalid API key. |
| 403 | {"message":"Invalid ability provided."} | Key carries none of the catalog:read, commerce:read, tenant:read or objects:read scopes. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Tenant account is suspended. |
| 429 | {"message":"Too Many Attempts."} | Rate limit of 120 requests per minute exceeded. |
curl "https://api.borkol.com/v1/api/sitemap" \
-H "Authorization: Bearer {api_key}" \
-H "Accept: application/json"{
"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"
}
}
]
}GET /v1/api/sitemap
Lists every live path of the modules enabled for the store, ready to render as a sitemap.xml. A module that is disabled contributes nothing, so a store without the catalog module gets no product or category entries (the response is then an empty data array, not an error). The catalog module contributes all products with status active and all categories that are active, products first, then categories, each group ordered by slug. The Objects module contributes one entry per publicly visible record, language and address, for object types that have a non-empty address prefix for that language; types without prefixes have no public pages and are left out, and so are drafts and scheduled records. The response is not paginated.
Parameters
None.
curl "https://api.borkol.com/v1/api/sitemap" \
-H "Authorization: Bearer {api_key}" \
-H "Accept: application/json"{
"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"
}
}
]
}Response fields
Each entry in data:
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | always | product, category or object_record. |
| path | string | always | Storefront path: /products/{slug}, /categories/{slug}, or for a record the type's address prefix for the language plus the record's address, for example /services/kitchen-fitting. |
| lastmod | string (ISO 8601) or null | always | When the product, category or record was last updated. |
| object_type | string | object_record only | Key of the record's object type. |
| locale | string | object_record only | The language of this entry's address. |
| alternates | object | object_record only | Language tag to path, for every enabled language in which the record has a public address (including this entry's own). Use it for hreflang links. |
Errors
| Status | Body | Cause |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Missing or invalid API key. |
| 403 | {"message":"Invalid ability provided."} | Key carries none of the catalog:read, commerce:read, tenant:read or objects:read scopes. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Tenant account is suspended. |
| 429 | {"message":"Too Many Attempts."} | Rate limit of 120 requests per minute exceeded. |