BrowserStack AI Evals

Experiments API

Create and manage experiments and experiment runs.

Experiments API

Experiments associate a dataset and an evaluator list (and optionally a prompt) to measure LLM pipeline quality systematically. Each experiment can have multiple runs.

Create Experiment

POST /api/public/experiments

Two creation modes are supported:

Mode 1 — by dataset run tag (use a tagged dataset run as ground truth):

FieldTypeRequiredDescription
namestringYesExperiment name
descriptionstringNoDescription
datasetRunTagIdstringYesTag ID from a dataset run
evaluatorListIdstringYesEvaluator list ID
concurrencyintegerNoMax concurrent eval executions

Mode 2 — by prompt + dataset (run evaluations against a prompt on a dataset):

FieldTypeRequiredDescription
namestringYesExperiment name
descriptionstringNoDescription
promptIdstringYesPrompt ID
datasetIdstringYesDataset ID
evaluatorListIdstringYesEvaluator list ID
concurrencyintegerNoMax concurrent eval executions

cURL Example (tag-based)

curl -X POST https://evals-api.browserstack.com/api/public/experiments \
  -u "pk-lf-...:sk-lf-..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "rag-eval-baseline",
    "datasetRunTagId": "tag-uuid-1",
    "evaluatorListId": "eval-list-uuid-1"
  }'

cURL Example (prompt-based)

curl -X POST https://evals-api.browserstack.com/api/public/experiments \
  -u "pk-lf-...:sk-lf-..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "prompt-eval-v2",
    "promptId": "prompt-uuid-1",
    "datasetId": "ds-uuid-1",
    "evaluatorListId": "eval-list-uuid-1"
  }'

Response

{
  "id": "exp-uuid-1",
  "name": "rag-eval-baseline",
  "description": null,
  "concurrency": 10,
  "projectId": "proj-xyz",
  "datasetId": "ds-uuid-1",
  "promptId": null,
  "datasetRunTagId": "tag-uuid-1",
  "experimentEvaluatorId": "eval-list-uuid-1",
  "createdBy": "user-123",
  "createdAt": "2026-04-03T10:00:00.000Z",
  "updatedAt": "2026-04-03T10:00:00.000Z"
}

List Experiments

GET /api/public/experiments
ParameterTypeDescription
pageintegerPage number
limitintegerItems per page
curl "https://evals-api.browserstack.com/api/public/experiments?page=1&limit=20" \
  -u "pk-lf-...:sk-lf-..."

Response:

{
  "experiments": [
    {
      "id": "exp-uuid-1",
      "name": "rag-eval-baseline",
      "projectId": "proj-xyz",
      "datasetId": "ds-uuid-1",
      "promptId": null,
      "datasetRunTagId": "tag-uuid-1",
      "experimentEvaluatorId": "eval-list-uuid-1",
      "concurrency": 10,
      "createdBy": "user-123",
      "createdAt": "2026-04-03T10:00:00.000Z",
      "updatedAt": "2026-04-03T10:00:00.000Z"
    }
  ],
  "totalCount": 1
}

Get Experiment

GET /api/public/experiments/{experimentId}
curl "https://evals-api.browserstack.com/api/public/experiments/exp-uuid-1" \
  -u "pk-lf-...:sk-lf-..."

Delete Experiment

DELETE /api/public/experiments/{experimentId}
curl -X DELETE "https://evals-api.browserstack.com/api/public/experiments/exp-uuid-1" \
  -u "pk-lf-...:sk-lf-..."

Experiment Runs

Create Experiment Run

POST /api/public/experiment-runs
FieldTypeRequiredDescription
experimentIdstringYesExperiment ID
namestringNoRun name
descriptionstringNoDescription
metadataobjectNoMetadata
curl -X POST https://evals-api.browserstack.com/api/public/experiment-runs \
  -u "pk-lf-...:sk-lf-..." \
  -H "Content-Type: application/json" \
  -d '{
    "experimentId": "exp-uuid-1",
    "name": "run-2026-04-03"
  }'

Response:

{
  "id": "run-uuid-1",
  "experimentId": "exp-uuid-1",
  "name": "run-2026-04-03",
  "status": "PENDING",
  "createdAt": "2026-04-03T10:00:00.000Z",
  "updatedAt": "2026-04-03T10:00:00.000Z"
}

List Experiment Runs

GET /api/public/experiment-runs
ParameterTypeDescription
experimentIdstringFilter by experiment ID
pageintegerPage number
limitintegerItems per page
curl "https://evals-api.browserstack.com/api/public/experiment-runs?experimentId=exp-uuid-1" \
  -u "pk-lf-...:sk-lf-..."

