Prerequisites
- An API key with
full_editoraccess — see Create an API key - A website to edit — see the Quickstart
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 A website has exactly one active lease. Acquiring a lease replaces the previous token, so any other editor’s token stops working immediately. Include
POST /v1/websiteSourceCode/lease/acquire.Acquire a lease
Response
leaseToken in every mutation below.2
List and read files
Orient with When The response returns each file’s See Finding code for regular expressions, glob and type filters, and the
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)
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
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
filesOnly mode.3
Write a page
A page module imports Upsert it with The response is Import it from a page, component, or another module with Use the focused guides for each singleton’s source contract:
definePage from @website and default-exports the call. The route must be an inline string literal, for example '/' or '/blog/:slug'.pages/about
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.{ "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 { 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
- Configure routes and redirects for page routes and
website_config.redirects - Style a website for raw CSS, Tailwind CSS, and class discovery
- Customize site-wide files for
not_foundand the four raw HTML slots
4
Make targeted edits with string replacement
For targeted changes, The response reports the blast radius per file:If any
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
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. The response adds The response returns
PATCH /v1/websiteSourceCode/pages/route takes the page’s source name and the new route:Change a route
"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
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:Then request a unified diff only for files that need source-level inspection:Both operations default to When the check passes, publish with
List changed files
Diff one file
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.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 return409 with kind conflict:
Invalid edit lease— yourleaseTokenwas 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.
- Re-acquire the lease with
POST /v1/websiteSourceCode/lease/acquireand note the returnedheadVersion. - Re-read the affected files with
POST /v1/websiteSourceCode/files/read. - Re-apply your edit against the current content.
Troubleshooting
Next steps
- Preview drafts and versions to review the head before publishing
- Build pages with CMS data for
definePage, typed CMS queries, and render props - Configure routes and redirects for URL matching and migrations
- Style a website for global CSS and Tailwind CSS
- Configure SEO metadata to build titles, social previews, and structured data from CMS content
- Customize site-wide files for the custom 404 page and HTML slots
- Roll back and recover when a change needs undoing
- Source code concepts for the full file and version model