Write a site file
Creates the file, or replaces its content. Send the version of the file you read to refuse the write with 409 if the file changed or was removed since, or must_not_exist to create a file only if the path is free; without either the write replaces whatever is there, so the last write wins. The check and the write are one step, so a write that loses a race changes nothing. A page’s file must already exist; add the page first. Writing config/menus.json replaces all of the site’s menus, writing config/pages.json adds, changes and removes pages to match it and sets the site’s default layout (a page added this way starts with an empty file), writing config/settings.json replaces its settings, and writing config/access.json replaces the site’s draft access declaration, the list of the member fields and scopes the site uses. The declaration is a draft until the site is published, and publishing the site publishes it together with the settings. A published declaration grants no access yet. Layouts, sections, snippets and pages are saved as drafts until the site is published. Assets must be css, js, json, txt or html text files.
curl --request PUT \
--url https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"content": "{{ content_for_layout }}"
}
'import requests
url = "https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}"
payload = { "content": "{{ content_for_layout }}" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({content: '{{ content_for_layout }}'})
};
fetch('https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'content' => '{{ content_for_layout }}'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}"
payload := strings.NewReader("{\n \"content\": \"{{ content_for_layout }}\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"content\": \"{{ content_for_layout }}\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"content\": \"{{ content_for_layout }}\"\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"data": {
"path": "layouts/site.liquid",
"content": "{{ content_for_layout }}",
"lock_version": null,
"version": "4c4127c0f28a648e32ccb71b3b6d1c7cf4c8c8cf8d4459c25a14dfab782049de"
},
"meta": {
"request_uuid": "0192f4c8-5b6c-7d74-a3e8-0f1a2b3c4d5e",
"timestamp": "2026-09-30T15:08:21Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}Authorizations
A member credential in the Authorization: Bearer header: a member
session JWT, a portal JWT whose login is linked to a membership in
that company, an opaque member token, or a verified One Auth identity
access token. The credential alone identifies the member and company.
Member session and portal JWTs are read-only and are refused with 403
on every write.
Path Parameters
Public id of one of the member's sites.
The theme-style path of the file, with its slash: layouts/<name>.liquid, sections/<name>.liquid, snippets/<name>.liquid, pages/<slug>.liquid (pages/index.liquid for the home page), assets/<file> (a css, js, json, txt or html file), config/settings.json, config/menus.json, config/pages.json or config/access.json. Names use lowercase letters, digits, hyphens and underscores, with no subfolders. Any other path is refused with 422.
^.+$Body
The content of a file. The path is in the URL.
The whole new content of the file. An asset must not be empty. config/settings.json takes a JSON object, and config/menus.json a JSON document of menus, config/pages.json a JSON document of the site's default layout and page settings, and config/access.json a JSON object whose uses key lists what the site uses, which is refused with 422 when it is not valid.
^[^\u0000]*$The lock_version of config/settings.json the caller last read. The write is refused with 409 if the settings changed since. Ignored for every other file. Send either this or version, not both.
0 <= x <= 2147483647The version of the file the caller last read. The write is refused with 409 if the file has changed or been removed since, and nothing is written. For config/settings.json it is the lock_version as a string, and for config/menus.json, config/pages.json and config/access.json the digest of the document. Without version or must_not_exist, the write replaces whatever is at the path. Cannot be sent with must_not_exist or lock_version.
255^[^\u0000]*$Send true to create the file only if the path holds none. The write is refused with 409 if a file is already there, so a file another writer created since the caller listed the files is not replaced. config/settings.json, config/menus.json and config/pages.json always exist, so it is always refused for them. config/access.json exists only once it is written, so it is refused for it only when the site already has one. Cannot be sent with version.
curl --request PUT \
--url https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"content": "{{ content_for_layout }}"
}
'import requests
url = "https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}"
payload = { "content": "{{ content_for_layout }}" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({content: '{{ content_for_layout }}'})
};
fetch('https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'content' => '{{ content_for_layout }}'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}"
payload := strings.NewReader("{\n \"content\": \"{{ content_for_layout }}\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"content\": \"{{ content_for_layout }}\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/member/v2026-10/sites/{site_id}/files/{path}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"content\": \"{{ content_for_layout }}\"\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"data": {
"path": "layouts/site.liquid",
"content": "{{ content_for_layout }}",
"lock_version": null,
"version": "4c4127c0f28a648e32ccb71b3b6d1c7cf4c8c8cf8d4459c25a14dfab782049de"
},
"meta": {
"request_uuid": "0192f4c8-5b6c-7d74-a3e8-0f1a2b3c4d5e",
"timestamp": "2026-09-30T15:08:21Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"status": 123,
"error": {
"message": "<string>",
"details": {}
},
"meta": {
"request_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timestamp": "2023-11-07T05:31:56Z"
}
}