---
title: "Update a site's settings"
method: PATCH
path: "/api/v1/sites/{slug}"
tags: ["Sites"]
---

# Update a site's settings

`PATCH /api/v1/sites/{slug}`

## Request

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

Changes the settings you send and leaves the rest alone. `title` is the display name. `keyConvention` is `flat` or `directory` (how content keys map to URLs). `editingMode` is `live` (every editor save goes public) or `staged` (saves stay drafts until you publish); API writes always stage. `signature` sets the "Made with Dotsy" badge and takes effect on the live site at once. `trailingSlash` (`ignore`, the default, `never` or `always`) sets the page URL form: with `never` or `always` the other form 301s to it, and the canonical tag and sitemap use it; it takes effect on the live site at once. `ownSeoFiles` (off by default) lets the next publish use the site's own robots.txt when it blocks the whole site (`Disallow: /`) and its own static sitemap.xml, which replaces the generated one; a robots.txt without `Disallow: /` is used either way. `integrations` resolves pending clone integrations. `aiAccess` sets what AI agents and API keys may do on the site (`viewer`, `contributor`, `editor` or `owner`, the default). Only the site owner signed in to the dashboard can change it: an API key or MCP token answers 403 `ai_access_limited`.

A guest token can change the title but not `editingMode` (it answers 403 `guest_requires_account`).

## Path parameters

- `slug` string, required

## Request body

- UpdateSiteRequest: Only the fields you send are changed. Send at least one of `title`, `keyConvention`, `editingMode`, `signature`, `trailingSlash`, `ownSeoFiles`, `aiAccess` or `integrations`.
  - `title` string
  - `keyConvention` 'flat' | 'directory'
  - `editingMode` 'live' | 'staged': How the editor's Save behaves. 'live' (default) publishes each save immediately; 'staged' keeps saves as drafts until publish_site. Prefer 'staged' when an agent will hand the editor to a person (see start_edit_session → draftEditorUrl) so keystrokes don't go straight to the public site. API writes always stage regardless of this setting.
  - `signature` object: "Made with Dotsy" signature badge. Takes effect on the live site immediately, no publish needed.
    - `enabled` boolean: Show the badge in the bottom-right corner of every published page.
    - `mark` 'logo' | 'dot': Resting look: 'dot' (senna dot, default) or 'logo' (Dotsy logo).
    - `reveal` 'pill' | 'seal': On hover: 'seal' (rotating stamp, default) or 'pill' ("Made with Dotsy").
  - `trailingSlash` 'ignore' | 'never' | 'always': Page URL form. 'ignore' (default): /blog and /blog/ both serve the page. 'never': /blog/ 301s to /blog. 'always': /blog 301s to /blog/. The canonical tag and sitemap use the same form. Live immediately, no publish needed.
  - `ownSeoFiles` boolean: Use the site's own robots.txt and sitemap.xml even when they can hurt search: a robots.txt that blocks the whole site (Disallow: /), and a static sitemap.xml, which replaces the generated one (collection pages included). Off by default; a robots.txt without Disallow: / is always used. Takes effect at the next publish.
  - `aiAccess` 'viewer' | 'contributor' | 'editor' | 'owner': What AI agents and API keys may do on this site: `viewer` (read only), `contributor` (save drafts; a person publishes), `editor` (drafts and publish) or `owner` (full access, the default). Only an owner signed in to the dashboard can change it.
  - `integrations` object[]: Resolve pending clone integrations (analytics stripped at import).
    - `id` string, required
    - `status` 'replaced' | 'dismissed', required
    - `replacedWith` string

## Response `200`

Updated.

- UpdateSiteResponse
  - `ok` boolean, required
  - `title` string, nullable, required
  - `editingMode` 'live' | 'staged', required
  - `keyConvention` 'flat' | 'directory', required
  - `updatedAt` number, required
  - `integrations` object[]: Present when the request resolved integrations: every integration after the change.
    - `id` string, required
    - `vendor` string, required
    - `sourceId` string, required
    - `status` 'pending' | 'replaced' | 'dismissed', required
    - `pageCount` number, required
  - `signature` object: The saved signature badge choice (present when the request changed it).
    - `enabled` boolean, required
    - `mark` 'logo' | 'dot', required
    - `reveal` 'pill' | 'seal', required
  - `trailingSlash` 'ignore' | 'never' | 'always': The saved trailing-slash policy (present when the request changed it).
  - `ownSeoFiles` boolean: The saved ownSeoFiles switch (present when the request changed it). Takes effect at the next publish.
  - `aiAccess` 'viewer' | 'contributor' | 'editor' | 'owner': The saved AI access level (present when the request changed it).

## Other responses

- `400`: The body is not valid JSON or not a valid settings object, or it changes nothing (`invalid_body`, with `issues`).
- `401`: Missing or invalid credentials.
- `403`: The key or token cannot edit this site: viewer role (`forbidden_role`), a read-only key (`forbidden_read_only`), a guest token changing `editingMode` (`guest_requires_account`), AI access set below what this change needs, or an API key or MCP token sending `aiAccess` (`ai_access_limited`). A signed-in non-owner sending `aiAccess` gets `forbidden_role`.
- `404`: No such site, or it is not visible to this account.
- `409`: The site is being renamed right now (`rename_active`). Retry when it finishes.
- `422`: An `integrations` id does not match a pending integration on this site (`invalid_integrations`).
- `429`: Over the account's write budget (`rate_limited`). Retry after the `Retry-After` seconds.

---

[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)
