# Environments

Your voice agent probably runs in several places: a development build, a staging copy and
production. CallQA models them as **environments**, so the same scenarios can run against
each one, on their own schedule, and you can compare them before you promote a version.

## The basics

- Every organization has one **default** environment (`production` for existing accounts).
  Runs and versions that name no environment go there: older CI jobs, older scenario
  documents. **Add development and staging** on the **Versions and environments** board
  creates the usual two in one click.
- A **scenario** is activated in the environments where it should run, each with its own
  schedule, time zone and call window (see below).
- A **run** belongs to one environment. Pick it with the selector next to **Run now**,
  or with `environment` when you trigger from the API or CI.
- A **version** (`v2.4.0`, a short sha) belongs to one environment. The same code gets one
  version per environment it reaches: **Promote** a version
  (`POST /versions/{id}/promote`) when it moves from staging to production.
- A phone number can be **reached at another number** in an environment (for example the
  staging line of the same agent). Set it on the environment page, or with
  `PUT /environments/{env}/numbers`. Both numbers must be registered and verified.

Environments are named by **slug** (`staging`), name or id everywhere in the API.

## Which scenario runs where

The **Versions and environments** board (Dashboard) shows one column per environment, headed
by the version deployed there, with the scenarios activated in it, their schedule and their
last verdict. Drag a scenario to a column, or tick scenarios in **Add scenarios to an
environment**, to activate them there.

| Task | Request | Scope |
|---|---|---|
| Environments of a scenario | `GET /scenarios/{id}/environments` | `read` |
| Activate, schedule or pause a scenario in an environment | `PUT /scenarios/{id}/environments/{env}` | `write` |
| Remove a scenario from an environment | `DELETE /scenarios/{id}/environments/{env}` | `write` |
| Scenarios of an environment | `GET /environments/{env}/scenarios` | `read` |
| Activate, pause or remove many scenarios at once | `POST /environments/{env}/scenarios` | `write` |
| The whole board (scenario x environment, versions) | `GET /matrix` | `read` |

```bash
curl -sS -X PUT "https://app.callqa.dev/api/v1/scenarios/$SCENARIO/environments/staging" \
  -H "Authorization: Bearer $CALLQA_API_KEY" -H "Content-Type: application/json" \
  -d '{"enabled": true, "schedule": "every hour", "timezone": "Europe/Paris", "call_window": "08:00-20:00"}'
```

`schedule_enabled: false` pauses the schedule without losing it, `"schedule": ""` removes it.

The same plans can live in the scenario document, so a YAML export round-trips: the top-level
`schedule`, `timezone` and `call_window` are the plan of the default environment, and
`environments` holds the other ones, keyed by slug. An environment the document does not name
is left as it is.

Environments are never created by an import (a typo would create one): a slug your
organization does not have is refused with `422 invalid_scenario`, on the entry at fault, with
the list of your environments and how to add one:

```json
{"detail": "invalid scenario", "code": "invalid_scenario", "errors": [{"loc": "environments.staging", "msg": "Unknown environment 'staging'. This organization's environments: production. Use one of them, remove this entry, or create the environment first (Dashboard > Versions and environments > + Environment, or POST /api/v1/environments), then import again."}]}
```

The example below needs `staging` and `development`: **Add development and staging** on the
board creates both.

```yaml
name: "Book appointment - staging and production"
target:
  phone: "+14155550100"
persona:
  description: "Calm customer"
goal: "Book a haircut for tomorrow 2pm"
schedule: "every day at 09:00"
timezone: "America/New_York"
environments:
  staging:
    schedule: "every hour"
    call_window: "08:00-20:00 America/New_York"
  development:
    enabled: false
```

## From CI

```bash
curl -sS -X POST "https://app.callqa.dev/api/v1/scenarios/$SCENARIO/runs?wait=true&threshold=80" \
  -H "Authorization: Bearer $CALLQA_API_KEY" -H "Content-Type: application/json" \
  -d '{"environment": "staging", "version": "v2.4.0", "git_sha": "'"$GITHUB_SHA"'"}'
```

The version `v2.4.0` is created in `staging` if it does not exist there yet. `release` is the
former name of `version` and is still read until 2027-03-31.

## Compare and follow stability

- **Compare environments** (`GET /environments/compare?a=staging&b=production`, the
  **Compare** button of the board): KPIs, per scenario scores and the test cases whose last
  verdict differs. Add `a_version` / `b_version` (version ids) to compare two versions, and
  `scenario_id` to focus on one scenario. Growth plan and above.
- **Stability per environment** (`GET /environments/stability?days=30`, and a table on the
  dashboard): tests, pass rate, average score, p95 latency, flaky test cases (both passed and
  failed in the period) and last run. Growth plan and above.
- Filter lists by environment: `GET /runs?environment=staging`, `/calls?environment=...`,
  `/versions?environment=...`, and `?env=staging` on the dashboard, runs, calls and versions
  pages.
- Webhook endpoints can be restricted to some environments: see [Webhooks](webhooks.md).

## Rules

- Deleting an environment that has runs or versions is refused (`409 environment_in_use`):
  their history stays readable. The default environment cannot be deleted
  (`409 default_required`); make another one the default first.
- Roles: everyone reads environments; admins and members create and edit them, and activate
  scenarios in them; only admins delete them. API scopes: `read`, `write`, `admin`.
