Skip to main content
This guide walks the full editing loop over the API: lease, write files, edit routes and metadata, and validate the result.

Prerequisites

Source file model

Every website owns a set of versioned source files. Each write that changes anything creates a new head version; history is append-only. Named files take a name of 1–255 characters. Singleton types take no name. A file’s content can be at most 1,048,576 bytes, and a website can hold at most 2,000 source files.
1

Acquire an edit lease

All source mutations require an edit lease. Acquire one with POST /v1/websiteSourceCode/lease/acquire.
Acquire a lease
Response
A website has exactly one active lease. Acquiring a lease replaces the previous token, so any other editor’s token stops working immediately. Include leaseToken in every mutation below.
2

List and read files

Orient with GET /v1/websiteSourceCode/files. It lists file metadata with size and line count, without contents, in pages of 20 by default (maximum 100).
List the head files
Response (truncated)
When nextCursor is not null, pass it as cursor and pass the response version as version on the next request. Pinning the resolved version keeps the file inventory stable if the head changes while you page.Then fetch only the files you need with POST /v1/websiteSourceCode/files/read (up to 50 names per call; singletons are addressed by their type name):
Read specific files
The response returns each file’s content and totalLines. When reading a single file, startLine and endLine select a line range. If truncated is true, continue by passing nextStartCharacter as startCharacter with the same line range and the response version as version. Pinning the version prevents concurrent edits from changing the file between chunks. endCharacter can bound the character range. Pass target: "published" for the live version.When you know what you are looking for but not where it lives, skip the listing and search instead with POST /v1/websiteSourceCode/files/search:
Find every file mentioning the old brand name
Response
See Finding code for regular expressions, glob and type filters, and the filesOnly mode.
3

Write a page

A page module imports definePage from @website and default-exports the call. The route must be an inline string literal, for example '/' or '/blog/:slug'.
pages/about
Upsert it with PUT /v1/websiteSourceCode/files. The file identity is a nested file object: { "type": "page", "name": "about" } for named types, or { "type": "global_css" } for singletons.
The response is { "version": 5, "changed": true }. A write that changes nothing returns changed: false and does not create a version. DELETE /v1/websiteSourceCode/files removes a file with the same { websiteId, file, leaseToken } body.Reusable modules use the same endpoint with type: "module". Their path-like name becomes the stable @modules/<name> import. A module exports plain TypeScript values and can import other registered modules; it does not need a default export or a React component:
modules/lib/config
Upsert a reusable module
Import it from a page, component, or another module with import { siteName } from '@modules/lib/config'. As with components, the extensionless name, explicit .tsx form, nested names, and a root registry import are supported. import modules from '@modules' exposes each file as a namespace such as modules['lib/config']. Relative imports remain unsupported. Module values ship to the browser with the rest of the site source, so never store secrets in them.Singletons work the same way without a name. For example, third-party tags go in a custom-code slot:
Set a custom head snippet
Use the focused guides for each singleton’s source contract:
4

Make targeted edits with string replacement

For targeted changes, POST /v1/websiteSourceCode/files/str-replace edits files in place without resending their whole content. Each edit’s oldStr must match the current content exactly once; set replaceAll: true to replace every occurrence. Edits apply in order against the evolving content of their file, and every edit across every file commits as a single version. Send up to 50 files and 200 edits per call.
Rename a component across two pages
The response reports the blast radius per file:
If any oldStr matches zero or multiple locations, the call fails with a 400 naming the offending file and edit, and nothing is written. Read the file again, or add more surrounding context to make the match unique.Before a wide replace, send the same body with "dryRun": true. It validates every edit and returns the same per-file counts with "changed": false, leaving version where it was.
5

Edit routes and page metadata

Route and metadata edits rewrite the page source for you, so you avoid string manipulation. PATCH /v1/websiteSourceCode/pages/route takes the page’s source name and the new route:
Change a route
The response adds "route": "/company" and "routeChanged": true to the usual { version, changed }.PATCH /v1/websiteSourceCode/metadata sets a metadata object (title, description, openGraph, robots, and more). With page set it edits that page’s metadata; without page it edits the site-wide defaults in website_config, creating the file when missing.Page metadata also supports {{ ... }} templates that resolve CMS query results, route parameters, and query parameters at request time. See Configure SEO metadata for the complete schema, template syntax, fallbacks, and rendering behavior.
Set website default metadata
The response returns metadata and metadataChanged alongside { version, changed }.
6

Validate the head

Review progressively before running the framework gate. First list file identities that changed from published to head:
List changed files
Then request a unified diff only for files that need source-level inspection:
Diff one file
Both operations default to from=published and to=head. The changed-file list is cursor-paginated; continue with nextCursor and the returned fromVersion and toVersion so later edits do not move the comparison.Run the framework quality gate with POST /v1/websiteSourceCode/head/check before you publish. It compiles and validates the head version without changing anything.
Check the head
You should see { "version": 6, "ok": true }. A failing check returns { "ok": false, "message": "...", "details": { ... } } pointing at the broken file. Fix it and check again.
When the check passes, publish with POST /v1/websiteSourceCode/head/publish (body { websiteId, leaseToken }). See Publishing for deployment states.

Version conflicts and recovery

Source writes are guarded two ways, and both failures return 409 with kind conflict:
  • Invalid edit lease — your leaseToken was replaced because someone else acquired the lease, or it never matched.
  • Website source changed; reload and try again — the head version moved between your read and your write.
Recovery is the same loop in both cases:
  1. Re-acquire the lease with POST /v1/websiteSourceCode/lease/acquire and note the returned headVersion.
  2. Re-read the affected files with POST /v1/websiteSourceCode/files/read.
  3. Re-apply your edit against the current content.
Re-acquiring a lease invalidates the other editor’s token. Coordinate before you take a lease from a human editing in the dashboard.

Troubleshooting

Next steps