Catalog
Redirects and sitemap
Resolve renamed product and category paths to their new address, and list every live catalog path for a sitemap.
View as MarkdownThese two endpoints help a storefront keep its search ranking when the merchant renames things. Borkol speaks in storefront paths of a fixed shape: a product lives at /products/{slug} and a category at /categories/{slug}. If your storefront uses different URLs, map these paths onto your own routes.
When the merchant changes a product or category slug, Borkol records a permanent redirect from the old path to the new one. 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 use the catalog authentication: a tenant API key carrying either the catalog:read or the commerce:read scope, sent as Authorization: Bearer {api_key}, and the catalog module enabled for the store. 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. Borkol speaks in storefront paths of a fixed shape: a product lives at /products/{slug} and a category at /categories/{slug}. If your storefront uses different URLs, map these paths onto your own routes.
When the merchant changes a product or category slug, Borkol records a permanent redirect from the old path to the new one. 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 use the catalog authentication: a tenant API key carrying either the catalog:read or the commerce:read scope, sent as Authorization: Bearer {api_key}, and the catalog module enabled for the store. 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 or category 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. 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 product's or category's status, and archiving 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 neither the catalog:read nor the commerce:read scope. |
| 403 | {"error":"Module not enabled: catalog"} | Tenant does not have the catalog module enabled. |
| 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 or category 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. 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 product's or category's status, and archiving 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 neither the catalog:read nor the commerce:read scope. |
| 403 | {"error":"Module not enabled: catalog"} | Tenant does not have the catalog module enabled. |
| 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 catalog path, ready to render as a sitemap.xml: all products with status active and all categories that are active. Products come first, then categories, each group ordered by slug. The response is not paginated.
Parameters
None.
Response fields
Each entry in data:
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | always | product or category. |
| path | string | always | Storefront path: /products/{slug} or /categories/{slug}. |
| lastmod | string (ISO 8601) or null | always | When the product or category was last updated. |
Errors
| Status | Body | Cause |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Missing or invalid API key. |
| 403 | {"message":"Invalid ability provided."} | Key carries neither the catalog:read nor the commerce:read scope. |
| 403 | {"error":"Module not enabled: catalog"} | Tenant does not have the catalog module enabled. |
| 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"
}
]
}GET /v1/api/sitemap
Lists every live catalog path, ready to render as a sitemap.xml: all products with status active and all categories that are active. Products come first, then categories, each group ordered by slug. 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"
}
]
}Response fields
Each entry in data:
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | always | product or category. |
| path | string | always | Storefront path: /products/{slug} or /categories/{slug}. |
| lastmod | string (ISO 8601) or null | always | When the product or category was last updated. |
Errors
| Status | Body | Cause |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Missing or invalid API key. |
| 403 | {"message":"Invalid ability provided."} | Key carries neither the catalog:read nor the commerce:read scope. |
| 403 | {"error":"Module not enabled: catalog"} | Tenant does not have the catalog module enabled. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Tenant account is suspended. |
| 429 | {"message":"Too Many Attempts."} | Rate limit of 120 requests per minute exceeded. |