curl --request POST \
--url https://app.goosybear.ai/api/pages/ingest \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file' \
--form siteId=3c90c3cc-0d44-4b50-8888-8dd25736052a \
--form 'name=<string>'import requests
url = "https://app.goosybear.ai/api/pages/ingest"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"siteId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
form.append('siteId', '3c90c3cc-0d44-4b50-8888-8dd25736052a');
form.append('name', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://app.goosybear.ai/api/pages/ingest', 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://app.goosybear.ai/api/pages/ingest",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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://app.goosybear.ai/api/pages/ingest"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.goosybear.ai/api/pages/ingest")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.goosybear.ai/api/pages/ingest")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"ok": true,
"data": {
"siteId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"siteName": "<string>",
"kind": "bundle",
"objectCount": 123,
"pageCount": 123,
"bytes": 123,
"findings": [
{
"rule": "<string>",
"severity": "<string>",
"message": "<string>",
"path": "<string>"
}
],
"report": {
"status": "<string>",
"kind": "<string>"
},
"deploySource": "declared",
"deploy": {
"entry": "<string>",
"assets": "<string>",
"routes": [
{
"pattern": "<string>"
}
],
"bindings": {
"database": {
"migrations": "<string>",
"tables": [
"<string>"
],
"stagedMigrationsPrefix": "<string>"
},
"projectDatabase": "none",
"secrets": [
"<string>"
],
"submissions": "<string>"
},
"limits": {
"cpuMs": 123,
"subRequests": 123
}
},
"revision": "<string>",
"buildRunId": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}Import or replace a site from an archive
Uploads one ZIP archive and either creates a site or replaces the source of an existing one. The service classifies the archive from its CONTENTS — a prebuilt bundle is staged immediately, a supported source project is queued for the build rail — and validates any pages.deploy.json it carries against the published PagesDeployManifest schema, returning the manifest it resolved on every success. The archive must be no larger than 41943040 bytes.
curl --request POST \
--url https://app.goosybear.ai/api/pages/ingest \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file' \
--form siteId=3c90c3cc-0d44-4b50-8888-8dd25736052a \
--form 'name=<string>'import requests
url = "https://app.goosybear.ai/api/pages/ingest"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"siteId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
form.append('siteId', '3c90c3cc-0d44-4b50-8888-8dd25736052a');
form.append('name', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://app.goosybear.ai/api/pages/ingest', 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://app.goosybear.ai/api/pages/ingest",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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://app.goosybear.ai/api/pages/ingest"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.goosybear.ai/api/pages/ingest")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.goosybear.ai/api/pages/ingest")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"siteId\"\r\n\r\n3c90c3cc-0d44-4b50-8888-8dd25736052a\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"ok": true,
"data": {
"siteId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"siteName": "<string>",
"kind": "bundle",
"objectCount": 123,
"pageCount": 123,
"bytes": 123,
"findings": [
{
"rule": "<string>",
"severity": "<string>",
"message": "<string>",
"path": "<string>"
}
],
"report": {
"status": "<string>",
"kind": "<string>"
},
"deploySource": "declared",
"deploy": {
"entry": "<string>",
"assets": "<string>",
"routes": [
{
"pattern": "<string>"
}
],
"bindings": {
"database": {
"migrations": "<string>",
"tables": [
"<string>"
],
"stagedMigrationsPrefix": "<string>"
},
"projectDatabase": "none",
"secrets": [
"<string>"
],
"submissions": "<string>"
},
"limits": {
"cpuMs": 123,
"subRequests": 123
}
},
"revision": "<string>",
"buildRunId": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"ok": false,
"error": {
"code": "<string>",
"message": "<string>"
}
}Authorizations
An API key minted at Settings › API & MCP. Send it as Authorization: Bearer <key>. A key carries its holder's own permissions, resolved on every call — revoking a membership closes the key's reach immediately. Keep it in an environment variable (GOOSY_API_KEY), never in a committed file.
Query Parameters
Which workspace to act in — its slug or id. The SAME argument every workspace-scoped tool takes, resolved the same way: an account-wide key may select any workspace its holder currently reaches, a workspace-pinned key may only name its own, and a call that names nothing falls through to your default. It is a QUERY parameter rather than a body field on purpose: this endpoint's body is a multipart upload, and reading a field out of it would decode that body before the bearer and the rate limiter had run. With more than one reachable workspace and no default, the call is refused as workspace_ambiguous and the choices are listed. Ignored for a signed-in session, whose workspace comes from its own claims.
1