Get Experiment Run

GET /api/public/experiment-runs/{runId}
curl "https://evals-api.browserstack.com/api/public/experiment-runs/run-uuid-1" \
  -u "pk-lf-...:sk-lf-..."

Delete Experiment Run

DELETE /api/public/experiment-runs/{runId}
curl -X DELETE "https://evals-api.browserstack.com/api/public/experiment-runs/run-uuid-1" \
  -u "pk-lf-...:sk-lf-..."

Get Experiment Run Summary

GET /api/public/experiment-runs/{experimentRunId}/summary

Returns scores, metrics, diffs, and UI deep-links for a run in a single call. This endpoint composes data from multiple internal sources so you do not need to orchestrate separate requests for scores, latency, and cost.

Path Parameters

ParameterTypeRequiredDescription
experimentRunIdstringYesID of the experiment run to summarise

Query Parameters

ParameterTypeRequiredDefaultDescription
compareToRunIdstringNoID of the baseline run to diff against. When omitted, the server uses the experiment's UI-pinned baseline (set via "Set as baseline" in the experiment view). If neither is set, no comparison is performed: comparisonExperimentRunId is null and every score's diff is null. The server does NOT auto-pick a baseline by recency.

cURL Examples

Minimal — uses the experiment's UI-pinned baseline (or returns no comparison if none):

curl "https://evals-api.browserstack.com/api/public/experiment-runs/run-uuid-1/summary" \
  -u "pk-lf-...:sk-lf-..."

With an explicit baseline:

curl "https://evals-api.browserstack.com/api/public/experiment-runs/run-uuid-1/summary\
?compareToRunId=run-uuid-0" \
  -u "pk-lf-...:sk-lf-..."

Response

{
  "projectId": "aa7557d1-0241-4a8e-91c4-184f55ab64a1",
  "projectName": "Demo Project",

  "experimentId": "cmpe0fezj01zrjx08m4wpr77y",
  "experimentName": "checkout-flow-v3",

  "experimentRunId": "cmpe0ff0g01zvjx08std8i28o",
  "experimentRunName": "run-2026-06-08-prompt-v3",

  "comparisonExperimentRunId": "cmpe0fabc01zvjx08abc1234",
  "comparisonExperimentRunName": "run-2026-06-07-prompt-v2",

  "urls": {
    "project":      "https://evals.browserstack.com/project/aa7557d1-...",
    "experiment":   "https://evals.browserstack.com/project/aa7557d1-.../experiments/cmpe0fezj...",
    "run":          "https://evals.browserstack.com/project/aa7557d1-.../experiments/cmpe0fezj.../runs?runId=cmpe0ff0g...",
    "scoresTab":    "https://evals.browserstack.com/project/aa7557d1-.../experiments/cmpe0fezj.../runs?runId=cmpe0ff0g...&tab=scores",
    "analyticsTab": "https://evals.browserstack.com/project/aa7557d1-.../experiments/cmpe0fezj.../runs?runId=cmpe0ff0g...&tab=analytics"
  },

  "scores": [
    {
      "name":            "exact_match",
      "dataType":        "NUMERIC",
      "score":           0.82,
      "passCount":       8,
      "failCount":       2,
      "totalItems":      10,
      "threshold":       0.70,
      "criteria":        "greater_than",
      "thresholdStatus": "pass",
      "diff":            0.07,
      "improvements":    5,
      "regressions":     2,
      "source":          "EVAL",
      "version":         3,
      "versionLabel":    "v3-prompt-tuned"
    },
    {
      "name":            "verdict",
      "dataType":        "CATEGORICAL",
      "score":           null,
      "categoryCounts":  { "pass": 8, "fail": 2, "skip": 0 },
      "passCategories":  ["pass"],
      "ignoreCategories":["skip"],
      "totalItems":      10,
      "threshold":       null,
      "criteria":        null,
      "thresholdStatus": "pass",
      "diff":            null,
      "improvements":    3,
      "regressions":     1,
      "source":          "EVAL"
    }
  ],

  "metrics": {
    "duration":          { "name": "duration",          "metric": 0.632,    "unit": "s",   "diff": -0.198 },
    "latency_avg":       { "name": "latency_avg",        "metric": 412,      "unit": "ms",  "diff": -85    },
    "latency_p50":       { "name": "latency_p50",        "metric": 380,      "unit": "ms",  "diff": -70    },
    "latency_p90":       { "name": "latency_p90",        "metric": 720,      "unit": "ms",  "diff": -120   },
    "latency_p99":       { "name": "latency_p99",        "metric": 1240,     "unit": "ms",  "diff": -200   },
    "prompt_tokens":     { "name": "prompt_tokens",      "metric": 14,       "unit": "tok", "diff": 0      },
    "completion_tokens": { "name": "completion_tokens",  "metric": 12,       "unit": "tok", "diff": -4     },
    "total_tokens":      { "name": "total_tokens",       "metric": 26,       "unit": "tok", "diff": -4     },
    "total_cost":        { "name": "total_cost",         "metric": 0.000074, "unit": "$",   "diff": -0.00002 },
    "cost_per_call":     { "name": "cost_per_call",      "metric": 0.000037, "unit": "$",   "diff": -0.00001 }
  },

  "summary": {
    "verdict":        "PASS",
    "failureCount":   0,
    "improvedCount":  6,
    "regressedCount": 2
  }
}

