A page redirect sends readers who open an old path on your help center to an article or a collection. The Notiondesk API lets you list, create, update and delete page redirects, so you can import hundreds of old URLs after a migration instead of adding them one by one in the dashboard. Bookmarks, shared links and search engine results keep working.
To manage redirects in the dashboard instead, see How to manage page redirects in your help center.
| Method | Path | Scope | Does |
|---|---|---|---|
GET | /sites/{site_id}/page-redirects | read | Lists redirects, newest first |
POST | /sites/{site_id}/page-redirects | write | Creates a redirect |
PATCH | /sites/{site_id}/page-redirects/{redirect_id} | write | Updates a redirect |
DELETE | /sites/{site_id}/page-redirects/{redirect_id} | admin | Deletes a redirect |
Creating and updating redirects requires a plan that includes page redirects.
List page redirects
curl "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/page-redirects?page_size=100" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16"The list covers every language, newest first. It includes the redirects you create and the ones Notiondesk creates automatically after a slug change.
{
"object": "list",
"data": [
{
"id": "3f1c2a9e-8b7d-4c6e-9a5f-1e2d3c4b5a69",
"locale": "en",
"type": "article",
"origin": "manual",
"source_path": "/legacy/getting-started",
"target_id": "1761580412",
"target_title": "Getting started",
"target_slug": "getting-started",
"target_status": "published"
}
],
"has_more": false,
"next_cursor": null
}| Field | Meaning |
|---|---|
id | The redirect identifier, used as redirect_id |
locale | The language of the target |
type | article or category (a collection) |
origin | manual for a redirect someone created, system for one Notiondesk created when a slug changed |
source_path | The path that redirects |
target_id | The id of the target article or collection |
target_title, target_slug | The target's title and path segment in locale |
target_status | The target's status, such as published or draft |
The list reaches at most the 10,000 newest redirects. If your integration deletes redirects while it lists them, start again from the first page afterwards: a deletion shifts the following items, so the next page can skip one. See Paginate and version Notiondesk API requests.
Create a page redirect
curl -X POST "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/page-redirects" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f0d6b1e-create-getting-started" \
-d '{"type": "article", "source_path": "/legacy/getting-started", "target_id": "1761580412", "locale": "en"}'Every field is required:
| Field | Type | Notes |
|---|---|---|
type | string | article or category |
source_path | string | The path to redirect, up to 512 characters, for example /legacy/getting-started. No other redirect of the help center may use it |
target_id | string | The id of the target article or collection, from the article or collection list. Up to 255 characters |
locale | string | The language of the target. The target must have a translation in it |
The route answers 201 Created with the new redirect, in the same fields as the list.
- A
source_pathanother redirect already uses answers 409conflict_error, and the message names the path.
- A
target_idwith no translation inlocaleanswers 404not_found, and the message names the id.
Send an Idempotency-Key with each create. A retry with the same key and body answers the same 201 without creating a second redirect. See Handle Notiondesk API errors, rate limits, and retries.
Update a page redirect
Send only the fields to change. Every field you leave out keeps its current value.
curl -X PATCH "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/page-redirects/3f1c2a9e-8b7d-4c6e-9a5f-1e2d3c4b5a69" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16" \
-H "Content-Type: application/json" \
-d '{"source_path": "/legacy/start"}'The fields follow the create rules. The route answers 200 OK with the updated redirect.
The redirect must still point to a target that exists in its locale after the change. To point an article redirect at a collection, send type and target_id together.
Delete a page redirect
curl -i -X DELETE "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/page-redirects/3f1c2a9e-8b7d-4c6e-9a5f-1e2d3c4b5a69" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Deleting cannot be undone, so the route needs two safeguards:
- the
adminscope;
- the body
{"confirm": true}, with the JSON booleantrue.
The route answers 204 No Content. Readers who open the old path stop being redirected. Deleting the same redirect again answers 404 not_found, unless you retry with the same Idempotency-Key.
admin scope works only on a key created by a workspace owner or Admin. An Editor can delete a redirect in the dashboard, but not with an API key.Import redirects after a migration
Moving from another help center tool? Map each old path to its new article, then create one redirect per row. This script reads a CSV file with the columns source_path,target_id,locale:
import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";
const siteId = "c74781f6-1aea-4e17-ab23-bbb8b4d174b6";
const rows = readFileSync("redirects.csv", "utf8").trim().split(/\r?\n/).slice(1);
for (const row of rows) {
const [source_path, target_id, locale] = row.split(",");
const idempotencyKey = createHash("sha256").update(`${source_path}|${target_id}|${locale}`).digest("hex");
const response = await fetch(`https://api.notiondesk.so/sites/${siteId}/page-redirects`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NOTIONDESK_API_KEY}`,
"nd-version": "2026-10-16",
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({ type: "article", source_path, target_id, locale }),
});
if (response.status === 429) {
const seconds = Number(response.headers.get("Retry-After")) || 5;
console.log(`Rate limited, waiting ${seconds}s`);
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
rows.push(row);
continue;
}
const result = await response.json();
console.log(response.status, source_path, response.ok ? result.id : result.code);
}- The key is derived from the row, so running the script twice creates nothing twice. A row you correct gets a new key.
- A path that already has a redirect logs
conflict_error. Update it instead, or leave it.
- Writes are limited to 20 requests per minute per key, so 500 redirects take about 25 minutes. The script waits when it is rate-limited, then retries the row.
- Use a key with the
readandwritescopes only.
Errors
| Status | code | When |
|---|---|---|
| 400 | validation_error | A field is missing or empty, type or locale is not accepted, a value is too long, confirm is not true, or the body has an unknown field |
| 403 | insufficient_scope | The key lacks read, write or admin, depending on the route |
| 403 | permission_denied | Your plan does not include page redirects, or the key's creator cannot change this help center |
| 404 | not_found | The help center or the redirect does not exist in your workspace, or the target has no translation in locale |
| 409 | conflict_error | Another redirect of the help center already uses source_path |
See Handle Notiondesk API errors, rate limits, and retries for the error envelope and the other codes.
Common questions
Do I need to create redirects when I change a slug?
Usually not. Notiondesk creates the redirect itself when you change a slug you set by hand on a published article or collection, automatic redirects are on for the help center, and your plan includes page redirects. Those redirects show origin: "system" in the list. In any other case, create the redirect yourself. Learn more about slugs in How to create pretty URLs for articles and collections.
Can a redirect point to an external URL?
No. A page redirect points to an article or a collection of the same help center.
Why can my key create redirects but not delete them?
Deleting needs the admin scope, which creating and updating do not. Add admin to the key, and check that its creator is a workspace owner or Admin.