Promote a site's branch
Makes the branch’s published version the site’s live one. The branch’s published bundle records the source commit it was built from, and the site’s live bundle the commit it was built from. The promote goes ahead only when the branch’s commit contains the live commit and the site repository’s default branch: it moves the default branch to the branch’s commit (never forced), then publishes the branch’s bundle on the site, holding it to every rule of a bundle publish. So a promote never replaces a live version the branch does not have. When the site already serves the branch’s version, nothing changes and replaced_bundle_id is null. Undo a promote by making replaced_bundle_id current again (POST /member-sites/{id}/bundles/{bundle_id}/current). When the branch’s schema holds destructive migrations, the first promote is refused with 422 naming them; promote again with confirm_migrations once the admin said yes.
Needs application_themes.update and a signed-in admin who may edit the branch’s drafts. It answers 404 for every caller that passes those checks until staging and the sites gateway are released to the company. A droplet installation token and an OAuth client token are refused with 403.
curl --request POST \
--url https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"confirm_migrations": [
"2026-11-05-drop-notes"
],
"bundle_id": "019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a"
}
'import requests
url = "https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote"
payload = {
"confirm_migrations": ["2026-11-05-drop-notes"],
"bundle_id": "019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
confirm_migrations: ['2026-11-05-drop-notes'],
bundle_id: '019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a'
})
};
fetch('https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote', 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/company/v2026-10/member-sites/{id}/branches/{slug}/promote",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'confirm_migrations' => [
'2026-11-05-drop-notes'
],
'bundle_id' => '019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a'
]),
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/company/v2026-10/member-sites/{id}/branches/{slug}/promote"
payload := strings.NewReader("{\n \"confirm_migrations\": [\n \"2026-11-05-drop-notes\"\n ],\n \"bundle_id\": \"019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a\"\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"confirm_migrations\": [\n \"2026-11-05-drop-notes\"\n ],\n \"bundle_id\": \"019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"confirm_migrations\": [\n \"2026-11-05-drop-notes\"\n ],\n \"bundle_id\": \"019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a\"\n}"
response = http.request(request)
puts response.read_body{
"promotion": {
"bundle_id": "019a4e5f-8081-7c92-8da3-b4c5d6e7f80a",
"replaced_bundle_id": "019a4e5f-7a81-7c92-8da3-b4c5d6e7f80a",
"commit": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b"
},
"status": 200,
"meta": {
"request_uuid": "3b8f1d27-6c4e-4a91-8e5d-2f7c9a1b0e64",
"timestamp": "2026-11-13T10:00:00Z"
}
}{}{}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}Authorizations
Path Parameters
Body
The optional confirmation of a promote, sent unwrapped. Send confirm_migrations, from a 422 destructive_change_unconfirmed refusal, after the admin said yes, with the refusal's bundle_id when you have it; omit both otherwise. Any other field is refused.
curl --request POST \
--url https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"confirm_migrations": [
"2026-11-05-drop-notes"
],
"bundle_id": "019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a"
}
'import requests
url = "https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote"
payload = {
"confirm_migrations": ["2026-11-05-drop-notes"],
"bundle_id": "019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
confirm_migrations: ['2026-11-05-drop-notes'],
bundle_id: '019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a'
})
};
fetch('https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote', 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/company/v2026-10/member-sites/{id}/branches/{slug}/promote",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'confirm_migrations' => [
'2026-11-05-drop-notes'
],
'bundle_id' => '019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a'
]),
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/company/v2026-10/member-sites/{id}/branches/{slug}/promote"
payload := strings.NewReader("{\n \"confirm_migrations\": [\n \"2026-11-05-drop-notes\"\n ],\n \"bundle_id\": \"019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a\"\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"confirm_migrations\": [\n \"2026-11-05-drop-notes\"\n ],\n \"bundle_id\": \"019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/company/v2026-10/member-sites/{id}/branches/{slug}/promote")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"confirm_migrations\": [\n \"2026-11-05-drop-notes\"\n ],\n \"bundle_id\": \"019a4e5f-7f81-7c92-8da3-b4c5d6e7f80a\"\n}"
response = http.request(request)
puts response.read_body{
"promotion": {
"bundle_id": "019a4e5f-8081-7c92-8da3-b4c5d6e7f80a",
"replaced_bundle_id": "019a4e5f-7a81-7c92-8da3-b4c5d6e7f80a",
"commit": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b"
},
"status": 200,
"meta": {
"request_uuid": "3b8f1d27-6c4e-4a91-8e5d-2f7c9a1b0e64",
"timestamp": "2026-11-13T10:00:00Z"
}
}{}{}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}