Recast Knowledge Base
Breadcrumbs

The Optimizer API

Link to Swagger docs: https://app.getrecast.com/api-docs/index.html (must be logged into to app.getrecast.com prior to visiting).

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

  1. GET /optimizations/[id] - Returns an existing Optimization with status, metadata, and available downloads

  2. POST /optimizations - Takes a form to create an Optimization

    1. Takes name to name it

    2. Takes show_in_ui to determine whether to show it in the app or not

    3. 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 the depvar_configurations part of the form.

  3. GET /optimizations - Returns a paginated list of all Optimizations

  4. GET /optimizations/[id]/downloads/[key] - Returns individual CSV downloads

Additionally, there are two KPI endpoints that may be helpful when formatting your requests:

  1. GET /kpis - returns all KPIs you have configured in your dash

  2. 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:

  1. Determines the weights to apply to each model (especially helpful when doing weighted multi-model optimizations)

  2. Determines the spikes to use in each model

  3. 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

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

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())