Recast Knowledge Base
Breadcrumbs

The Forecaster API

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

The Forecaster API let’s you submit forecast request to the Recast backend for your models. It is currently more limited than the Plans tool in the app. Some things to keep in mind:

  • Forecasts must start immediately after the last day in the model. They cannot start in the future or in the past.

  • Daily budgets for each channel must be provided. The API will not create a daily budget from a weekly/monthly budget for you.

First, set up a forecast as you want to run it using the Forecaster UI (not the Plans UI). Run it. Use the /forecasts/[id] endpoint to get the form used. Re-use the arguments, changing only the ones you need (like the budget).

The endpoints

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

  2. POST /forecasts - Takes a form to create a Forecast

    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.

    4. Takes run_recommendationsto determine whether to calculate recommendations on how to improve your budget (Note: this significantly increases run times).

  3. GET /forecasts - Returns a paginated list of all Forecasts

  4. GET /forecasts/[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 Forecast.

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.

The Budget

The budget is a json object with the following format:

JSON
[["date","influencers","linear_tv","mailers","meta_prospecting","meta_retargeting","podcast","search_non_branded","brand_awareness","price","search_branded"],
  ["2026-03-15","1587.47","5450.46","0.0","13779.0","3331.96","2004.89","1513.74","1.171613","15.10184","1283.49"],
  ["2026-03-16","   0.00","5423.17","    0.0","13699.1","3315.81","   0.00","1480.58","1.171111","15.10184","1282.77"], ...]

It must contain date, all spend channels, non-spend channels, and contextual variables. Lower funnel channels are optional depending on what arguments you use for lower_funnel_caps.

Code Examples

Simple examples showing the recommended flow in code:

R

R
# Submit a forecast from a CSV file
#
# Usage: Set CSV_PATH below (or pass via command line), then source this script.
# The CSV should have a "date" column and one column per channel/context variable,
# matching the format used in the Recast app.

library(httr2)
library(jsonlite)

# ── Configuration ─────────────────────────────────────────────────────────────
CSV_PATH    <- "budget.csv"  # <-- Update this to your CSV file path
BASE_URL    <- "https://api.getrecast.com"
CLIENT_SLUG <- "democlient"
PAT         <- Sys.getenv("API_PAT")
KPI_NAME <- "Amazon Revenue"  # <-- Update to your KPI name


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
}

forecast_request <- function(...) {
  request(BASE_URL) |>
    req_url_path_append("v1", "clients", CLIENT_SLUG, "forecasts", ...) |>
    req_auth_bearer_token(PAT) |>
    req_headers(Accept = "application/json") |>
    req_error(is_error = \(resp) FALSE)
}

# ── Step 1: Read CSV and convert to budget 2D array ──────────────────────────
csv <- read.csv(CSV_PATH, check.names = FALSE)
cat(sprintf("Read %d rows x %d columns from %s\n", nrow(csv), ncol(csv), CSV_PATH))

#Put budget in format that can be converted to json
budget = trimws(rbind(
  colnames(csv),
  apply(t(csv), 1, as.vector)
))

# ── Step 2: Get depvar_configurations from the KPIs endpoint ─────────────────
kpi_request <- function(...) {
  request(BASE_URL) |>
    req_url_path_append("v1", "clients", CLIENT_SLUG, "kpis", ...) |>
    req_auth_bearer_token(PAT) |>
    req_headers(Accept = "application/json") |>
    req_error(is_error = \(resp) FALSE)
}

cat(sprintf("Fetching KPI '%s'...\n", KPI_NAME))
kpis <- kpi_request() |> req_perform() |> check() |> parse()
kpi <- Filter(\(x) x$name == KPI_NAME, kpis$data)[[1]]

depvar_configs <- kpi_request(kpi$id) |> req_perform() |> check() |> parse()
depvar_configs <- depvar_configs$depvar_configurations

cat(sprintf("Using depvar_configurations from KPI '%s' (%d depvars)\n",
            KPI_NAME, length(depvar_configs)))

# ── Step 3: Submit forecast ──────────────────────────────────────────────────
resp <- forecast_request() |>
  req_method("POST") |>
  req_headers(`Content-Type` = "application/json") |>
  req_body_json(list(
    form = list(
      budget = budget,
      depvar_configurations = depvar_configs
    ),
    name = paste("Forecast from CSV -", 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 forecast %d\n", new_id))

# ── Step 4: Poll until complete ──────────────────────────────────────────────
repeat {
  result <- forecast_request(new_id) |> req_perform() |> check() |> parse()
  cat(sprintf("Status: %s\n", result$status))
  if (!result$status %in% c("ready", "processing")) break
  Sys.sleep(30)
}

if (result$status == "success") {
  cat("Forecast complete!\n")
} else {
  warning(sprintf("Forecast ended with status: %s", result$status))
}


Python

Python
# Submit a forecast from a CSV file
#
# Usage: python submit_forecast.py
# The CSV should have a "date" column and one column per channel/context variable.

import os
import csv
import time
import requests
from datetime import datetime

# ── Configuration ─────────────────────────────────────────────────────────────
CSV_PATH    = "budget.csv"  # <-- Update this to your CSV file path
BASE_URL    = "https://api.getrecast.com"
CLIENT_SLUG = "democlient"
PAT         = os.environ["API_PAT"]

KPI_NAME    = "Amazon Revenue"  # <-- Update to your KPI name

BASE_PATH = f"{BASE_URL}/v1/clients/{CLIENT_SLUG}/forecasts"
KPI_PATH  = f"{BASE_URL}/v1/clients/{CLIENT_SLUG}/kpis"
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: Read CSV and convert to budget 2D array ──────────────────────────
with open(CSV_PATH) as f:
    reader = csv.reader(f)
    rows = list(reader)

headers = rows[0]
budget = [headers]
for row in rows[1:]:
    # First column (date) stays as-is, rest are numeric converted to strings
    budget.append([row[0]] + [str(float(v)) for v in row[1:]])

print(f"Read {len(rows) - 1} rows x {len(headers)} columns from {CSV_PATH}")

# ── Step 2: Get depvar_configurations from the KPIs endpoint ──────────────────
print(f"Fetching KPI '{KPI_NAME}'...")
kpis = check(requests.get(KPI_PATH, headers=HEADERS)).json()["data"]
kpi = next(k for k in kpis if k["name"] == KPI_NAME)
depvar_configs = check(requests.get(f"{KPI_PATH}/{kpi['id']}", headers=HEADERS)).json()["depvar_configurations"]

print(f"Using depvar_configurations from KPI '{KPI_NAME}' ({len(depvar_configs)} depvars)")

# ── Step 3: Submit forecast ──────────────────────────────────────────────────
resp = check(requests.post(BASE_PATH, headers={**HEADERS, "Content-Type": "application/json"}, json={
    "form": {
        "budget": budget,
        "depvar_configurations": depvar_configs,
    },
    "name": f"Forecast from CSV - {datetime.now()}",
    "show_in_ui": False,
    "use_latest_deployments": True,
}), expected=201)

new_id = resp.json()["id"]
print(f"Created forecast {new_id}")

# ── Step 4: Poll until complete ──────────────────────────────────────────────
while True:
    result = check(requests.get(f"{BASE_PATH}/{new_id}", headers=HEADERS)).json()
    print(f"Status: {result['status']}")
    if result["status"] not in ("ready", "processing"):
        break
    time.sleep(30)

if result["status"] == "success":
    print("Forecast complete!")
else:
    print(f"WARNING: Forecast ended with status: {result['status']}")