{
  "openapi": "3.1.0",
  "info": {
    "title": "Otter API",
    "version": "1.0.0",
    "summary": "Create and run A/B tests, and read their results, from anything that can make an HTTP request.",
    "description": "The Otter REST API manages projects, experiments, and experiment results.\n\n## Authentication\n\nEvery request needs an API key in the `Authorization` header:\n\n```\nAuthorization: Bearer oab_live_...\n```\n\nKeys are created in the dashboard under Settings → API keys, or through\n`POST /api/v1/account/api_keys` with an existing account-wide key. A key\ncarries a set of scopes and, optionally, a list of project ids that\nrestrict it to those projects.\n\n## Reading results\n\nResults carry a resolved statistical contract. Read `score_label`,\n`score_value`, and `effective_confidence_threshold` rather than deriving\nsignificance from a raw confidence number: the threshold is adjusted for\nthe number of variants, and Bayesian experiments are judged on chance to\nbeat rather than on `1 - p`.\n\n## Idempotency\n\nSend an `Idempotency-Key` header on `POST`, `PATCH`, and `DELETE` requests\nto make a retry safe. Replaying the same key with the same request returns\nthe original response; replaying it with a different request is a `409`.\n\n## Rate limits\n\nLimits are per account and per minute; the account's limit is on\n`GET /api/v1/account`. Exceeding it returns `429` with an error code of\n`rate_limited`.\n\n## Errors\n\nEvery non-2xx response uses the same envelope, with a stable `code` to\nbranch on and a `request_id` to quote when reporting a problem.\n",
    "contact": {
      "name": "Otter support",
      "email": "support@otterab.com",
      "url": "https://www.otterab.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.otterab.com/terms"
    },
    "termsOfService": "https://www.otterab.com/terms"
  },
  "servers": [
    {
      "url": "https://www.otterab.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Projects",
      "description": "Sites and apps that experiments run on."
    },
    {
      "name": "Experiments",
      "description": "Creating, configuring, and driving the lifecycle of experiments."
    },
    {
      "name": "Results",
      "description": "Experiment results and the resolved statistical contract."
    },
    {
      "name": "Account",
      "description": "Account details, members, and plan usage."
    },
    {
      "name": "API keys",
      "description": "Issuing and revoking API keys."
    },
    {
      "name": "Capabilities",
      "description": "Enumerations an agent needs before composing a valid experiment payload."
    }
  ],
  "paths": {
    "/api/v1/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "List projects",
        "description": "Returns the projects this API key can reach, most recently updated first. A project-restricted key sees only its own projects.\n\nRequired scope: `projects:read`.",
        "tags": [
          "Projects"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "A page of projects.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Rows per page. Values above 100 are clamped to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ]
      },
      "post": {
        "operationId": "createProject",
        "summary": "Create a project",
        "description": "Creates a project and returns it with the API key the SDK snippet needs. Requires an account-wide key: project-restricted keys cannot create projects. Returns 422 with code `plan_limit_reached` when the plan's project allowance is used up.\n\nRequired scope: `projects:write`.",
        "tags": [
          "Projects"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "201": {
            "description": "The created project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectInput"
              }
            }
          }
        }
      }
    },
    "/api/v1/projects/{id}": {
      "get": {
        "operationId": "getProject",
        "summary": "Get a project",
        "description": "Returns one project.\n\nRequired scope: `projects:read`.",
        "tags": [
          "Projects"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "The project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id.",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "patch": {
        "operationId": "updateProject",
        "summary": "Update a project",
        "description": "Updates the supplied attributes and leaves the rest untouched.\n\nRequired scope: `projects:write`.",
        "tags": [
          "Projects"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The updated project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectInput"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteProject",
        "summary": "Delete a project",
        "description": "Permanently deletes the project and everything under it, including experiments and their collected results. There is no undo.\n\nRequired scope: `projects:write`.",
        "tags": [
          "Projects"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "204": {
            "description": "Deleted."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/projects/{id}/stats": {
      "get": {
        "operationId": "getProjectStats",
        "summary": "Get project stats",
        "description": "Aggregate counters for the project: experiments by status, visitors, and conversions.\n\nRequired scope: `projects:read`.",
        "tags": [
          "Projects"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "Project statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProjectStats"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/v1/projects/{id}/installation": {
      "get": {
        "operationId": "getProjectInstallation",
        "summary": "Get installation instructions",
        "description": "Returns the SDK snippet for this project, its API key, and the platform-specific steps for installing it. Use this rather than assembling the snippet by hand.\n\nRequired scope: `projects:read`.",
        "tags": [
          "Projects"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "Installation payload.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProjectInstallation"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments": {
      "get": {
        "operationId": "listExperiments",
        "summary": "List experiments",
        "description": "Returns the project's experiments, most recently updated first.\n\nRequired scope: `experiments:read`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "A page of experiments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Experiment"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Return only experiments in this status.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "running",
                "paused",
                "completed",
                "archived"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Rows per page. Values above 100 are clamped to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ]
      },
      "post": {
        "operationId": "createExperiment",
        "summary": "Create an experiment",
        "description": "Creates a draft experiment with its variants and goals. Drafts do not receive traffic until started. Call GET /api/v1/capabilities/* first if you need the valid enumerations for goals, experiment types, visual change actions, or targeting fields.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "201": {
            "description": "The created experiment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExperimentInput"
              }
            }
          }
        }
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}": {
      "get": {
        "operationId": "getExperiment",
        "summary": "Get an experiment",
        "description": "Returns one experiment with its variants, goals, and resolved statistical contract.\n\nRequired scope: `experiments:read`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "The experiment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "patch": {
        "operationId": "updateExperiment",
        "summary": "Update an experiment",
        "description": "Updates a draft or paused experiment. A running experiment must be paused first — editing one mid-flight would invalidate the data already collected, so the API returns 422 instead.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The updated experiment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExperimentInput"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteExperiment",
        "summary": "Delete an experiment",
        "description": "Deletes a draft experiment. Experiments that have collected data cannot be deleted — complete or archive them instead, which returns 422 with code `not_draft`.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "204": {
            "description": "Deleted."
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/start": {
      "post": {
        "operationId": "startExperiment",
        "summary": "Start an experiment",
        "description": "Launches a draft or scheduled experiment. Visitors begin being assigned immediately. Returns 422 when the experiment's current status does not allow this transition.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The experiment in its new status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/pause": {
      "post": {
        "operationId": "pauseExperiment",
        "summary": "Pause an experiment",
        "description": "Stops assigning new visitors. Already-assigned visitors keep their variant, and the configuration becomes editable. Returns 422 when the experiment's current status does not allow this transition.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The experiment in its new status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/resume": {
      "post": {
        "operationId": "resumeExperiment",
        "summary": "Resume an experiment",
        "description": "Resumes a paused experiment without resetting the data collected so far. Returns 422 when the experiment's current status does not allow this transition.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The experiment in its new status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/complete": {
      "post": {
        "operationId": "completeExperiment",
        "summary": "Complete an experiment",
        "description": "Ends the experiment and freezes its results. Completed experiments cannot be resumed. Returns 422 when the experiment's current status does not allow this transition.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The experiment in its new status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/archive": {
      "post": {
        "operationId": "archiveExperiment",
        "summary": "Archive an experiment",
        "description": "Hides a completed experiment from the default listing. Results are retained. Returns 422 when the experiment's current status does not allow this transition.\n\nRequired scope: `experiments:write`.",
        "tags": [
          "Experiments"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The experiment in its new status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentDetail"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/results": {
      "get": {
        "operationId": "getExperimentResults",
        "summary": "Get full experiment results",
        "description": "Per-goal, per-variant metrics with the resolved statistical contract. Read `score_label` and `effective_confidence_threshold` from the payload rather than assuming a 95% confidence story: the threshold is adjusted for the number of variants, and Bayesian experiments report chance to beat.\n\nRequired scope: `results:read`.",
        "tags": [
          "Results"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "Full results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentResults"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "traffic_source",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "device_type",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visitor_type",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "day_of_week",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "os",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "browser",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_source",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_medium",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_campaign",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visitor_identity",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "custom_dimension_key",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "custom_dimension_value",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/results/summary": {
      "get": {
        "operationId": "getExperimentResultsSummary",
        "summary": "Get the primary-goal summary",
        "description": "The decision-ready view: primary goal only, with score, lift, and winner status. Prefer this over the full results payload when you only need to answer \"has this experiment decided yet?\". Read `score_label` and `effective_confidence_threshold` from the payload rather than assuming a 95% confidence story: the threshold is adjusted for the number of variants, and Bayesian experiments report chance to beat.\n\nRequired scope: `results:read`.",
        "tags": [
          "Results"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "Primary-goal summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentResultsSummary"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "traffic_source",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "device_type",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visitor_type",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "day_of_week",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "os",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "browser",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_source",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_medium",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_campaign",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visitor_identity",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "custom_dimension_key",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "custom_dimension_value",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/v1/projects/{project_id}/experiments/{id}/results/timeseries": {
      "get": {
        "operationId": "getExperimentResultsTimeseries",
        "summary": "Get results over time",
        "description": "Daily visitors and conversions per variant, bucketed in the project's reporting timezone.\n\nRequired scope: `results:read`.",
        "tags": [
          "Results"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "200": {
            "description": "Daily series.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ExperimentResultsTimeseries"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "Project the experiment belongs to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Experiment id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "traffic_source",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "device_type",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visitor_type",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "day_of_week",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "os",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "browser",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_source",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_medium",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_campaign",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visitor_identity",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "custom_dimension_key",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "custom_dimension_value",
            "in": "query",
            "required": false,
            "description": "Restrict the figures to this segment of visitors.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "goal_id",
            "in": "query",
            "required": false,
            "description": "Return the series for one goal instead of the primary goal.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "Get the current account",
        "description": "Returns the account the API key belongs to, including its plan and its per-minute rate limit.\n\nRequired scope: `account:read`.",
        "tags": [
          "Account"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/account/members": {
      "get": {
        "operationId": "listAccountMembers",
        "summary": "List account members",
        "description": "Returns accepted members of the account. These ids are what `assigned_user_ids` on an experiment expects.\n\nRequired scope: `account:read`.",
        "tags": [
          "Account"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "Accepted members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Member"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/account/usage": {
      "get": {
        "operationId": "getAccountUsage",
        "summary": "Get plan usage",
        "description": "Project allowance, projects used, and the API rate limit. Check this before creating a project to avoid a `plan_limit_reached` error.\n\nRequired scope: `account:read`.",
        "tags": [
          "Account"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "Plan usage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AccountUsage"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/account/api_keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "description": "Returns the account's API keys, newest first. Secrets are never included. Requires an account-wide key held by an owner or admin; project-restricted keys are refused.\n\nRequired scope: `api_keys:write`.",
        "tags": [
          "API keys"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "The account's API keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "summary": "Create an API key",
        "description": "Issues a new key and returns its secret. The secret is shown once and cannot be retrieved afterwards. Requires an account-wide key held by an owner or admin; project-restricted keys are refused.\n\nRequired scope: `api_keys:write`.",
        "tags": [
          "API keys"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "201": {
            "description": "The issued key and its secret.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IssuedApiKey"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiKeyInput"
              }
            }
          }
        }
      }
    },
    "/api/v1/account/api_keys/{id}/revoke": {
      "post": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key",
        "description": "Revokes a key immediately. In-flight requests using it start failing with `invalid_api_key`. Requires an account-wide key held by an owner or admin; project-restricted keys are refused.\n\nRequired scope: `api_keys:write`.",
        "tags": [
          "API keys"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "200": {
            "description": "The revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "api_key": {
                          "$ref": "#/components/schemas/ApiKey"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "API key id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/v1/capabilities/targeting_fields": {
      "get": {
        "operationId": "listTargetingFields",
        "summary": "List targeting fields",
        "description": "JSON Schema for the targeting fields an experiment's `targeting_rules` may reference, with the operators each field accepts. Fetch this before composing an experiment payload rather than hardcoding the values — they change as the product gains capabilities.\n\nRequired scope: `account:read` or `experiments:read`.",
        "tags": [
          "Capabilities"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "List targeting fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/capabilities/goal_types": {
      "get": {
        "operationId": "listGoalTypes",
        "summary": "List goal types",
        "description": "The goal types a goal may use. Fetch this before composing an experiment payload rather than hardcoding the values — they change as the product gains capabilities.\n\nRequired scope: `account:read` or `experiments:read`.",
        "tags": [
          "Capabilities"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "List goal types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "pageview",
                          "click",
                          "custom_event",
                          "revenue",
                          "ga4_event"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/capabilities/experiment_types": {
      "get": {
        "operationId": "listExperimentTypes",
        "summary": "List experiment types",
        "description": "The experiment types the `type` field accepts. Fetch this before composing an experiment payload rather than hardcoding the values — they change as the product gains capabilities.\n\nRequired scope: `account:read` or `experiments:read`.",
        "tags": [
          "Capabilities"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "List experiment types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "redirect",
                          "visual",
                          "personalization"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/capabilities/visual_change_actions": {
      "get": {
        "operationId": "listVisualChangeActions",
        "summary": "List visual change actions",
        "description": "The DOM operations a visual experiment's changes may use. Fetch this before composing an experiment payload rather than hardcoding the values — they change as the product gains capabilities.\n\nRequired scope: `account:read` or `experiments:read`.",
        "tags": [
          "Capabilities"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "List visual change actions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "modify_text",
                          "modify_html",
                          "modify_attribute",
                          "modify_style",
                          "modify_class",
                          "remove",
                          "insert_before",
                          "insert_after",
                          "insert_inside",
                          "replace"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/capabilities/lifecycle": {
      "get": {
        "operationId": "listLifecycleTransitions",
        "summary": "List lifecycle transitions",
        "description": "The lifecycle transitions an experiment supports, each available as POST .../experiments/{id}/{transition}. Fetch this before composing an experiment payload rather than hardcoding the values — they change as the product gains capabilities.\n\nRequired scope: `account:read` or `experiments:read`.",
        "tags": [
          "Capabilities"
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "200": {
            "description": "List lifecycle transitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "start",
                          "pause",
                          "resume",
                          "complete",
                          "archive"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An Otter API key, sent as `Authorization: Bearer oab_live_...`."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Makes a retry of this write safe. Replaying the same key with the same request returns the original response.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The API key is missing, malformed, or revoked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The account has no active subscription.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key lacks a required scope, or is project-restricted and cannot reach this resource.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource, or the key cannot reach it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The request was well-formed but could not be applied. `details` lists the offending fields.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "An Idempotency-Key conflict: the same key is in flight, or was already used for a different request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "The account's per-minute request limit was exceeded.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every non-2xx response from this API uses this envelope.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code. Branch on this, not on the message.",
                "enum": [
                  "invalid_api_key",
                  "account_inactive",
                  "forbidden",
                  "not_found",
                  "validation_error",
                  "parameter_missing",
                  "unprocessable_entity",
                  "plan_limit_reached",
                  "not_draft",
                  "rate_limited",
                  "idempotency_in_progress",
                  "idempotency_key_reused"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. May change between releases."
              },
              "details": {
                "description": "Present on validation errors (a list of field/message pairs) and on rate limits (the applicable limit).",
                "nullable": true
              },
              "request_id": {
                "type": "string",
                "description": "Echoes the request id. Quote it when reporting a problem."
              }
            }
          }
        }
      },
      "PaginationMeta": {
        "type": "object",
        "required": [
          "page",
          "per_page",
          "total"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "per_page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "total": {
            "type": "integer",
            "description": "Total rows matching the query, across all pages."
          }
        }
      },
      "Project": {
        "type": "object",
        "description": "A site or app that Otter runs experiments on. Experiments always belong to a project.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Project identifier, serialized as a string."
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Primary site URL. Used to validate experiment target URLs."
          },
          "platform": {
            "type": "string",
            "enum": [
              "custom_js",
              "shopify"
            ]
          },
          "installation_guide": {
            "type": "string",
            "nullable": true,
            "description": "Which setup guide the dashboard shows for this project."
          },
          "reporting_timezone": {
            "type": "string",
            "description": "IANA timezone that day boundaries in reporting are calculated in."
          },
          "revenue_currency": {
            "type": "string",
            "description": "ISO 4217 code that revenue metrics are reported in."
          },
          "integrations": {
            "type": "object",
            "additionalProperties": true,
            "description": "Per-integration state, keyed by integration name (ga4, gtm)."
          },
          "environments": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "settings": {
            "type": "object",
            "additionalProperties": true
          },
          "consent_mode": {
            "type": "string",
            "enum": [
              "opt_out",
              "opt_in"
            ],
            "description": "opt_out tracks until a visitor objects; opt_in withholds tracking until consent is granted."
          },
          "consent_region_scope": {
            "type": "string",
            "enum": [
              "global",
              "eu_uk"
            ],
            "description": "Whether the consent mode applies worldwide or only to EU/UK visitors."
          },
          "anonymize_ip": {
            "type": "boolean"
          },
          "data_retention_days": {
            "type": "integer",
            "nullable": true,
            "description": "Visitor-level data older than this is anonymized. Null means the account default."
          },
          "snippet_verified": {
            "type": "boolean",
            "description": "True once Otter has seen the SDK snippet execute on this site."
          },
          "running_experiments_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProjectInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Primary site URL. Used to validate experiment target URLs."
          },
          "platform": {
            "type": "string",
            "enum": [
              "custom_js",
              "shopify"
            ]
          },
          "installation_guide": {
            "type": "string",
            "nullable": true,
            "description": "Which setup guide the dashboard shows for this project."
          },
          "reporting_timezone": {
            "type": "string",
            "description": "IANA timezone that day boundaries in reporting are calculated in."
          },
          "revenue_currency": {
            "type": "string",
            "description": "ISO 4217 code that revenue metrics are reported in."
          },
          "settings": {
            "type": "object",
            "additionalProperties": true
          },
          "environments": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "consent_mode": {
            "type": "string",
            "enum": [
              "opt_out",
              "opt_in"
            ],
            "description": "opt_out tracks until a visitor objects; opt_in withholds tracking until consent is granted."
          },
          "consent_region_scope": {
            "type": "string",
            "enum": [
              "global",
              "eu_uk"
            ],
            "description": "Whether the consent mode applies worldwide or only to EU/UK visitors."
          },
          "anonymize_ip": {
            "type": "boolean"
          },
          "data_retention_days": {
            "type": "integer",
            "nullable": true,
            "description": "Visitor-level data older than this is anonymized. Null means the account default."
          }
        }
      },
      "ProjectStats": {
        "type": "object",
        "description": "Aggregate counters for a project. Shape follows Projects::StatsQuery.",
        "additionalProperties": true
      },
      "ProjectInstallation": {
        "type": "object",
        "description": "Everything needed to install the SDK on this project: the snippet, the API key, and platform-specific steps.",
        "additionalProperties": true
      },
      "Experiment": {
        "type": "object",
        "description": "An experiment (A/B, A/B/n, or split URL test) belonging to a project.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "test_key": {
            "type": "string",
            "description": "Stable key the SDK uses to identify this experiment on the page."
          },
          "type": {
            "type": "string",
            "enum": [
              "redirect",
              "visual",
              "personalization"
            ],
            "description": "redirect splits traffic across URLs; visual applies DOM changes; personalization targets a segment without a control split."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "running",
              "paused",
              "completed",
              "archived"
            ]
          },
          "url": {
            "type": "string",
            "description": "Target URL the experiment runs on."
          },
          "url_match_type": {
            "type": "string",
            "enum": [
              "exact",
              "simple",
              "contains",
              "regex",
              "wildcard"
            ]
          },
          "url_rules": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Additional include/exclude URL rules evaluated alongside url and url_match_type."
          },
          "traffic_allocation": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of eligible visitors entered into the experiment."
          },
          "trigger": {
            "type": "string",
            "enum": [
              "direct",
              "url_change",
              "dom_change",
              "wait_for",
              "manual"
            ],
            "description": "When the SDK activates the experiment on the page."
          },
          "trigger_selector": {
            "type": "string",
            "nullable": true,
            "description": "CSS selector the trigger waits for, when the trigger needs one."
          },
          "custom_css": {
            "type": "string",
            "nullable": true
          },
          "custom_js": {
            "type": "string",
            "nullable": true
          },
          "relay_params": {
            "type": "boolean",
            "description": "Merge the visitor's current query params and hash into the redirect destination."
          },
          "hypothesis": {
            "type": "string",
            "nullable": true
          },
          "scheduled_start_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "scheduled_end_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "stop_conditions": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Automatic stop rules (duration, visitor cap, significance)."
          },
          "targeting_rules": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Audience rules. Field names come from GET /api/v1/capabilities/targeting_fields."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "confidence_level": {
            "type": "number",
            "description": "Configured confidence level for this experiment, as a percentage."
          },
          "analysis_method": {
            "type": "string",
            "description": "frequentist or bayesian. Decides which score the experiment is judged on."
          },
          "score_label": {
            "type": "string",
            "description": "Name of the decision score this experiment is judged on, resolved server-side (for example \"Significance (1-p)\" or \"Chance to Beat\"). Render this label rather than assuming a confidence percentage."
          },
          "score_description": {
            "type": "string",
            "description": "One-line explanation of the score, safe to show to end users."
          },
          "effective_confidence_threshold": {
            "type": "number",
            "description": "The threshold the score must clear for this experiment, already adjusted for multiple variants. Compare against this, not against a hardcoded 95."
          },
          "traffic_strategy": {
            "type": "string",
            "description": "How new visitors are divided between variants."
          },
          "traffic_strategy_label": {
            "type": "string"
          },
          "traffic_strategy_description": {
            "type": "string"
          },
          "traffic_strategy_note": {
            "type": "string",
            "nullable": true
          },
          "variant_traffic_label": {
            "type": "string",
            "description": "What the per-variant traffic number means under the active strategy."
          },
          "bandit_allocation": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Present only when traffic_strategy is a bandit: the current allocation and how it was derived."
          }
        }
      },
      "ExperimentDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Experiment"
          },
          {
            "type": "object",
            "description": "Returned by single-experiment reads and by every write. Adds the nested configuration.",
            "properties": {
              "variants": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Variant"
                }
              },
              "goals": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Goal"
                }
              },
              "assigned_user_ids": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        ]
      },
      "ExperimentInput": {
        "type": "object",
        "description": "Experiments are created as drafts. Once running, an experiment must be paused before its configuration can be changed.",
        "required": [
          "name",
          "type"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "redirect",
              "visual",
              "personalization"
            ],
            "description": "redirect splits traffic across URLs; visual applies DOM changes; personalization targets a segment without a control split."
          },
          "url": {
            "type": "string",
            "description": "Target URL the experiment runs on."
          },
          "url_match_type": {
            "type": "string",
            "enum": [
              "exact",
              "simple",
              "contains",
              "regex",
              "wildcard"
            ]
          },
          "url_rules": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Additional include/exclude URL rules evaluated alongside url and url_match_type."
          },
          "traffic_allocation": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of eligible visitors entered into the experiment."
          },
          "trigger": {
            "type": "string",
            "enum": [
              "direct",
              "url_change",
              "dom_change",
              "wait_for",
              "manual"
            ],
            "description": "When the SDK activates the experiment on the page."
          },
          "trigger_selector": {
            "type": "string",
            "nullable": true,
            "description": "CSS selector the trigger waits for, when the trigger needs one."
          },
          "custom_css": {
            "type": "string",
            "nullable": true
          },
          "custom_js": {
            "type": "string",
            "nullable": true
          },
          "relay_params": {
            "type": "boolean",
            "description": "Merge the visitor's current query params and hash into the redirect destination."
          },
          "hypothesis": {
            "type": "string",
            "nullable": true
          },
          "scheduled_start_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "scheduled_end_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "stop_conditions": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Automatic stop rules (duration, visitor cap, significance)."
          },
          "targeting_rules": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Audience rules. Field names come from GET /api/v1/capabilities/targeting_fields."
          },
          "variants": {
            "type": "array",
            "description": "Replaces the full variant list. Omit to leave the existing variants untouched.",
            "items": {
              "$ref": "#/components/schemas/Variant"
            }
          },
          "goals": {
            "type": "array",
            "description": "Replaces the full goal list. Omit to leave the existing goals untouched.",
            "items": {
              "$ref": "#/components/schemas/Goal"
            }
          },
          "assigned_user_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Variant": {
        "type": "object",
        "description": "One arm of an experiment. Exactly one variant is the control.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "is_control": {
            "type": "boolean"
          },
          "traffic_percentage": {
            "type": "number",
            "description": "Share of experiment traffic assigned to this variant."
          },
          "url": {
            "type": "string",
            "nullable": true,
            "description": "Destination for redirect experiments."
          },
          "changes": {
            "type": "array",
            "description": "DOM changes applied by visual experiments.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "modify_text",
                    "modify_html",
                    "modify_attribute",
                    "modify_style",
                    "modify_class",
                    "remove",
                    "insert_before",
                    "insert_after",
                    "insert_inside",
                    "replace"
                  ]
                },
                "selector": {
                  "type": "string"
                },
                "value": {
                  "nullable": true
                }
              },
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "Goal": {
        "type": "object",
        "description": "A conversion goal. One goal per experiment is primary and decides the winner.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "goal_type": {
            "type": "string",
            "enum": [
              "pageview",
              "click",
              "custom_event",
              "revenue",
              "ga4_event"
            ]
          },
          "is_primary": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      },
      "ExperimentResults": {
        "type": "object",
        "description": "Full results payload: per-goal, per-variant metrics plus the resolved statistical contract. Read score_label and effective_confidence_threshold rather than deriving significance from a raw confidence number.",
        "additionalProperties": true
      },
      "ExperimentResultsSummary": {
        "type": "object",
        "description": "Primary-goal summary only — the decision-ready view. Cheaper than the full results payload.",
        "additionalProperties": true
      },
      "ExperimentResultsTimeseries": {
        "type": "object",
        "description": "Daily series of visitors and conversions per variant, in the project's reporting timezone.",
        "additionalProperties": true
      },
      "Account": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "reporting_timezone": {
            "type": "string"
          },
          "plan": {
            "type": "string",
            "nullable": true,
            "description": "Stripe plan identifier."
          },
          "plan_name": {
            "type": "string",
            "nullable": true
          },
          "subscription_status": {
            "type": "string",
            "nullable": true
          },
          "rate_limit_per_minute": {
            "type": "integer",
            "description": "Requests per minute this account may make to this API."
          }
        }
      },
      "AccountUsage": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "nullable": true
          },
          "plan_name": {
            "type": "string",
            "nullable": true
          },
          "projects_count": {
            "type": "integer"
          },
          "projects_remaining": {
            "type": "integer",
            "nullable": true,
            "description": "Null on plans with no project limit."
          },
          "max_projects": {
            "type": "integer",
            "nullable": true,
            "description": "Null on plans with no project limit."
          },
          "rate_limit_per_minute": {
            "type": "integer"
          }
        }
      },
      "Member": {
        "type": "object",
        "description": "An accepted member of the account. Use these ids for assigned_user_ids on an experiment.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "ApiKey": {
        "type": "object",
        "description": "API key metadata. The secret itself is only ever returned once, at creation.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "key_prefix": {
            "type": "string",
            "description": "Non-secret prefix, safe to display and to log."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "projects:read",
                "projects:write",
                "experiments:read",
                "experiments:write",
                "results:read",
                "account:read",
                "api_keys:write",
                "sdk:write"
              ]
            }
          },
          "project_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Empty means the key reaches every project in the account."
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revoked_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKeyInput": {
        "type": "object",
        "required": [
          "name",
          "scopes"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "default": "live"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "projects:read",
                "projects:write",
                "experiments:read",
                "experiments:write",
                "results:read",
                "account:read",
                "api_keys:write",
                "sdk:write"
              ]
            }
          },
          "project_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Omit or leave empty for an account-wide key."
          }
        }
      },
      "IssuedApiKey": {
        "type": "object",
        "description": "Returned once, at creation. The token is not retrievable afterwards.",
        "required": [
          "token",
          "api_key"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The full secret. Store it now — it is never shown again."
          },
          "api_key": {
            "$ref": "#/components/schemas/ApiKey"
          }
        }
      }
    }
  }
}