prompts.csv:
text,category
best mixed pickle brand,Discovery
misali vs shan achar,Comparison
achar price per kg,Pricing
import csv, os, requests
BASE = "https://api.rankraft.ai"
HEADERS = {"X-API-Key": os.environ["RANKRAFT_API_KEY"]}
SITE = os.environ["RANKRAFT_SITE_ID"]
URL = f"{BASE}/v1/sites/{SITE}/prompts"
with open("prompts.csv") as f:
wanted = [{"text": r["text"], "category": r["category"]} for r in csv.DictReader(f)]
# 1. Dry run: see what would be created, and whether it fits the plan.
preview = requests.post(URL, json={"prompts": wanted, "dry_run": True}, headers=HEADERS, timeout=60)
if preview.status_code == 422:
raise SystemExit(preview.json()["error"]["message"])
preview.raise_for_status()
print(f"{preview.json()['created']} new, {preview.json()['existing']} already tracked")
# 2. Apply. Re-running is safe: existing text is matched, never duplicated.
requests.post(URL, json={"prompts": wanted}, headers=HEADERS, timeout=60).raise_for_status()
# 3. Make the active set match the sheet. Creating returns an existing
# prompt as it is, so a row that was disabled earlier stays disabled
# until it is switched back on here.
def norm(t): return " ".join(t.split()).lower()
keep = {norm(w["text"]) for w in wanted}
every = requests.get(URL, params={"limit": 500}, headers=HEADERS, timeout=60).json()["data"]
stale = [p["id"] for p in every if p["status"] == "active" and norm(p["text"]) not in keep]
revive = [p["id"] for p in every if p["status"] == "disabled" and norm(p["text"]) in keep]
for ids, status in ((stale, "disabled"), (revive, "active")):
if ids:
r = requests.patch(f"{URL}/status", json={"prompt_ids": ids, "status": status}, headers=HEADERS, timeout=60)
r.raise_for_status()
print(f"{status}: {len(ids)}")
Prompts are never deleted over the API, only disabled, so a mistake in the sheet loses no history: re-add the row and the next sync switches the prompt back on with its answers intact.Over 500 prompts? Page through
GET /prompts with cursor before step 3.