---
title: "Change a page's URL"
method: POST
path: "/api/v1/sites/{slug}/routes/rename"
tags: ["Routes"]
---

# Change a page's URL

`POST /api/v1/sites/{slug}/routes/rename`

## Request

- Base URL: `https://api.dotsy.ai`
- URL: `https://api.dotsy.ai/api/v1/sites/{slug}/routes/rename`
- Auth: HTTP bearer

Moves a page's route from `from` to `to`. The whole route record moves (content key, layout, title, …), menu links and other redirects that pointed at the old path retarget automatically, and a 301 redirect is left at the old path unless `leaveRedirect` is false. The change is staged to the draft — publish to make it live.

It is safe to retry: resend the same call after any error and a partly finished move completes. If `to` already serves exactly the content `from` did, the move counts as already done and answers 200.

## Path parameters

- `slug` string, required

## Request body

- RenameRouteRequest
  - `from` string, required: The page's current route path: a site path without `?`, `#` or spaces.
  - `to` string, required: The new route path: a site path without `?`, `#` or spaces.
  - `leaveRedirect` boolean: Leave a 301 redirect from `from` to `to` (default true). `false` removes the old path outright, so old links break.

## Response `200`

Moved (staged).

- RenameRouteResponse
  - `route` object, required: The route at its new path.
    - `path` string, required: The URL path. The home page is `/`. A `:param` segment makes a pattern route.
    - `contentKey` string, nullable, required: The content key this route serves. Null on a redirect, and on a route with no explicit key (it then serves the key derived from its path).
    - `layout` string, nullable, required: Layout file key, for example `layouts/main.html`.
    - `title` string, nullable, required: Page title.
    - `vars` object, nullable, required: Template variables passed to the layout.
    - `purpose` string, nullable, required: `redirect` for a redirect route, otherwise null.
    - `redirectTo` string, nullable, required: Redirect target, a root-relative path. Set only when `purpose` is `redirect`.
    - `redirectStatus` union, required: Redirect status code. Null means the default (301).
      - 301
      - 302
      - 308
    - `preserveQuery` boolean, nullable, required: Whether a redirect appends the visitor's query string to its target. Null means the default (true).
    - `navLabel` string, nullable, required: Deprecated. Menu link text — use the menu API instead.
    - `nav` object[], nullable, required: Deprecated. Menu memberships — use the menu API instead.
      - `menu` string, required: Menu this page joins — matches a <dotsy-nav menu="…"> in the layout/fragment (e.g. "primary", "footer").
      - `order` integer: Sort position among siblings in this menu, ascending. Use gaps (10, 20, 30) to leave room for inserts.
      - `parent` string, nullable: Parent route path within this menu for a nested/dropdown item (e.g. "/services"). Omit for top level.
    - `experiment` object, nullable: The page's A/B test, when it has one.
      - `id` string, required: Stable experiment slug — cookie namespace; unique per site forever.
      - `name` string, required
      - `hypothesis` string
      - `status` 'draft' | 'running' | 'ended', required
      - `variants` object[], required
        - `id` string, required: Variant arm id — default "a" / "b" on create.
        - `label` string
        - `weight` integer, required
        - `contentKey` string, required: Published body key (may be routeless).
      - `goalId` string: Optional Dotsy conversion goal; omit when tracking in external analytics.
      - `changeNote` string: One line on what the variant changes (0.56e M2). Kept here so route PATCH round-trips (weights/settings updates) don't strip it.
      - `handsOff` boolean: Hands-off mode (0.56e M4): the daily sweep may auto-promote a ≥95% winner or auto-end keep-A after 30 days. Prefer setting this via start_test; kept here so route PATCH round-trips (weights/settings updates) don't strip the flag.
      - `startedAt` string
      - `endedAt` string
    - `noindex` boolean, nullable: True when the page is excluded from the sitemap and marked `noindex`.
    - `createdBy` 'person' | 'ai' | 'key' | 'move', nullable: Who made this route a redirect: a signed-in `person`, an `ai` (MCP OAuth), an API `key`, or a page `move`. Present only on redirects.
    - `createdById` string, nullable: The user id when `createdBy` is `person`.
    - `updatedAt` number, required: Last change, Unix ms.
  - `redirect` object: The 301 left at the old path. Absent when `leaveRedirect` is false.
    - `path` string, required: The URL path. The home page is `/`. A `:param` segment makes a pattern route.
    - `contentKey` string, nullable, required: The content key this route serves. Null on a redirect, and on a route with no explicit key (it then serves the key derived from its path).
    - `layout` string, nullable, required: Layout file key, for example `layouts/main.html`.
    - `title` string, nullable, required: Page title.
    - `vars` object, nullable, required: Template variables passed to the layout.
    - `purpose` string, nullable, required: `redirect` for a redirect route, otherwise null.
    - `redirectTo` string, nullable, required: Redirect target, a root-relative path. Set only when `purpose` is `redirect`.
    - `redirectStatus` union, required: Redirect status code. Null means the default (301).
      - 301
      - 302
      - 308
    - `preserveQuery` boolean, nullable, required: Whether a redirect appends the visitor's query string to its target. Null means the default (true).
    - `navLabel` string, nullable, required: Deprecated. Menu link text — use the menu API instead.
    - `nav` object[], nullable, required: Deprecated. Menu memberships — use the menu API instead.
      - `menu` string, required: Menu this page joins — matches a <dotsy-nav menu="…"> in the layout/fragment (e.g. "primary", "footer").
      - `order` integer: Sort position among siblings in this menu, ascending. Use gaps (10, 20, 30) to leave room for inserts.
      - `parent` string, nullable: Parent route path within this menu for a nested/dropdown item (e.g. "/services"). Omit for top level.
    - `experiment` object, nullable: The page's A/B test, when it has one.
      - `id` string, required: Stable experiment slug — cookie namespace; unique per site forever.
      - `name` string, required
      - `hypothesis` string
      - `status` 'draft' | 'running' | 'ended', required
      - `variants` object[], required
        - `id` string, required: Variant arm id — default "a" / "b" on create.
        - `label` string
        - `weight` integer, required
        - `contentKey` string, required: Published body key (may be routeless).
      - `goalId` string: Optional Dotsy conversion goal; omit when tracking in external analytics.
      - `changeNote` string: One line on what the variant changes (0.56e M2). Kept here so route PATCH round-trips (weights/settings updates) don't strip it.
      - `handsOff` boolean: Hands-off mode (0.56e M4): the daily sweep may auto-promote a ≥95% winner or auto-end keep-A after 30 days. Prefer setting this via start_test; kept here so route PATCH round-trips (weights/settings updates) don't strip the flag.
      - `startedAt` string
      - `endedAt` string
    - `noindex` boolean, nullable: True when the page is excluded from the sitemap and marked `noindex`.
    - `createdBy` 'person' | 'ai' | 'key' | 'move', nullable: Who made this route a redirect: a signed-in `person`, an `ai` (MCP OAuth), an API `key`, or a page `move`. Present only on redirects.
    - `createdById` string, nullable: The user id when `createdBy` is `person`.
    - `updatedAt` number, required: Last change, Unix ms.
  - `menuItemsUpdated` integer, required: Menu links that followed the page to its new URL.
  - `redirectsRetargeted` integer, required: Other redirects that pointed at the old path and now point at the new one.
  - `published` false, required: Always false: a route change is staged until you publish.
  - `hint` string, required

## Other responses

- `400`: The body is not valid (`invalid_body`): send two site paths without `?`, `#` or spaces. Or a path is platform-owned (`reserved_route`).
- `401`: Missing or invalid credentials.
- `403`: The key or token cannot edit this site (viewer role, guest token, or a key scoped to other sites).
- `404`: No such site (`site_not_found`), or no route at `from` (`route_not_found`).
- `409`: An A/B test is running on `from` (`rename_experiment_running`), or `to` is already a different route (`rename_target_exists`).
- `422`: `from` equals `to` (`rename_same_path`), either is a pattern route with a `:param` segment (`rename_pattern_route`), `from` is itself a different redirect (`rename_redirect_route`), or the route fails validation.

---

[API](https://skmtc.dev/dotsy/apis/dotsy-api.md) · [All operations](https://skmtc.dev/dotsy/apis/dotsy-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dotsy/apis/dotsy-api/revisions/4272ed3b85d7?raw)
