> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rankraft.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage prompts from a CSV

> Sync a prompt list from a spreadsheet, safely, with a dry run first.

Keep your prompt list in a spreadsheet and sync it with two calls. Needs a key with **Read and manage prompts** access.

`prompts.csv`:

```csv theme={"system"}
text,category
best mixed pickle brand,Discovery
misali vs shan achar,Comparison
achar price per kg,Pricing
```

```python theme={"system"}
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)}")
```

> [!WARNING]
>
> 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.

To change a prompt's wording, disable the old one and add the new text as a new row. Answers to different wordings are not comparable, so RankRaft keeps them apart.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.