curl --request POST \
--url https://app-api.costory.io/reports \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Weekly cloud cost digest",
"description": "Top movers across our AWS + GCP spend",
"type": "PRIVATE",
"context": {
"metricId": "cost",
"groupBy": "provider",
"datePreset": "TRAILING_90_DAYS",
"currency": "USD"
},
"scheduledPeriod": "WEEKLY",
"scheduledWeekday": 1,
"status": "ACTIVE",
"widgets": [
{
"type": "TEXT",
"contentMarkdown": "## Weekly summary"
}
],
"destinations": [
{
"destinationType": "SLACK",
"destinationValues": [
"C0123456789"
]
},
{
"destinationType": "EMAIL",
"destinationValues": [
"finance@acme.com"
]
}
]
}
'import requests
url = "https://app-api.costory.io/reports"
payload = {
"name": "Weekly cloud cost digest",
"description": "Top movers across our AWS + GCP spend",
"type": "PRIVATE",
"context": {
"metricId": "cost",
"groupBy": "provider",
"datePreset": "TRAILING_90_DAYS",
"currency": "USD"
},
"scheduledPeriod": "WEEKLY",
"scheduledWeekday": 1,
"status": "ACTIVE",
"widgets": [
{
"type": "TEXT",
"contentMarkdown": "## Weekly summary"
}
],
"destinations": [
{
"destinationType": "SLACK",
"destinationValues": ["C0123456789"]
},
{
"destinationType": "EMAIL",
"destinationValues": ["finance@acme.com"]
}
]
}
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({
name: 'Weekly cloud cost digest',
description: 'Top movers across our AWS + GCP spend',
type: 'PRIVATE',
context: {
metricId: 'cost',
groupBy: 'provider',
datePreset: 'TRAILING_90_DAYS',
currency: 'USD'
},
scheduledPeriod: 'WEEKLY',
scheduledWeekday: 1,
status: 'ACTIVE',
widgets: [{type: 'TEXT', contentMarkdown: '## Weekly summary'}],
destinations: [
{destinationType: 'SLACK', destinationValues: ['C0123456789']},
{destinationType: 'EMAIL', destinationValues: ['finance@acme.com']}
]
})
};
fetch('https://app-api.costory.io/reports', 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-api.costory.io/reports",
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([
'name' => 'Weekly cloud cost digest',
'description' => 'Top movers across our AWS + GCP spend',
'type' => 'PRIVATE',
'context' => [
'metricId' => 'cost',
'groupBy' => 'provider',
'datePreset' => 'TRAILING_90_DAYS',
'currency' => 'USD'
],
'scheduledPeriod' => 'WEEKLY',
'scheduledWeekday' => 1,
'status' => 'ACTIVE',
'widgets' => [
[
'type' => 'TEXT',
'contentMarkdown' => '## Weekly summary'
]
],
'destinations' => [
[
'destinationType' => 'SLACK',
'destinationValues' => [
'C0123456789'
]
],
[
'destinationType' => 'EMAIL',
'destinationValues' => [
'finance@acme.com'
]
]
]
]),
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-api.costory.io/reports"
payload := strings.NewReader("{\n \"name\": \"Weekly cloud cost digest\",\n \"description\": \"Top movers across our AWS + GCP spend\",\n \"type\": \"PRIVATE\",\n \"context\": {\n \"metricId\": \"cost\",\n \"groupBy\": \"provider\",\n \"datePreset\": \"TRAILING_90_DAYS\",\n \"currency\": \"USD\"\n },\n \"scheduledPeriod\": \"WEEKLY\",\n \"scheduledWeekday\": 1,\n \"status\": \"ACTIVE\",\n \"widgets\": [\n {\n \"type\": \"TEXT\",\n \"contentMarkdown\": \"## Weekly summary\"\n }\n ],\n \"destinations\": [\n {\n \"destinationType\": \"SLACK\",\n \"destinationValues\": [\n \"C0123456789\"\n ]\n },\n {\n \"destinationType\": \"EMAIL\",\n \"destinationValues\": [\n \"finance@acme.com\"\n ]\n }\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://app-api.costory.io/reports")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Weekly cloud cost digest\",\n \"description\": \"Top movers across our AWS + GCP spend\",\n \"type\": \"PRIVATE\",\n \"context\": {\n \"metricId\": \"cost\",\n \"groupBy\": \"provider\",\n \"datePreset\": \"TRAILING_90_DAYS\",\n \"currency\": \"USD\"\n },\n \"scheduledPeriod\": \"WEEKLY\",\n \"scheduledWeekday\": 1,\n \"status\": \"ACTIVE\",\n \"widgets\": [\n {\n \"type\": \"TEXT\",\n \"contentMarkdown\": \"## Weekly summary\"\n }\n ],\n \"destinations\": [\n {\n \"destinationType\": \"SLACK\",\n \"destinationValues\": [\n \"C0123456789\"\n ]\n },\n {\n \"destinationType\": \"EMAIL\",\n \"destinationValues\": [\n \"finance@acme.com\"\n ]\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app-api.costory.io/reports")
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 \"name\": \"Weekly cloud cost digest\",\n \"description\": \"Top movers across our AWS + GCP spend\",\n \"type\": \"PRIVATE\",\n \"context\": {\n \"metricId\": \"cost\",\n \"groupBy\": \"provider\",\n \"datePreset\": \"TRAILING_90_DAYS\",\n \"currency\": \"USD\"\n },\n \"scheduledPeriod\": \"WEEKLY\",\n \"scheduledWeekday\": 1,\n \"status\": \"ACTIVE\",\n \"widgets\": [\n {\n \"type\": \"TEXT\",\n \"contentMarkdown\": \"## Weekly summary\"\n }\n ],\n \"destinations\": [\n {\n \"destinationType\": \"SLACK\",\n \"destinationValues\": [\n \"C0123456789\"\n ]\n },\n {\n \"destinationType\": \"EMAIL\",\n \"destinationValues\": [\n \"finance@acme.com\"\n ]\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"reportId": "<string>"
}{
"error": "VALIDATION_ERROR",
"details": "<string>",
"reason": "<string>"
}{
"error": "<string>"
}{
"error": "TEAM_MEMBER_REQUIRED"
}Create a report
Creates a report. Rules not captured by the field types: at least one destinations entry is required; when scheduledPeriod is WEEKLY, scheduledWeekday (0=Sunday…6=Saturday) is required; EMAIL destinations must be a valid address (or ALL_ACTIVE_USERS); and a DIGEST widget rendered as a table supports at most two group-by dimensions (report context.groupBy plus additionalGroupBy).
curl --request POST \
--url https://app-api.costory.io/reports \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Weekly cloud cost digest",
"description": "Top movers across our AWS + GCP spend",
"type": "PRIVATE",
"context": {
"metricId": "cost",
"groupBy": "provider",
"datePreset": "TRAILING_90_DAYS",
"currency": "USD"
},
"scheduledPeriod": "WEEKLY",
"scheduledWeekday": 1,
"status": "ACTIVE",
"widgets": [
{
"type": "TEXT",
"contentMarkdown": "## Weekly summary"
}
],
"destinations": [
{
"destinationType": "SLACK",
"destinationValues": [
"C0123456789"
]
},
{
"destinationType": "EMAIL",
"destinationValues": [
"finance@acme.com"
]
}
]
}
'import requests
url = "https://app-api.costory.io/reports"
payload = {
"name": "Weekly cloud cost digest",
"description": "Top movers across our AWS + GCP spend",
"type": "PRIVATE",
"context": {
"metricId": "cost",
"groupBy": "provider",
"datePreset": "TRAILING_90_DAYS",
"currency": "USD"
},
"scheduledPeriod": "WEEKLY",
"scheduledWeekday": 1,
"status": "ACTIVE",
"widgets": [
{
"type": "TEXT",
"contentMarkdown": "## Weekly summary"
}
],
"destinations": [
{
"destinationType": "SLACK",
"destinationValues": ["C0123456789"]
},
{
"destinationType": "EMAIL",
"destinationValues": ["finance@acme.com"]
}
]
}
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({
name: 'Weekly cloud cost digest',
description: 'Top movers across our AWS + GCP spend',
type: 'PRIVATE',
context: {
metricId: 'cost',
groupBy: 'provider',
datePreset: 'TRAILING_90_DAYS',
currency: 'USD'
},
scheduledPeriod: 'WEEKLY',
scheduledWeekday: 1,
status: 'ACTIVE',
widgets: [{type: 'TEXT', contentMarkdown: '## Weekly summary'}],
destinations: [
{destinationType: 'SLACK', destinationValues: ['C0123456789']},
{destinationType: 'EMAIL', destinationValues: ['finance@acme.com']}
]
})
};
fetch('https://app-api.costory.io/reports', 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-api.costory.io/reports",
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([
'name' => 'Weekly cloud cost digest',
'description' => 'Top movers across our AWS + GCP spend',
'type' => 'PRIVATE',
'context' => [
'metricId' => 'cost',
'groupBy' => 'provider',
'datePreset' => 'TRAILING_90_DAYS',
'currency' => 'USD'
],
'scheduledPeriod' => 'WEEKLY',
'scheduledWeekday' => 1,
'status' => 'ACTIVE',
'widgets' => [
[
'type' => 'TEXT',
'contentMarkdown' => '## Weekly summary'
]
],
'destinations' => [
[
'destinationType' => 'SLACK',
'destinationValues' => [
'C0123456789'
]
],
[
'destinationType' => 'EMAIL',
'destinationValues' => [
'finance@acme.com'
]
]
]
]),
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-api.costory.io/reports"
payload := strings.NewReader("{\n \"name\": \"Weekly cloud cost digest\",\n \"description\": \"Top movers across our AWS + GCP spend\",\n \"type\": \"PRIVATE\",\n \"context\": {\n \"metricId\": \"cost\",\n \"groupBy\": \"provider\",\n \"datePreset\": \"TRAILING_90_DAYS\",\n \"currency\": \"USD\"\n },\n \"scheduledPeriod\": \"WEEKLY\",\n \"scheduledWeekday\": 1,\n \"status\": \"ACTIVE\",\n \"widgets\": [\n {\n \"type\": \"TEXT\",\n \"contentMarkdown\": \"## Weekly summary\"\n }\n ],\n \"destinations\": [\n {\n \"destinationType\": \"SLACK\",\n \"destinationValues\": [\n \"C0123456789\"\n ]\n },\n {\n \"destinationType\": \"EMAIL\",\n \"destinationValues\": [\n \"finance@acme.com\"\n ]\n }\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://app-api.costory.io/reports")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Weekly cloud cost digest\",\n \"description\": \"Top movers across our AWS + GCP spend\",\n \"type\": \"PRIVATE\",\n \"context\": {\n \"metricId\": \"cost\",\n \"groupBy\": \"provider\",\n \"datePreset\": \"TRAILING_90_DAYS\",\n \"currency\": \"USD\"\n },\n \"scheduledPeriod\": \"WEEKLY\",\n \"scheduledWeekday\": 1,\n \"status\": \"ACTIVE\",\n \"widgets\": [\n {\n \"type\": \"TEXT\",\n \"contentMarkdown\": \"## Weekly summary\"\n }\n ],\n \"destinations\": [\n {\n \"destinationType\": \"SLACK\",\n \"destinationValues\": [\n \"C0123456789\"\n ]\n },\n {\n \"destinationType\": \"EMAIL\",\n \"destinationValues\": [\n \"finance@acme.com\"\n ]\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app-api.costory.io/reports")
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 \"name\": \"Weekly cloud cost digest\",\n \"description\": \"Top movers across our AWS + GCP spend\",\n \"type\": \"PRIVATE\",\n \"context\": {\n \"metricId\": \"cost\",\n \"groupBy\": \"provider\",\n \"datePreset\": \"TRAILING_90_DAYS\",\n \"currency\": \"USD\"\n },\n \"scheduledPeriod\": \"WEEKLY\",\n \"scheduledWeekday\": 1,\n \"status\": \"ACTIVE\",\n \"widgets\": [\n {\n \"type\": \"TEXT\",\n \"contentMarkdown\": \"## Weekly summary\"\n }\n ],\n \"destinations\": [\n {\n \"destinationType\": \"SLACK\",\n \"destinationValues\": [\n \"C0123456789\"\n ]\n },\n {\n \"destinationType\": \"EMAIL\",\n \"destinationValues\": [\n \"finance@acme.com\"\n ]\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"reportId": "<string>"
}{
"error": "VALIDATION_ERROR",
"details": "<string>",
"reason": "<string>"
}{
"error": "<string>"
}{
"error": "TEAM_MEMBER_REQUIRED"
}Authorizations
Send Authorization: Bearer <credential> using either a Clerk session JWT or a Clerk API key (ak_*). API keys may identify a user (user_*) or organization (org_*) principal and are generated in the Clerk Dashboard under API keys.
Body
Report name. May be empty or whitespace — the server then generates one from the report's content.
Delivery cadence, or null for an on-demand (one-time) report that only runs when triggered.
DAILY, WEEKDAYS, WEEKLY, MONTHLY, null Delivery targets. At least one is required. Each report is fanned out to every destination on every run.
1Show child attributes
Show child attributes
Optional longer description (default empty).
Visibility: PRIVATE (default), PUBLIC, or TEMPLATE. Team sharing is via teamId — there is no TEAM value.
PRIVATE, PUBLIC, TEMPLATE Optional owning team id. Independent of type/visibility; omit or null for no team.
Report-wide defaults (metric, group-by, date range, filters, currency) that widgets and destinations inherit unless they override them.
Show child attributes
Show child attributes
Day of week for WEEKLY reports: 0 = Sunday … 6 = Saturday. Required when scheduledPeriod is WEEKLY; ignored for every other cadence.
First send time (UTC, truncated to the hour). Ignored once the report has run at least once.
ACTIVE, INACTIVE Ordered widgets that make up the report body. Each widget's type selects its shape (see ReportWidgetParameters).
One report widget. The type field selects the shape: TOP_FLOP | GRAPH_SNAPSHOT | DASHBOARD_PDF | DIGEST | TEXT. All fields other than type depend on the chosen type.
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
Show child attributes
Show child attributes
Response
Report created
Was this page helpful?
