curl --request POST \
--url https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"paths": [
"<string>"
]
}
'import requests
url = "https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish"
payload = { "paths": ["<string>"] }
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({paths: ['<string>']})
};
fetch('https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish', 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.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish",
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([
'paths' => [
'<string>'
]
]),
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.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish"
payload := strings.NewReader("{\n \"paths\": [\n \"<string>\"\n ]\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.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"paths\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish")
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 \"paths\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"results": [
{
"path": "/pricing",
"outcome": "queued",
"retry_after": null,
"in_sitemap": true,
"warnings": []
},
{
"path": "/launch/fall-event",
"outcome": "queued",
"retry_after": null,
"in_sitemap": false,
"warnings": [
"Retired (http_gone), last checked 2026-10-20: it serves only once the origin answers 200.",
"Not in the site's sitemap. Published as a manual Agent Page: it is not removed by the sitemap check, only by an origin 404 or redirect, or the unpublish API."
]
},
{
"path": "/about",
"outcome": "already_published",
"retry_after": null,
"in_sitemap": true,
"warnings": []
},
{
"path": "/brochure.pdf",
"outcome": "invalid_path",
"retry_after": null,
"in_sitemap": null,
"warnings": []
}
],
"quota": {
"hourly": {
"limit": 500,
"remaining": 498,
"resets_at": "2026-10-28T15:02:41Z"
},
"daily": {
"limit": 5000,
"remaining": 4987,
"resets_at": "2026-10-29T09:15:03Z"
}
}
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Publish
Make pages Agent Pages now, including pages your sitemap doesn’t list. Each path is fetched from your live site, optimized and served once that finishes, usually within minutes. On a page-by-page setup each page is selected for Agent Pages and counts against your Agent Pages budget. On a page-by-page setup, the site serves only its Agent Pages set. With Site Optimization, a site with an empty set serves no Agent Pages; without it, a site serves every page until its first page is marked or published, so the first publish stops serving every page outside the set.
Returns one outcome per requested path. queued means the fetch was sent, not that the page is live: if your site doesn’t serve the page (for example it answers 404), it stays selected but isn’t served until a scheduled check finds it live (about every 30 days) or you send it again. If the send fails, the hourly scheduled check picks the page up, usually within an hour. warnings says when a queued page waits on your site: it was retired earlier, or recent checks saw it redirect.
A path the Agent Pages Unpublish API put on the Ignore List is published again; a dashboard Ignore List entry or path rule is never overridden (excluded). On a whole-site (legacy) setup the page is optimized without being added to any list. A path your CDN doesn’t route to the Scrunch edge worker is stored but never served.
Limits: 100 paths per request; each path once every 5 minutes; per brand, 500 published paths per rolling hour and 5,000 per rolling 24 hours. These quotas are separate from Force Refresh’s and Unpublish’s.
Publish acts on the Agent Page only. Curated (Adaptive) content at the same path keeps being served, and the response doesn’t report it.
curl --request POST \
--url https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"paths": [
"<string>"
]
}
'import requests
url = "https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish"
payload = { "paths": ["<string>"] }
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({paths: ['<string>']})
};
fetch('https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish', 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.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish",
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([
'paths' => [
'<string>'
]
]),
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.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish"
payload := strings.NewReader("{\n \"paths\": [\n \"<string>\"\n ]\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.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"paths\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scrunchai.com/v1/{brand_id}/sites/{site_id}/agent-pages/publish")
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 \"paths\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"results": [
{
"path": "/pricing",
"outcome": "queued",
"retry_after": null,
"in_sitemap": true,
"warnings": []
},
{
"path": "/launch/fall-event",
"outcome": "queued",
"retry_after": null,
"in_sitemap": false,
"warnings": [
"Retired (http_gone), last checked 2026-10-20: it serves only once the origin answers 200.",
"Not in the site's sitemap. Published as a manual Agent Page: it is not removed by the sitemap check, only by an origin 404 or redirect, or the unpublish API."
]
},
{
"path": "/about",
"outcome": "already_published",
"retry_after": null,
"in_sitemap": true,
"warnings": []
},
{
"path": "/brochure.pdf",
"outcome": "invalid_path",
"retry_after": null,
"in_sitemap": null,
"warnings": []
}
],
"quota": {
"hourly": {
"limit": 500,
"remaining": 498,
"resets_at": "2026-10-28T15:02:41Z"
},
"daily": {
"limit": 5000,
"remaining": 4987,
"resets_at": "2026-10-29T09:15:03Z"
}
}
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path Parameters
The unique identifier for the brand that owns the site.
The ULID of the registered site whose Agent Pages you are publishing.
Body
Site-relative paths to publish, e.g. /pricing. 1 to 100 entries, each up to 2,048 characters. Full or protocol-relative URLs are rejected. Paths are trimmed and normalized (lowercased, trailing slash and query string dropped), and duplicates after normalization are merged. / is the homepage. The page is fetched with the first spelling sent, so a case-sensitive site gets the URL it expects.
1 - 100 elementsResponse
One outcome per requested path, in request order, plus the brand's remaining publish quota.