---
title: Rename a website, or move its functions
description: "Body { slug } renames the site: the slug rule runs, the unique index refuses a slug another site holds, the Vercel project is renamed, the new <slug>.vercel.app host is read back into the live URL, and the old one is removed."
api_method: PATCH
api_path: "/v1/workspaces/{workspaceId}/sites/{siteId}"
canonical_url: https://wiblo.app/docs/developers/api/sites/update-workspace-site
last_updated: 2026-09-28T10:47:20+02:00
md_url: https://wiblo.app/docs/developers/api/sites/update-workspace-site.md
---

# Rename a website, or move its functions

`PATCH /v1/workspaces/{workspaceId}/sites/{siteId}`

Body `{ slug }` renames the site: the slug rule runs, the unique index refuses a slug another site holds, the Vercel project is renamed, the new `<slug>.vercel.app` host is read back into the live URL, and the old one is removed. Body `{ region }` patches the project's function region, applied on the next build. Exactly one of the two. Needs an owner or admin; a run's workspace key carries its initiator's role, because a name is not a publish. Per-user rate-limited at 10/60s in its own bucket.

## Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `workspaceId` | `string` | Yes |  |
| `siteId` | `string` | Yes |  |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `slug` | `string` | No | The site's new public name. Send this or `region`, not both. |
| `region` | `string` | No | Where the site's functions run from the next build: iad1, sfo1, cpt1, syd1, or lhr1. Send this or `slug`, not both. |

## Request

**curl**

```bash
curl https://api.wiblo.app/v1/workspaces/{workspaceId}/sites/{siteId} \
  -X PATCH \
  -H "Authorization: Bearer $WIBLO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "slug": "driftwood-physio"
}'
```

**TypeScript**

```ts
import { createSdk, updateWorkspaceSite } from "@workspace/sdk"

const sdk = createSdk({ baseUrl: "https://api.wiblo.app" })

const { data, error } = await updateWorkspaceSite({
  client: sdk,
  path: { workspaceId: "...", siteId: "..." },
  body: {
    "slug": "driftwood-physio"
  },
})
```

## Responses

**`200`** — The site after the change. Returns `SiteSummary`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes |  |
| `repo_id` | `string \| null` | Yes |  |
| `display_name` | `string` | Yes |  |
| `slug` | `string` | Yes |  |
| `region` | `"iad1" \| "sfo1" \| "cpt1" \| "syd1" \| "lhr1"` | Yes |  |
| `status` | `"provisioning" \| "ready" \| "failed" \| "archived"` | Yes |  |
| `live_url` | `string \| null` | Yes |  |
| `published_version` | `integer \| null` | Yes | The number of the version the live URL serves; null while nothing is published. |
| `latest_version` | `object \| null` | Yes | The newest version that is `queued`, `building`, `ready`, or `error`, with its `number` and its `status`. The `number` is null until that version is first ready. An `error` version whose deployment is gone keeps the number it had. Null when the site has no such version. |
| `latest_version.number` | `integer \| null` | Yes |  |
| `latest_version.status` | `string` | Yes |  |
| `created_at` | `string` | Yes |  |
| `updated_at` | `string` | Yes |  |

**`400`** — Body failed validation (`VALIDATION_FAILED`): neither or both of `slug` and `region`. Returns `ApiErrorEnvelope`.

**`401`** — No valid session, `wbl_*` bearer, or run token was present. Returns `ApiErrorEnvelope`.

**`403`** — The caller is a member, not an owner or admin (`ADMIN_REQUIRED`). Returns `ApiErrorEnvelope`.

**`404`** — Workspace not visible to the caller (`WORKSPACE_NOT_FOUND`), or no site with that id in it (`SITE_NOT_FOUND`). Returns `ApiErrorEnvelope`.

**`409`** — Another site holds the slug, or Vercel holds a project of that name (`SLUG_TAKEN`). Returns `ApiErrorEnvelope`.

**`422`** — Path param failed UUID validation (`INVALID_PARAMS`), or the slug or region is refused (`SLUG_INVALID`, `SLUG_RESERVED`, `REGION_INVALID`). Returns `ApiErrorEnvelope`.

**`429`** — Rate limit exceeded. The body's `error.code` is `RATE_LIMITED` and `error.details.retry_after` is the same number of seconds as the `Retry-After` header. Returns `ApiErrorEnvelope`.

**`502`** — Vercel did not answer past the client's retries (`PROVIDER_DOWN`), Vercel refused the rename or the region with its reason, or refused the token or the plan so a retry will not help (`PROVIDER_REJECTED`), or the deploy provider is not configured (`UPSTREAM_UNAVAILABLE`). The row is unchanged. Returns `ApiErrorEnvelope`.

**`503`** — Vercel is rate limited; try again after `details.retry_after` seconds, at most a minute (`PROVIDER_BUSY`). Returns `ApiErrorEnvelope`.
