The Run Launching API lets you programmatically launch Recast runs without going through the admin UI. This is an alternative to the manual process of visiting the Launch Run page in the admin, uploading a CSV, and a prior settings Google Sheet link.
See the Swagger documentation for full schema details. See Authentication section in the MMM knowledge base for details on how to gain access.
The Endpoints
-
POST /v1/clients/{client_slug}/runs: Launches a new model run
-
GET /v1/clients/{client_slug}/runs/{id}: Returns the status, metadata, and form for a specific run
-
GET /v1/clients/{client_slug}/runs: Returns a paginated list of runs
-
GET /v1/clients/{client_slug}/runs/{id}/downloads/clean_data: Returns the clean data CSV the run was trained on
The Form
The request body wraps all run settings inside a form object. The client is identified by the {client_slug} path parameter in the URL:
{
"form": {
"run_type": "model_run",
"google_sheet_priors_url": "https://docs.google.com/spreadsheets/d/abc/edit",
"clean_data": "date,channel_1,...\n2024-1-1,1000,...\n"
}
}
Required Inputs
|
Field |
Type |
Description |
|---|---|---|
|
|
string |
URL pointing to the prior settings Google Sheet |
|
|
string |
Raw CSV text (UTF-8) with |
|
|
string |
One of: |
Optional Inputs
Stability Loop
The following fields are only used when run_type is stability_loop:
|
Field |
Type |
Description |
|---|---|---|
|
|
integer |
Number of stability loop periods to run |
|
|
integer |
Number of days to hold out in each period |
Holdout Test
The following field is only used when run_type is holdout:
|
Field |
Type |
Description |
|---|---|---|
|
|
array of integers |
Number of days to holdout in each test period |
Run Types
|
|
What it does |
|---|---|
|
|
Standard model run |
|
|
Tests that the model can recover known parameters |
|
|
Runs the model across consecutive holdout periods to assess stability |
|
|
Runs the model holding out specific numbers of days to evaluate out-of-sample performance |
Response
A successful POST returns HTTP 201 with the new run's ID:
{
"id": 2786
}
Use id to poll for status and retrieve results.
Rate Limits
-
100 runs per day
-
20 runs per minute
Exceeding either limit returns HTTP 429. Rate limits are in place to protect against scripts accidentally launching runs. Space out batch submissions accordingly.
Status Polling
Model runs are asynchronous. After launching, poll GET /v1/clients/{client_slug}/runs/{id} until status is no longer an in-progress value. Typical run times vary by run type and data size β model runs generally take 20β60 minutes.
In-progress statuses (keep polling):
|
Status |
Meaning |
|---|---|
|
|
Run has been accepted and will begin processing shortly |
|
|
Run is queued |
|
|
Run is actively processing |
|
|
Run is actively processing |
Terminal statuses (stop polling):
|
Status |
Meaning |
|---|---|
|
|
Run completed successfully |
|
|
Run failed |
|
|
Run was aborted |
|
|
Run failed during data sync (treated as aborted) |
Downloading Clean Data
Once a run exists, you can retrieve the clean data CSV it was trained on:
GET /v1/clients/{client_slug}/runs/{id}/downloads/clean_data
This returns text/csv. When the clean_data field is submitted via the POST endpoint the format should be a CSV string with new line characters (\n).
Skill File (Beta)
We have a skill file to help AI coding assistants get up to speed on the Model Launching API. It is in beta and is best used for writing boilerplate code. Please review any generated code before running it.
Code Examples
Python
# Recast Model Launching API β Python Usage Example
#
# Workflow: Submit a model run β Poll for completion
#
# Prerequisites:
# pip install requests
# Generate your token: log into the Recast app β click your email (top right) β Generate API token
# Set as an environment variable: export API_PAT=gr_your_token_here
# Never hard-code or share this token.
import os
import time
import requests
# ββ Edit these ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CLIENT_SLUG = "my-client" # Client slug (visible in the app URL)
PRIORS_URL = "https://docs.google.com/spreadsheets/d/..." # Google Sheet URL
CLEAN_DATA = "clean_data.csv" # Path to your clean data CSV
RUN_TYPE = "model_run" # model_run | parameter_recovery | holdout | stability_loop
# ββ Rarely needs changing βββββββββββββββββββββββββββββββββββββββββββββββββββββ
BASE_URL = "https://api.getrecast.com"
PAT = os.environ["API_PAT"] # Set via: export API_PAT=gr_your_token_here
BASE_PATH = f"{BASE_URL}/v1/clients/{CLIENT_SLUG}"
HEADERS = {"Authorization": f"Bearer {PAT}", "Content-Type": "application/json"}
# ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def check(resp, expected=200):
if resp.status_code != expected:
body = resp.text or "(no body)"
raise Exception(f"HTTP {resp.status_code}: {body}")
return resp
# ββ Step 1: Read CSV and normalize line endings βββββββββββββββββββββββββββββββ
with open(CLEAN_DATA, "r", newline="") as f:
clean_data = f.read().replace("\r\n", "\n")
# ββ Step 2: Submit the run ββββββββββββββββββββββββββββββββββββββββββββββββββββ
resp = check(requests.post(
f"{BASE_PATH}/runs",
headers=HEADERS,
json={
"form": {
"run_type": RUN_TYPE,
"google_sheet_priors_url": PRIORS_URL,
"clean_data": clean_data,
},
},
), expected=201)
run_id = resp.json()["id"]
print(f"Launched run {run_id}")
# ββ Step 3: Poll until complete βββββββββββββββββββββββββββββββββββββββββββββββ
timeout_seconds = 90 * 60 # model runs can take 20β60 minutes
started_at = time.time()
while True:
if time.time() - started_at > timeout_seconds:
raise TimeoutError(f"Timed out waiting for run {run_id}")
result = check(requests.get(f"{BASE_PATH}/runs/{run_id}", headers=HEADERS)).json()
print(f"Status: {result['status']}")
if result["status"] not in ("launching", "waiting", "running", "processing"):
break
time.sleep(60)
print(f"Run {run_id} finished with status: {result['status']}")
Stability Loop Example (Python)
with open(CLEAN_DATA, "r", newline="") as f:
clean_data = f.read().replace("\r\n", "\n")
resp = check(requests.post(
f"{BASE_PATH}/runs",
headers=HEADERS,
json={
"form": {
"run_type": "stability_loop",
"google_sheet_priors_url": PRIORS_URL,
"clean_data": clean_data,
"n_holdout": 6,
"days_per_holdout": 30,
},
},
), expected=201)
print(f"Launched stability loop: {resp.json()['id']}")
Holdout Test Example (Python)
with open(CLEAN_DATA, "r", newline="") as f:
clean_data = f.read().replace("\r\n", "\n")
resp = check(requests.post(
f"{BASE_PATH}/runs",
headers=HEADERS,
json={
"form": {
"run_type": "holdout",
"google_sheet_priors_url": PRIORS_URL,
"clean_data": clean_data,
"holdout_days": [14, 28, 42],
},
},
), expected=201)
print(f"Launched holdout: {resp.json()['id']}")
Downloading Clean Data (Python)
import io, pandas as pd
csv_resp = check(requests.get(
f"{BASE_PATH}/runs/{run_id}/downloads/clean_data",
headers={"Authorization": f"Bearer {PAT}"},
))
df = pd.read_csv(io.StringIO(csv_resp.text))
df.to_csv("downloaded_clean_data.csv", index=False)
print(f"Downloaded clean data: {len(df)} rows, {len(df.columns)} columns")
R
# Recast Model Launching API β R Usage Example
#
# Prerequisites:
# install.packages(c("httr2", "jsonlite"))
# Add to .Renviron: API_PAT=gr_your_token_here
library(httr2)
library(jsonlite)
# ββ Edit these ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CLIENT_SLUG <- "my-client"
PRIORS_URL <- "https://docs.google.com/spreadsheets/d/..."
CLEAN_DATA <- "clean_data.csv"
RUN_TYPE <- "model_run"
# ββ Rarely needs changing βββββββββββββββββββββββββββββββββββββββββββββββββββββ
BASE_URL <- "https://api.getrecast.com"
PAT <- Sys.getenv("API_PAT")
parse <- \(resp) resp |> resp_body_string() |> fromJSON(simplifyVector = FALSE)
check <- function(resp, expected = 200) {
if (resp_status(resp) != expected) {
body <- tryCatch(resp_body_string(resp), error = \(e) "(no body)")
stop(sprintf("HTTP %d: %s", resp_status(resp), body))
}
resp
}
# ββ Step 1: Read CSV and normalize line endings βββββββββββββββββββββββββββββββ
clean_data <- paste(readLines(CLEAN_DATA), collapse = "\n")
# ββ Step 2: Submit the run ββββββββββββββββββββββββββββββββββββββββββββββββββββ
resp <- request(BASE_URL) |>
req_url_path_append("v1", "clients", CLIENT_SLUG, "runs") |>
req_auth_bearer_token(PAT) |>
req_error(is_error = \(resp) FALSE) |>
req_body_json(list(
form = list(
run_type = RUN_TYPE,
google_sheet_priors_url = PRIORS_URL,
clean_data = clean_data
)
), auto_unbox = TRUE) |>
req_perform() |>
check(expected = 201)
run_id <- parse(resp)$id
cat(sprintf("Launched run %d\n", run_id))
# ββ Step 3: Poll until complete βββββββββββββββββββββββββββββββββββββββββββββββ
timeout_seconds <- 90 * 60
started_at <- proc.time()["elapsed"]
repeat {
if ((proc.time()["elapsed"] - started_at) > timeout_seconds) {
stop(sprintf("Timed out waiting for run %d", run_id))
}
result <- request(BASE_URL) |>
req_url_path_append("v1", "clients", CLIENT_SLUG, "runs", run_id) |>
req_auth_bearer_token(PAT) |>
req_error(is_error = \(resp) FALSE) |>
req_perform() |> check() |> parse()
cat(sprintf("Status: %s\n", result$status))
if (!result$status %in% c("launching", "waiting", "running", "processing")) break
Sys.sleep(60)
}
cat(sprintf("Run %d finished with status: %s\n", run_id, result$status))