Quickstart
This page takes you from an API key to a finished run over plain HTTP, with curl. To do the same
from Claude Code, Cursor or Codex, connect them to the MCP server instead:
Connect your coding agent has the setup and the same first run as prompts.
Every request goes to https://api.saram.sh with Authorization: Bearer <your key>. The
HTTP API reference has every operation in full.
1. Get an API key
Section titled “1. Get an API key”Sign up or sign in at dashboard.saram.sh. Signing up creates your organisation. Mint a key under Settings > API keys. The dashboard shows it once: Saram keeps only a hash of it, so a lost key is revoked and replaced, never shown again.
Put it in your shell, never in a file you commit:
export API_KEY=<your key>Check it with who_am_i, the one route every key may call:
curl -s https://api.saram.sh/api/v1/me -H "Authorization: Bearer $API_KEY"The answer names the key’s caller, its organisation (tenant, with its plan) and what it may do
(scopes). To start runs, the key needs runs:write; to read them, runs:read. Storing a model key
needs credentials:write, and billing needs keys:admin.
2. Pick a persona
Section titled “2. Pick a persona”A run sends personas through your product. Each one is named by its registry address, id@version:
curl -s https://api.saram.sh/api/v1/personas -H "Authorization: Bearer $API_KEY"{ "personas": [ { "address": "dana_first_timer@1", "id": "dana_first_timer", "version": 1, "name": "Dana Whitfield", "pack": "general", "tags": ["general", "novice", "desktop", "first_time"] } ]}The list above is cut to its first entry. get_persona answers one persona in
full: its goals, its tolerance and the question it answers at the end.
3. Bring a model provider
Section titled “3. Bring a model provider”The personas and the judge make model calls. On the Free and Pro plans they are made on your own provider: store a key from Anthropic, OpenAI, OpenRouter or another supported provider, and the run spec names it. Store one under Settings > Credentials on the dashboard, or over the API:
jq -n --arg v "$ANTHROPIC_API_KEY" '{name: "anthropic", kind: "anthropic", value: $v}' | curl -s -X POST https://api.saram.sh/api/v1/credentials \ -H "Authorization: Bearer $API_KEY" -H 'content-type: application/json' -d @-The answer’s secret_ref is what the spec names. The key itself is never returned.
Providers and your own keys covers every provider, the ChatGPT login, and which
model each role uses.
4. Write the smallest run spec
Section titled “4. Write the smallest run spec”A run spec says where to go (target), who goes (personas), what they try (missions) and how far
each may go (budget). Save this as spec.json, with your own URL, the secret_ref from step 3,
and in place of /account a part of the URL your product shows once someone is signed in:
{ "target": { "entry_url": "https://staging.example.com" }, "personas": ["dana_first_timer@1"], "missions": [ { "id": "sign_up", "goal": "create an account", "success_criteria": [ { "kind": "url_matches", "value": "/account" }, { "kind": "judge_assertion", "value": "the persona reached a signed-in page" } ] } ], "budget": { "steps_per_session": 30 }, "providers": { "entries": { "main": { "model": "anthropic:claude-opus-5", "credential_ref": "<your secret_ref>" } } }}Only the url_matches criterion is checked by machine: when the persona says it is finished, the
session ends success_criteria_met if the last URL it was on contains /account, and
claimed_finished_unverified if not. The judge_assertion is text the judge reads beside the goal.
Without a url_matches criterion, every finished session ends unverified
(Success criteria: what is checked).
The URL must be one Saram’s browsers reach over the internet: a staging deployment, a preview, or a tunnel to your machine (Testing localhost and previews). The run spec explains every field.
5. Validate it (free)
Section titled “5. Validate it (free)”validate_run runs every check that starting a run runs, and starts
nothing. It costs nothing, so call it before every run:
curl -s -X POST https://api.saram.sh/api/v1/runs/validate \ -H "Authorization: Bearer $API_KEY" -H 'content-type: application/json' -d @spec.jsonA spec that passes answers 200 with the spec as the run would store it, the sessions it plans (one
per persona per mission), any warnings, and a cost_ceiling:
{ "cost_ceiling": { "usd": 0.6, "sessions": 1, "steps_per_session": 30, "basis": "up to $0.60: 1 session x 30 persona steps at $0.02 a step on the pilot plan, which bills steps; judging and reproduction are included. ...", "model_calls": "Model calls, never in this figure: ..." }, "sessions": [ { "session_id": "<run_id>.sess_01_dana_first_timer", "persona_id": "dana_first_timer@1", "mission_id": "sign_up" } ], "warnings": []}This answer is cut to its main fields, and it is from an organisation on an older plan that bills
persona steps; on Free and Pro the basis counts Usage instead. cost_ceiling.usd is the most the
run can bill on your plan, not a forecast: every step is counted at
its most. basis says how it was reached, in your plan’s terms, and model_calls says who bills the
model calls. On Free and Pro, usage_ceiling also says whether your balance covers the run
(Usage and billing).
A spec that fails answers 422 with a detail naming each field and what is wrong with it, for
example a credential_ref that is not one of yours:
run spec rejected: providers entry 'main' names awssm://... which is not this tenant's credential: upload the key with POST /api/v1/credentials and name the ref it returnsFix the spec and validate again until it answers 200.
6. Start the run
Section titled “6. Start the run”curl -s -X POST https://api.saram.sh/api/v1/runs \ -H "Authorization: Bearer $API_KEY" -H 'content-type: application/json' -d @spec.jsoncreate_run answers 202 with the run_id at once, and the run starts in the
background:
{ "run_id": "run_2026-10-23_ab12cd", "workflow_id": "..." }A 422 is a spec that fails a check, with no run created. A 402 is a run your plan cannot pay for
now (Usage and billing).
7. Poll until it finishes
Section titled “7. Poll until it finishes”get_run answers the run’s status, which moves pending, running,
observing, then stops on completed, failed or cancelled:
RUN=run_2026-10-23_ab12cdwhile :; do status=$(curl -s https://api.saram.sh/api/v1/runs/$RUN \ -H "Authorization: Bearer $API_KEY" | jq -r .status) echo "$status" case $status in completed|failed|cancelled) break ;; esac sleep 15doneOnly completed means there are results to read. failed carries a status_reason. While the run
is open, billed_steps and each session’s heartbeat_step climb. To stop a run early,
POST /api/v1/runs/$RUN/cancel (signal_run); a cancelled run is not judged.
8. Read what it found
Section titled “8. Read what it found”A run gives two kinds of output, and they are not the same thing (Issues vs persona feedback):
H="Authorization: Bearer $API_KEY"curl -s https://api.saram.sh/api/v1/runs/$RUN/issues -H "$H" # defectscurl -s https://api.saram.sh/api/v1/runs/$RUN/verdicts -H "$H" # persona feedbackcurl -s https://api.saram.sh/api/v1/runs/$RUN/opportunities -H "$H" # persona feedbackcurl -s https://api.saram.sh/api/v1/runs/$RUN/tester-errors -H "$H" # the persona's own slips- Issues (
get_issues) are product defects. Beside them,reading[]says for each one whethermay_file_ticket_aloneistrue: only then did a replay reproduce it, and only then file it as a confirmed bug. - Verdicts (
get_verdicts) are each persona’s answer at the end of its session, quoted from its own diary. Opportunities (get_opportunities) are what the personas’ behaviour says the product could do better or already does well. Both are opinion: attribute them to the persona. - Tester errors (
get_tester_errors) are slips the persona made, such as a mistyped password. They are not defects and say what to fix in your test setup.
The session video
Section titled “The session video”Each session is recorded unless the spec turns it off. list_sessions
gives each session’s video_url, a path on the API that takes the same key:
video=$(curl -s https://api.saram.sh/api/v1/runs/$RUN/sessions -H "$H" | jq -r '.sessions[0].video_url')curl -s -o session.webm "https://api.saram.sh$video" -H "$H"The video is a WebM file. The route honours Range
and HEAD, so a player can seek in it. Evidence is kept for your plan’s retention period
(Usage and billing); the run says when in evidence_expires_at.
- The run spec: every field, with a full example.
- Issues vs persona feedback: what to file as a ticket, and what not to.
- Providers and your own keys: who bills each model call.
- Usage and billing: what a run costs and how you pay for it.
What was checked
Section titled “What was checked”| Block | Checked |
|---|---|
GET /me, GET /personas |
2026-10-23 against api.saram.sh, platform-api 0.9.0; the answers’ fields as shown, with the account’s own values left out |
| Storing a model key | Not run for this page; the body is checked against the reference’s CredentialWrite schema |
Validate, 200 |
2026-10-23, the spec above without its providers block and its url_matches criterion, on an organisation on a plan that bills steps; the answer is cut to the fields shown |
The url_matches criterion |
Not run for this page; added 2026-10-24 from the RunSpecMission schema in platform-api 0.11.0, which also gives the two end_reasons |
Validate, 422 |
2026-10-23, a providers entry naming a ref that is not the organisation’s; the ref is elided here |
| Starting a run | Not run for this page; the answer’s fields are the reference’s StartedRun |
| Polling, the read routes and the video | 2026-10-23, on a finished run: the status, the four read routes, and HEAD on a video_url (200, video/webm, accept-ranges: bytes) |