---
title: "Change the files, the name or the password of a site"
method: PUT
path: "/sites/{id}"
tags: ["Sites"]
---

# Change the files, the name or the password of a site

`PUT /sites/{id}`

## Request

- Base URL: `https://api.hypamoon.com/api/v1`
- URL: `https://api.hypamoon.com/api/v1/sites/{id}`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://app.hypamoon.com/oauth/authorize`, token URL `https://api.hypamoon.com/oauth/token`, scopes: `sites:write`)

Changes the name, the files, the password, or a combination. The slug and the url stay the same. The usual way is a partial update with keep_existing: true: only the files in files are added or replaced, delete_paths removes files, every other file stays, so images already on the site are never sent again. A partial update needs if_match_version, the version from GET; if the site has changed since, or if_match_version is missing, the request returns 409 version_conflict with current_version and nothing changes. Without keep_existing, files is the complete new set of files: a file you leave out is removed, and the response lists those paths in warnings (code files_removed). With version history, send message (what changed and why) so the history reads like a logbook; without it Hypamoon writes a short summary like "Replaced 6 files with 1, -88% size". Guardrail: an update without keep_existing that removes more than half of the files, makes the site more than half smaller, or rewrites more than 70% of /index.html while other files disappear, does not go live. It becomes a preview (preview.url, version_id) and the response has guardrail with what would disappear in plain words; the version that was live gets auto_label "before big change". Ask the user, then publish the preview with POST /sites/{id}/versions/{versionId}/promote, or send the update again with confirm_replace: true. preview: true makes a preview on purpose. Files can come as source_url instead of content, as with POST /sites. source_url: Hypamoon fetches the file once from a public https URL on port 443 (no IP address or internal host, at most 3 redirects, each checked again; 10 s per file) within the plan's size limits. Its type must match the path: an image or font is recognized by its bytes, text must be UTF-8 and not a web page where a .css or .js was expected. The fetched file then goes through the same checks as one sent with content. A file that cannot be fetched returns 422 with the reason at files.<i>.source_url, and nothing changes. At most 100 files with source_url per request and 60 such requests per project per hour (429 rate_limited). The limits, the abuse checks and four-eyes approval always apply to the whole site as it will be after the update; a 202 requests approval for that whole site. The new version is live as soon as this request returns, for every visitor and as a whole: all files switch together, never a new page with old css. version in the response is the new one. A static site has no logins or server-side code of its own. To share it with a few people, set password (one shared password, entered once per browser) or make it unlisted; paid access gives each buyer a personal sign-in link. For server-side code and sign-in per user, add an app with auth to the site (POST /apps). Not yet: files over the limits. Every deploy is checked before it goes live: input fields for passwords, PIN codes, card numbers or IBANs, forms that send to another site, hidden iframes, redirects to another site, obfuscated scripts, and login fields next to the name of a bank, government service, parcel or payment service or large platform return 422 site_blocked with the reasons. A new project has a lower limit on deploys per hour and on sites in its first week (deploy_limit_reached, site_limit_reached). Setting or changing password logs every visitor out (they enter the new one once); null or an empty string removes it and makes the site open to everyone. Sending the same password again changes nothing. A site that was open may still come from the CDN for up to a minute after its first password is set; after that nothing of it is cached.

## Path parameters

- `id` string, uuid, required

## Request body