Score fields

scores is an array of score summaries, ordered by the server with the worst-regressed evaluators first (sort key: regressions desc, improvements desc, name asc). Each entry contains one of two shapes, discriminated by dataType. Look an evaluator up by iterating, e.g. scores.find(s => s.name === "exact_match").

Numeric score (dataType: "NUMERIC")

FieldTypeDescription
namestringEvaluator name
dataType"NUMERIC"Discriminator
scorenumber | nullMean score across all dataset items
minScorenumberMinimum score (when available)
maxScorenumberMaximum score (when available)
p90Scorenumber90th-percentile score (when available)
passCountnumberItems meeting the threshold
failCountnumberItems below the threshold
totalItemsnumberTotal dataset items scored
thresholdnumber | nullConfigured pass threshold
criteriastring | nullThreshold direction (e.g. "greater_than")
thresholdStatus"pass" | "fail" | nullWhether the mean score meets the threshold
diffnumber | nullMean score change vs baseline (null when no baseline)
improvementsnumberItems that moved toward passing vs baseline
regressionsnumberItems that moved away from passing vs baseline
source"EVAL" | "ANNOTATION"Score origin
versionnumberEvaluator version (when available)
versionLabelstringEvaluator version label (when available)

Note: minScore, maxScore, and p90Score are present when the server has per-item distribution data for the run. They may be absent in v1 for some evaluator configurations.

Categorical score (dataType: "CATEGORICAL")

FieldTypeDescription
namestringEvaluator name
dataType"CATEGORICAL"Discriminator
scorenullAlways null for categorical scores
categoryCountsobjectMap of category label to item count
passCategoriesstring[]Which categories count as passing
ignoreCategoriesstring[]Categories excluded from pass/fail accounting
neutralCountnumberItems in ignored categories (when available)
totalItemsnumberTotal dataset items scored
thresholdStatus"pass" | "fail" | nullWhether the run meets its categorical threshold
diffnullCategorical diff is not computed in v1
improvementsnumberItems that moved into a pass category vs baseline
regressionsnumberItems that moved out of a pass category vs baseline
source"EVAL" | "ANNOTATION"Score origin

Note: neutralCount is present when the server has per-item categorical classification data. It may be absent in v1 for some evaluator configurations.

Metric fields

Each entry in metrics is keyed by metric name and contains:

FieldTypeDescription
namestringMetric name
metricnumberAggregate value for this run
unitstringUnit code: s, ms, tok, $, or ""
diffnumber | nullChange vs baseline run (null when no baseline)

Metrics are emitted only when the underlying data exists for the run. The full set of possible metric names is: duration, latency_avg, latency_p50, latency_p90, latency_p95, latency_p99, llm_latency_avg, llm_latency_sum, eval_latency_avg, prompt_tokens, completion_tokens, total_tokens, eval_prompt_tokens, eval_completion_tokens, total_cost, cost_per_call, eval_cost.


Typical Workflow

# 1. Create or identify a dataset
curl -X POST https://evals-api.browserstack.com/api/public/datasets \
  -u "pk-lf-...:sk-lf-..." \
  -d '{ "name": "my-dataset" }'

# 2. Upload items to the dataset (see Datasets API)

# 3. Create an evaluator list (see Evaluators API)

# 4. Create an experiment
curl -X POST https://evals-api.browserstack.com/api/public/experiments \
  -u "pk-lf-...:sk-lf-..." \
  -d '{
    "name": "my-experiment",
    "datasetId": "<dataset-id>",
    "promptId": "<prompt-id>",
    "evaluatorListId": "<eval-list-id>"
  }'

# 5. Trigger a run
curl -X POST https://evals-api.browserstack.com/api/public/experiment-runs \
  -u "pk-lf-...:sk-lf-..." \
  -d '{ "experimentId": "<experiment-id>", "name": "run-1" }'