curl --request POST \
--url https://app.goosybear.ai/api/v1/tools/videos.compose_plan \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"brief_id": "<string>",
"workspace": "<string>"
}
'import requests
url = "https://app.goosybear.ai/api/v1/tools/videos.compose_plan"
payload = {
"brief_id": "<string>",
"workspace": "<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({brief_id: '<string>', workspace: '<string>'})
};
fetch('https://app.goosybear.ai/api/v1/tools/videos.compose_plan', 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/v1/tools/videos.compose_plan",
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([
'brief_id' => '<string>',
'workspace' => '<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://app.goosybear.ai/api/v1/tools/videos.compose_plan"
payload := strings.NewReader("{\n \"brief_id\": \"<string>\",\n \"workspace\": \"<string>\"\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://app.goosybear.ai/api/v1/tools/videos.compose_plan")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"brief_id\": \"<string>\",\n \"workspace\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.goosybear.ai/api/v1/tools/videos.compose_plan")
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 \"brief_id\": \"<string>\",\n \"workspace\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"ready": true,
"title": "<string>",
"readback": "<string>",
"learned": "<string>",
"standing": [
{
"label": "<string>",
"value": "<string>"
}
],
"open_questions": [
"<string>"
],
"workspace": "<string>",
"working_in": {
"label": "<string>",
"note": "<string>",
"workspace": "<string>",
"source": "call-override"
},
"brief_id": "<string>",
"chapters": [
{
"number": 123,
"name": "<string>",
"line": "<string>",
"seconds": 123,
"made_by": "you"
}
],
"clips": [
{
"name": "<string>",
"seconds": 123
}
],
"assumed": [
{
"question": "<string>",
"value": "<string>",
"why": "<string>"
}
],
"length": {
"seconds": 123,
"target_seconds": 123,
"low_seconds": 123,
"high_seconds": 123,
"within_target": true,
"set_by": "script",
"why": "<string>",
"note": "<string>"
},
"style": "<string>",
"ratio": "<string>",
"beats": [
{
"number": 123,
"name": "<string>",
"line": "<string>",
"seconds": 123,
"budget": "<string>",
"made_by": "you"
}
],
"their_words": {
"kind": "script",
"words": 123
}
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}Call videos.compose_plan
Put together the plan for a video or a short from the answers so far — ‘put the plan together’, ‘show me the plan for this short’, ‘just draw it’. Send kind “video” or “short” and the brief’s id. It works on an unfinished brief: any ask to see the plan, a draft of it or where it stands calls it, whatever is still open — never describe the plan from memory, never reply that it cannot be drawn yet, and never take the ask as an answer to the open question (it is not a yes to looking at YouTube). For a video it draws the plan from the answers so far, and a question with a sensible default that is still unanswered (one video or a series, whether to look at YouTube first) takes it, saved on the brief as assumed, and assumed lists each one so the reply can say what was assumed and that the person can change it. Four are only ever the person’s — what the video is, who is on screen and where, the voice, and what it is about: while one is open the plan waits, open_questions names it, and the reply asks it. This makes nothing, spends nothing and runs no YouTube look — it is made when the person says to make it (there is no button for that in the chat), and a short’s film once they press Goosify it on its storyboard.
Every result names the workspace it ran in. Say which workspace the answer is about. When the account has more than one workspace and none is selected, this tool refuses with workspace_ambiguous and lists the choices — offer them, never pick one.
curl --request POST \
--url https://app.goosybear.ai/api/v1/tools/videos.compose_plan \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"brief_id": "<string>",
"workspace": "<string>"
}
'import requests
url = "https://app.goosybear.ai/api/v1/tools/videos.compose_plan"
payload = {
"brief_id": "<string>",
"workspace": "<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({brief_id: '<string>', workspace: '<string>'})
};
fetch('https://app.goosybear.ai/api/v1/tools/videos.compose_plan', 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/v1/tools/videos.compose_plan",
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([
'brief_id' => '<string>',
'workspace' => '<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://app.goosybear.ai/api/v1/tools/videos.compose_plan"
payload := strings.NewReader("{\n \"brief_id\": \"<string>\",\n \"workspace\": \"<string>\"\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://app.goosybear.ai/api/v1/tools/videos.compose_plan")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"brief_id\": \"<string>\",\n \"workspace\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.goosybear.ai/api/v1/tools/videos.compose_plan")
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 \"brief_id\": \"<string>\",\n \"workspace\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"ready": true,
"title": "<string>",
"readback": "<string>",
"learned": "<string>",
"standing": [
{
"label": "<string>",
"value": "<string>"
}
],
"open_questions": [
"<string>"
],
"workspace": "<string>",
"working_in": {
"label": "<string>",
"note": "<string>",
"workspace": "<string>",
"source": "call-override"
},
"brief_id": "<string>",
"chapters": [
{
"number": 123,
"name": "<string>",
"line": "<string>",
"seconds": 123,
"made_by": "you"
}
],
"clips": [
{
"name": "<string>",
"seconds": 123
}
],
"assumed": [
{
"question": "<string>",
"value": "<string>",
"why": "<string>"
}
],
"length": {
"seconds": 123,
"target_seconds": 123,
"low_seconds": 123,
"high_seconds": 123,
"within_target": true,
"set_by": "script",
"why": "<string>",
"note": "<string>"
},
"style": "<string>",
"ratio": "<string>",
"beats": [
{
"number": 123,
"name": "<string>",
"line": "<string>",
"seconds": 123,
"budget": "<string>",
"made_by": "you"
}
],
"their_words": {
"kind": "script",
"words": 123
}
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}{
"ok": false,
"code": "<string>",
"message": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"retry_after_seconds": 123
},
"retry_after_seconds": 123
}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.
Headers
Body
With kind "video": The video brief's id. If this conversation has named one, pass it — a named id always wins. If none has been named and the person is on a video's plan and means that plan ('make it two minutes', 'write it'), LEAVE IT OUT: the tool acts on the plan on screen. If none has been named and no plan is open, do not call this — ask which video they mean. Never invent an id. With kind "short": The short's brief id. If this conversation has named one, pass it — a named id always wins. If none has been named and the person is on a short's plan and means that plan ('show me the plan', 'make it'), LEAVE IT OUT: the tool acts on the plan on screen. If none has been named and no plan is open, do not call this — ask which short they mean. Never invent an id.
Response
The call was admitted and dispatched. ok says whether the tool succeeded — a refusal the tool itself produced is still a 200, exactly as it is a successful JSON-RPC result over MCP.
- Option 1
- Option 2
The tool ran and answered.