- SiteUpdate: Change a site: a new name, new files, a password (or null to remove it), or a combination. The slug stays the same. The usual way: send only the changed files with keep_existing: true and if_match_version, images preferably as source_url.
  - `name` string: A name for your own overview, like the business or the page.
  - `files` object[]: Usually with keep_existing: true: only the files you add or change; every other file stays as it is, so never send images that are already on the site again. Without keep_existing, files is the complete new set of files: a file you leave out is removed, and the answer lists it in warnings.
    - `path` string, required: Where the file lives on the site, like /index.html, /style.css or /img/logo.png. Relative paths (index.html) get a leading /.
    - `content` string: The file itself: plain text for html, css, js, json, txt, xml, svg, webmanifest and md; base64 for png, jpg, gif, webp, ico, woff, woff2, pdf, mp4, webm, mp3 and m4a only when there is no URL for it (prefer source_url; a video or audio file via source_url or an upload, never base64). Leave it out for a file with source_url, or one you uploaded with create_upload_urls (POST /sites/uploads): then send sha256 and bytes.
    - `source_url` string: Instead of content: a public https URL of the file (port 443), for images, fonts, PDFs, video and audio above all. Hypamoon fetches it once (at most 3 redirects, within the size limits of the plan) and checks it like a file sent with content. Its type must match the path (a .jpg path needs a JPEG, a .mp4 path an MP4). Leave encoding out. Use it instead of base64 whenever the file has a URL; never send a video as base64.
    - `encoding` 'utf8' | 'base64': utf8 for text, base64 for images, fonts, PDF, video and audio. Leave it out: it follows from the file type.
    - `content_type` string: Leave it out: it follows from the extension (.html is text/html). Only for a path without a known extension, and only an allowed type.
    - `sha256` string: Only for an uploaded file, instead of content: the SHA-256 of its bytes as 64 lowercase hex characters, as you sent it to create_upload_urls.
    - `bytes` integer: Only for an uploaded file, instead of content: its exact size in bytes.
  - `keep_existing` boolean: true: a partial update. Only the files in files are added or replaced, delete_paths removes files, and every other file stays. Needs if_match_version. The limits, the checks and four-eyes approval apply to the whole site as it will be after the update.
  - `delete_paths` string[]: Only with keep_existing: true. Paths of existing files to remove, like /old.html. A path that is not on the site is an error.
  - `media` string[]: Optional. Files from the project's media library (list_media) to put on the site, without uploading them again: folders like "portfolio/" (with subfolders), file ids, or paths like /media/logo.webp. Each file lands at its library path with its 640 version next to it; private originals never go on a site. A file you also send in files wins.
  - `if_match_version` string: The version of the site you are changing, from list_sites, get_site or the last deploy (version). Required with keep_existing. If the site changed since, you get 409 version_conflict with current_version and nothing changes: read the site again and redo your change.
  - `password` string, nullable: Optional. One shared password for the whole site (4 to 72 characters): visitors enter it once per browser, like password protection on a preview. Not a login system: no accounts, no per-visitor access. null or an empty string removes it. Never returned; the site shows password_protected instead.
  - `message` string: Optional, but always send it: what changed and why, in one short sentence (up to 200 characters), like a commit message: "New opening hours for the summer" or "Spring menu, the user asked for it". It shows in the version history; without it Hypamoon writes a short summary like "Changed /index.html".
  - `preview` boolean: true: do not put this change live. It becomes a preview version at a secret link (preview.url in the answer, not indexed), and the live site stays as it is. Put it live later with promote_site_version. Only files (with keep_existing as usual), no name or password. Use it for large changes the user should see first.
  - `confirm_replace` boolean: Only after the user agreed. A deploy without keep_existing that removes more than half of the files, makes the site more than half smaller, or rewrites most of /index.html while other files disappear, is held as a preview instead of going live (guardrail in the answer). Send confirm_replace: true only when the user has said yes to replacing the site like that; then it goes live right away.

## Response `200`

The site as it is now.

- Site: A static site on hypamoon.app.
  - `id` string, uuid, required: The id of the site. Use it to update or delete the site.
  - `slug` string, required: The first part of the address: https://<slug>.hypamoon.app.
  - `name` string, required: Your name for the site.
  - `url` string, required: The public address of the site, live right away.
  - `files` SiteFile[], required: The files of the site, sorted by path, without their content.
    - `path` string, required: The path of the file on the site, like /index.html.
    - `content_type` string, required: The type it is served with, like text/html.
    - `bytes` integer, required: The size of the file in bytes.
    - `updated_at` string, date-time, required: When the file was last written.
  - `created_at` string, date-time, required: When the site was created.
  - `updated_at` string, date-time, required: When the site or its files last changed.
  - `version` string, required: The version that is live now. Changes on every update; every file on the site carries it as its ETag, so you can check that a page shows this version.
  - `password_protected` boolean, required: true when visitors have to enter the site's password first (set with password on POST /sites or PUT /sites/{id}). The password itself is never returned.
  - `test` boolean, required: true for a site of a test project. It is public all the same.
  - `taken_offline` object, nullable, required: null while the site is live. Set when Hypamoon took the site offline because of abuse: visitors get a page that says it is unavailable, the files are kept, and PUT and DELETE return 403 site_offline. To object, the owner replies to the email or writes to hello@hypamoon.com.
    - `at` string, date-time, required: When Hypamoon took the site offline.
    - `reason` string, required: Why, in the words the owner also received by email.
  - `warnings` union[]: Only in the answer of a deploy, and only when there is something to tell: files a deploy without keep_existing removed, and build hints. Warnings never block a deploy.
    - union
      - object
        - `code` 'files_removed', required: A deploy without keep_existing removed files that were on the site.
        - `message` string, required: What happened and what to do if it was not intended.
        - `paths` string[], required: The paths that are gone.
      - object
        - `code` 'build_hint', required: Something on the site looks like it works but does not: a form that saves nowhere, localStorage used as a database, a password checked in JavaScript, a button to # or javascript:, a cart without a checkout, test wording on the live project, or a map from a service that needs a key the site does not have. Advice only: the deploy succeeded.
        - `hint` string, required: Which hint, like form_no_target, local_storage_db or cart_without_checkout.
        - `message` string, required: What does not work, in plain words.
        - `paths` string[], required: The files the hint is about.
        - `next_step` string, required: What to do next, with the tool to use.

## Other responses

- `202`: Four-eyes approval is on: the update waits for another owner or builder. Nothing has changed on the live site yet.
- `400`: Bad Request: invalid_json.
- `401`: Unauthorized: unauthorized, missing_api_key, invalid_api_key, api_key_revoked, project_deleted.
- `403`: Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline, phone_verification_required.
- `404`: Not Found: not_found.
- `409`: Conflict: version_conflict.
- `422`: Unprocessable Content: validation_failed, site_too_large, upload_not_received, site_blocked.
- `429`: Too Many Requests: rate_limited, deploy_limit_reached.
- `500`: Internal Server Error: internal_error.
- `503`: Service Unavailable: service_unavailable.

---

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