Link to Swagger docs: https://app.getrecast.com/api-docs/index.html (must be logged into to app.getrecast.com prior to visiting).
Recommended Workflow
The Optimizer is a complicated tool with many possible paths and inputs that are all packaged in the form argument. Instead of trying to construct arguments using documentation, we strongly suggest as a starting point making an Optimization like the one you want to run in the UI first. Once thatโs created you can call the optimizations/[id] GET endpoint to retrieve the filled out form. You can then simply make targeted edits to the form to make changes, which helps ensure the rest of the settings are as you would expect if using the UI.
The endpoints
-
GET /optimizations/[id] - Returns an existing Optimization with status, metadata, and available downloads
-
POST /optimizations - Takes a form to create an Optimization
-
Takes
nameto name it -
Takes
show_in_uito determine whether to show it in the app or not -
Takes
use_latest_deployments(default: true). If true, will use the latest deployments currently deployed to the Recast app. If false, will use whatever deployments are specified in thedepvar_configurationspart of the form.
-
-
GET /optimizations - Returns a paginated list of all Optimizations
-
GET /optimizations/[id]/downloads/[key] - Returns individual CSV downloads
Additionally, there are two KPI endpoints that may be helpful when formatting your requests:
-
GET /kpis - returns all KPIs you have configured in your dash
-
GET /kpis/[kpi-id] returns the current depvar_configurations with the latest deployments and spikes for a given KPI
See Swagger for more information
The Form
The form contains all the settings used to identify an Optimization. Anything that is possible to do in the Optimizer UI is possible through tweaking a setting on the form.
Depvar_configurations
The depvar_configurations portion of the form does three important things:
-
Determines the weights to apply to each model (especially helpful when doing weighted multi-model optimizations)
-
Determines the spikes to use in each model
-
Determines the deployments to use with each model
Importantly, the deployments specified are ignored if use_latest_deployments is true. In that case, the deployments used will always match whatโs visible on your Recast dashboard.
You can fetch the latest spikes/weight/deployments for the KPIs you have configured in the app by calling the /kpi endpoints detailed above.
Terminology used in the form
Instead of providing line by line details for every possible setting in the form, we will instead provide a glossary that will make it easy to find and tweak settings you care about from an existing Optimization run through the UI.
-
Target - the numeric goal youโre trying to reach. If using a profit objective, this doubles as the margin field.
-
Objective - the type of goal youโre trying to reach
-
Confidence - The percent of simulations that you want to reach your goal
-
Optimize for in sample effect - Whether to optimize for the shifted or unshifted impact. This defaults to unshifted (FALSE), and that is generally recommended as optimizing for shifted impact (a) takes much longer, and (b) will bias your Optimization to spending money early in the optimized period so that all the effect is realized within the Optimization window.
-
Multiplier - the weight applied to the dependent variable in the Optimization. For example, if trying to optimize across a revenue and conversions model, the revenue model may get a weight of 1 and the conversion model may get a weight equal to the average revenue per conversion.'
-
Committed spend - A minimum threshold of spend per channel per constraint that must be met
-
Drop days - How frequently the Optimizer is allowed to spend money in a channel (2 means spend is allowed every other day)
Code Examples
Simple examples showing the recommended flow in code:
R
# Recast Optimizer API โ R Usage Example
#
# Workflow: List โ Show โ Modify form โ Create โ Poll โ Download CSV
#
# Prerequisites:
# install.packages(c("httr2", "jsonlite"))
# Add to .Renviron: API_PAT=gr_your_token_here
library(httr2)
library(jsonlite)
BASE_URL <- "https://app.getrecast.com"
CLIENT_SLUG <- "democlient" #Update to your client
PAT <- Sys.getenv("API_PAT")
parse <- \(resp) resp |> resp_body_string() |> fromJSON(simplifyVector = FALSE)
check <- function(resp, expected = 200) {
if (resp_status(resp) != expected) {
stop(sprintf("HTTP %d: %s", resp_status(resp), resp_body_string(resp)))
}
resp
}
opt_request <- function(...) {
request(BASE_URL) |>
req_url_path_append("api", "v1", "clients", CLIENT_SLUG, "optimizations", ...) |>
req_auth_bearer_token(PAT) |>
req_headers(Accept = "application/json") |>
req_error(is_error = \(resp) FALSE)
}
# Step 1: List optimizations and pick a successful one as a template
list_resp <- opt_request() |> req_url_query(page = 1, per_page = 10) |> req_perform() |> check()
optimizations <- parse(list_resp)$data
template_id <- Filter(\(x) x$status == "success", optimizations)[[1]]$id
# Step 2: Get the full optimization and extract its form
optimization <- opt_request(template_id) |> req_perform() |> check() |> parse()
form <- optimization$form
# Step 3: Modify the form and submit a new optimization
form$target <- as.character(round(as.numeric(form$target) * 1.1))
resp <- opt_request() |>
req_method("POST") |>
req_headers(`Content-Type` = "application/json") |>
req_body_json(list(
form = form,
name = paste("API Example -", Sys.time()),
show_in_ui = FALSE,
use_latest_deployments = TRUE
), auto_unbox = TRUE) |>
req_perform() |>
check(expected = 201)
new_id <- parse(resp)$id
cat(sprintf("Created optimization %d\n", new_id))
# Step 4: Poll until complete (typically 1โ6 minutes)
repeat {
result <- opt_request(new_id) |> req_perform() |> parse()
cat(sprintf("Status: %s\n", result$status))
if (result$status != "processing") break
Sys.sleep(30)
}
# Step 5: Download first available CSV
dl_key <- result$results[[1]]$downloads[[1]]$key
csv_resp <- opt_request(new_id, "downloads", dl_key) |>
req_headers(Accept = "text/csv") |>
req_perform() |>
check()
csv_data <- read.csv(text = resp_body_string(csv_resp), check.names = FALSE)
head(csv_data)
Python
# Recast Optimizer API โ Python Usage Example
#
# Workflow: List โ Show โ Modify form โ Create โ Poll โ Download CSV
#
# Prerequisites:
# pip install requests pandas
# Set environment variable: export API_PAT=gr_your_token_here
import os
import time
import io
import requests
import pandas as pd
from datetime import datetime
BASE_URL = "https://app.getrecast.com"
CLIENT_SLUG = "democlient" # Update to your client
PAT = os.environ["API_PAT"]
BASE_PATH = f"{BASE_URL}/api/v1/clients/{CLIENT_SLUG}/optimizations"
HEADERS = {"Authorization": f"Bearer {PAT}", "Accept": "application/json"}
def check(resp, expected=200):
if resp.status_code != expected:
raise Exception(f"HTTP {resp.status_code}: {resp.text}")
return resp
# Step 1: List optimizations and pick a successful one as a template
optimizations = check(requests.get(BASE_PATH, headers=HEADERS, params={"page": 1, "per_page": 10})).json()["data"]
template_id = next(o["id"] for o in optimizations if o["status"] == "success")
# Step 2: Get the full optimization and extract its form
optimization = check(requests.get(f"{BASE_PATH}/{template_id}", headers=HEADERS)).json()
form = optimization["form"]
# Step 3: Modify the form and submit a new optimization
form["target"] = str(round(float(form["target"]) * 1.1))
resp = check(requests.post(BASE_PATH, headers={**HEADERS, "Content-Type": "application/json"}, json={
"form": form,
"name": f"API Example - {datetime.now()}",
"show_in_ui": False,
"use_latest_deployments": True,
}), expected=201)
new_id = resp.json()["id"]
print(f"Created optimization {new_id}")
# Step 4: Poll until complete (typically 1โ6 minutes)
while True:
result = requests.get(f"{BASE_PATH}/{new_id}", headers=HEADERS).json()
print(f"Status: {result['status']}")
if result["status"] != "processing":
break
time.sleep(30)
# Step 5: Download first available CSV
dl_key = result["results"][0]["downloads"][0]["key"]
csv_resp = check(requests.get(f"{BASE_PATH}/{new_id}/downloads/{dl_key}", headers={**HEADERS, "Accept": "text/csv"}))
csv_data = pd.read_csv(io.StringIO(csv_resp.text))
print(csv_data.head())