---
title: "Create Experiment"
method: POST
path: "/api/v2/experiments/"
tags: ["experiments"]
---

# Create Experiment

`POST /api/v2/experiments/`

Create an experiment. Two equally-supported modes, distinguished
by the payload shape:

1. **Draft mode** — client sends just ``name`` (plus optional
   description). Row lands in ``status=draft`` and stays there
   until the client fills in the rest via PATCH and triggers
   execution via ``POST /api/v2/experiments/{id}/runs/``.

2. **Create-and-run mode** — client sends ``dataset`` + a
   non-empty ``workflow`` (plus evaluators, config, etc.). After
   the row is created we dispatch the Celery workflow task
   directly, matching the pattern in
   ``dataset/views.py::DatasetsView.post()``. Dispatch failures
   leave the row with ``status=failed`` (via
   ``dispatch_experiment_run``), so the client can re-run in
   place; the failure is also reflected in the HTTP response.

Body mutations inline per convention (no helper methods):

- ``created_by`` injected from ``request.user``. Organization fields
  are injected automatically by ``SuperAdminMixin.post()`` via
  ``inject_target_organization``.
- Alias/default transforms (experiment_id → id, dataset_id →
  dataset, default id + name) live in
  ``ExperimentV2CreateSerializer`` per serializer conventions.

## Headers

- `Authorization` string, required

## Request body

- ExperimentV2CreateRequest — Write serializer for POST /api/v2/experiments/. Two equally-valid create modes, distinguished by payload shape: - **Draft mode**: send just `name` (plus optional description). Row lands in DRAFT; the client fills in the rest via PATCH and triggers execution via POST /api/v2/experiments/{id}/runs/. - **Create-and-run mode**: send `dataset` + non-empty `workflow` + evaluators + config. The view dispatches the Celery workflow task directly after ``super().post()`` returns (same pattern as ``DatasetsView.post()`` in ``dataset/views.py``). Transforms owned here (no view-side body mutations): - `experiment_id` → `id` alias (legacy payload shape) - `dataset_id` → `dataset` alias (legacy payload shape) - `evaluator_ids` → `evaluator_slugs` alias (via ``ExperimentV2WriteMixin``) - Workflow step type canonicalization (via ``ExperimentV2WriteMixin``) - Default id (falls back to ``generate_unique_id()``) - Default name (``f"Experiment {id[:8]}"``) Server-injected by the view (ownership, not transforms): - `created_by` — view sets ``request.data["created_by"] = request.user.id`` - `organization`, `unique_organization_id`, `project`, `project_id` — auto-injected by ``SuperAdminMixin.post()`` via ``inject_target_organization``.
  - `id` string
  - `name` string
  - `description` string
  - `dataset` string, nullable
  - `workflow` unknown
  - `evaluator_slugs` string[] — List of evaluators to run for producing scores for comparison
  - `evaluator_workflow_ids` string[] — List of WorkflowVersion IDs (eval_only) defining evaluator pipelines. Mutually exclusive with evaluator_slugs.
  - `batch_size` integer
  - `concurrency` integer
  - `enable_tracing` boolean
  - `organization` integer
  - `project` string, nullable
  - `unique_organization_id` string

## Response `201`

- ExperimentV2Create — Write serializer for POST /api/v2/experiments/. Two equally-valid create modes, distinguished by payload shape: - **Draft mode**: send just `name` (plus optional description). Row lands in DRAFT; the client fills in the rest via PATCH and triggers execution via POST /api/v2/experiments/{id}/runs/. - **Create-and-run mode**: send `dataset` + non-empty `workflow` + evaluators + config. The view dispatches the Celery workflow task directly after ``super().post()`` returns (same pattern as ``DatasetsView.post()`` in ``dataset/views.py``). Transforms owned here (no view-side body mutations): - `experiment_id` → `id` alias (legacy payload shape) - `dataset_id` → `dataset` alias (legacy payload shape) - `evaluator_ids` → `evaluator_slugs` alias (via ``ExperimentV2WriteMixin``) - Workflow step type canonicalization (via ``ExperimentV2WriteMixin``) - Default id (falls back to ``generate_unique_id()``) - Default name (``f"Experiment {id[:8]}"``) Server-injected by the view (ownership, not transforms): - `created_by` — view sets ``request.data["created_by"] = request.user.id`` - `organization`, `unique_organization_id`, `project`, `project_id` — auto-injected by ``SuperAdminMixin.post()`` via ``inject_target_organization``.
  - `id` string
  - `name` string
  - `description` string
  - `dataset` string, nullable
  - `workflow` unknown
  - `evaluator_slugs` string[] — List of evaluators to run for producing scores for comparison
  - `evaluator_workflow_ids` string[] — List of WorkflowVersion IDs (eval_only) defining evaluator pipelines. Mutually exclusive with evaluator_slugs.
  - `batch_size` integer
  - `concurrency` integer
  - `enable_tracing` boolean
  - `organization` integer
  - `project` string, nullable
  - `unique_organization_id` string
  - `created_by` integer, nullable, required

---

[API](https://skmtc.net/keywordsai/apis/api-reference.md) · [All operations](https://skmtc.net/keywordsai/apis/api-reference/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/keywordsai/api-reference/versions/c26d550029f8/schema)
