curl --request POST \
--url https://findtempo.co/api/v1/canvas/generate \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"canvas_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"subject": {
"mode": "anchor",
"runAnchor": {
"type": "deal",
"id": "<string>"
},
"expectedDealId": "<string>"
},
"deal_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"filter": {},
"template_options": {},
"period": {},
"as_of": "<string>"
}
'import requests
url = "https://findtempo.co/api/v1/canvas/generate"
payload = {
"canvas_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"subject": {
"mode": "anchor",
"runAnchor": {
"type": "deal",
"id": "<string>"
},
"expectedDealId": "<string>"
},
"deal_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"filter": {},
"template_options": {},
"period": {},
"as_of": "<string>"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
canvas_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
subject: {
mode: 'anchor',
runAnchor: {type: 'deal', id: '<string>'},
expectedDealId: '<string>'
},
deal_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
filter: {},
template_options: {},
period: {},
as_of: '<string>'
})
};
fetch('https://findtempo.co/api/v1/canvas/generate', 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://findtempo.co/api/v1/canvas/generate",
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([
'canvas_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'subject' => [
'mode' => 'anchor',
'runAnchor' => [
'type' => 'deal',
'id' => '<string>'
],
'expectedDealId' => '<string>'
],
'deal_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'filter' => [
],
'template_options' => [
],
'period' => [
],
'as_of' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"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://findtempo.co/api/v1/canvas/generate"
payload := strings.NewReader("{\n \"canvas_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"subject\": {\n \"mode\": \"anchor\",\n \"runAnchor\": {\n \"type\": \"deal\",\n \"id\": \"<string>\"\n },\n \"expectedDealId\": \"<string>\"\n },\n \"deal_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"filter\": {},\n \"template_options\": {},\n \"period\": {},\n \"as_of\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
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://findtempo.co/api/v1/canvas/generate")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"canvas_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"subject\": {\n \"mode\": \"anchor\",\n \"runAnchor\": {\n \"type\": \"deal\",\n \"id\": \"<string>\"\n },\n \"expectedDealId\": \"<string>\"\n },\n \"deal_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"filter\": {},\n \"template_options\": {},\n \"period\": {},\n \"as_of\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://findtempo.co/api/v1/canvas/generate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"canvas_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"subject\": {\n \"mode\": \"anchor\",\n \"runAnchor\": {\n \"type\": \"deal\",\n \"id\": \"<string>\"\n },\n \"expectedDealId\": \"<string>\"\n },\n \"deal_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"filter\": {},\n \"template_options\": {},\n \"period\": {},\n \"as_of\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"run_id": "<string>",
"status": "pending",
"result": {},
"error": "<string>",
"created_at": "<string>",
"completed_at": "<string>",
"warnings": [
"<string>"
]
}Generate a canvas document — async job, poll by run_id
Trigger a canvas generation job against a deal universe: an anchored canonical subject, a GIVEN set, OR a structured filter (the same automation FilterExpression). A generate call always produces exactly one document over the resolved deal set — a caller wanting one document per deal makes N independent calls and owns batching and partial failures.
Integrator notes:
- Always async. The job runs in the background and this returns
202withrun_id— pollGET /api/v1/canvas/generate/{run_id}for the result. - Actor scoping. A bare API key is an org principal: the deal universe is your organization’s full book, matching what the query surface serves the same key. Pass
X-Acting-User: <user_id_or_email>to run the request as a specific member instead — the deal universe then narrows to the deals that member can see (their own book), and the value must name a real member of your org, or the request is rejected (403). - Private templates. A canvas published with
privatevisibility is generable only by its owner: name the owner inX-Acting-User, or have the owner set the canvas’s visibility to public (org-wide) in Tempo. Any other acting user gets a403naming the fix. - Selector. Pass exactly one of
subject,deal_ids, orfilter.subjectcarries the canonical selector shape and can anchor generation to one deal or meeting; it can also carry canonical deal IDs or a structured filter.deal_idsandfilterare the backward-compatible top-level forms. - Chip recognition. Before the job is enqueued, every variable chip the canvas template declares is classified against the canvas chip catalog — the same catalog the canvas editor’s chip picker offers: identity values (
current_date,current_datetime,user_name,seller_profile), organization playbook rails (company_context,house_rules, …), period chips (resolve when the request carries aperiod, otherwise render unresolved), deal-field chips from your CRM field catalog (plus friendly aliases likedeal_name/stage), aggregate rollups (unconditional), the bound tool’s contract chips, and yourtemplate_optionskeys. A chip the catalog does not recognize never blocks the run: it renders as “unknown” (prose) or its humanized name (generate instructions), is named in the trigger response’swarnings, and is reported per document inresult.documents[].unrecognized. Tool-contract chips (e.g. the meeting presets’meeting_title) are recognized vocabulary whose values you can supply viatemplate_options. - Warnings. Unrecognized chips and
template_optionskeys the template neither declares nor consumes through a declared chip are surfaced in the trigger response’swarningsarray — check it to catch chip and key typos. The draft-message preset consumesrecipient_name/recipient_rolewhen itsrecipient_contextchip is declared, so those feeder keys do not warn in that case.
curl --request POST \
--url https://findtempo.co/api/v1/canvas/generate \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"canvas_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"subject": {
"mode": "anchor",
"runAnchor": {
"type": "deal",
"id": "<string>"
},
"expectedDealId": "<string>"
},
"deal_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"filter": {},
"template_options": {},
"period": {},
"as_of": "<string>"
}
'import requests
url = "https://findtempo.co/api/v1/canvas/generate"
payload = {
"canvas_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"subject": {
"mode": "anchor",
"runAnchor": {
"type": "deal",
"id": "<string>"
},
"expectedDealId": "<string>"
},
"deal_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"filter": {},
"template_options": {},
"period": {},
"as_of": "<string>"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
canvas_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
subject: {
mode: 'anchor',
runAnchor: {type: 'deal', id: '<string>'},
expectedDealId: '<string>'
},
deal_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
filter: {},
template_options: {},
period: {},
as_of: '<string>'
})
};
fetch('https://findtempo.co/api/v1/canvas/generate', 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://findtempo.co/api/v1/canvas/generate",
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([
'canvas_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'subject' => [
'mode' => 'anchor',
'runAnchor' => [
'type' => 'deal',
'id' => '<string>'
],
'expectedDealId' => '<string>'
],
'deal_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'filter' => [
],
'template_options' => [
],
'period' => [
],
'as_of' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"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://findtempo.co/api/v1/canvas/generate"
payload := strings.NewReader("{\n \"canvas_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"subject\": {\n \"mode\": \"anchor\",\n \"runAnchor\": {\n \"type\": \"deal\",\n \"id\": \"<string>\"\n },\n \"expectedDealId\": \"<string>\"\n },\n \"deal_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"filter\": {},\n \"template_options\": {},\n \"period\": {},\n \"as_of\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
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://findtempo.co/api/v1/canvas/generate")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"canvas_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"subject\": {\n \"mode\": \"anchor\",\n \"runAnchor\": {\n \"type\": \"deal\",\n \"id\": \"<string>\"\n },\n \"expectedDealId\": \"<string>\"\n },\n \"deal_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"filter\": {},\n \"template_options\": {},\n \"period\": {},\n \"as_of\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://findtempo.co/api/v1/canvas/generate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"canvas_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"subject\": {\n \"mode\": \"anchor\",\n \"runAnchor\": {\n \"type\": \"deal\",\n \"id\": \"<string>\"\n },\n \"expectedDealId\": \"<string>\"\n },\n \"deal_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"filter\": {},\n \"template_options\": {},\n \"period\": {},\n \"as_of\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"run_id": "<string>",
"status": "pending",
"result": {},
"error": "<string>",
"created_at": "<string>",
"completed_at": "<string>",
"warnings": [
"<string>"
]
}Authorizations
API key passed as Bearer token (e.g. "Bearer your-api-key")
Headers
Scope the request to a specific member of your organization (user id or email). Defaults to the API key's default member.
Body
- Option 1
- Option 2
- Option 3
Exactly one of subject, deal_ids, or filter is required.
The published canvas to generate from.
Canonical generation selector. Provide this OR top-level deal_ids OR top-level filter.
- Option 1
- Option 2
- Option 3
Show child attributes
Show child attributes
An explicit deal set (provide this OR subject OR filter).
1A structured automation FilterExpression selecting the deal universe (provide this OR subject OR deal_ids).
Author/runtime chip values not derivable from the deals (e.g. period_label, target_amount, user_prompt). The draft-message preset reads recipient_name/recipient_role from here for its recipient_context chip. Keys the template neither declares nor consumes through a declared chip are accepted but echoed back as warnings.
Show child attributes
Show child attributes
Reporting-period spec; resolved to a concrete window for the change/movement lens.
Point-in-time anchor (YYYY-MM-DD).
^\d{4}-\d{2}-\d{2}$Response
The job was enqueued; poll GET by run_id for the result.
pending, running, completed, failed Present once status is completed.
Present once status is failed.
Trigger responses only: non-fatal request problems (unrecognized chips, template_options keys the template does not declare).

