> ## Documentation Index
> Fetch the complete documentation index at: https://bilanc.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# DORA Metrics

> How Bilanc measures Deployment Frequency, Lead Time for Changes, Change Failure Rate and Time to Restore, which numbers are measured directly and which are proxies, and how to make them accurate.

Bilanc reports all four DORA metrics for every account, plus the supporting stages that explain them. Some start as proxies, or as 0, until the tools that carry the real signal are connected. This page tells you exactly what is measured, how, and what to connect to improve it.

## What Bilanc reports

<CardGroup cols={2}>
  <Card title="Deployment Frequency" icon="rocket">
    How often you ship to production. Counted from releases, deployment records or production deploy workflow runs, per repository.
  </Card>

  <Card title="Lead Time for Changes" icon="clock">
    Time from the first commit on a change to that change running in production.
  </Card>

  <Card title="Change Failure Rate" icon="triangle-exclamation">
    Share of production deployments that led to a failure: a failed deploy, an incident, or a revert or hotfix.
  </Card>

  <Card title="Time to Restore Service" icon="wrench">
    How long production stayed degraded after a failed change, until it was restored.
  </Card>
</CardGroup>

Supporting stages, shown alongside the four:

* **Merge to Deploy** – merge to running in production. The part of lead time that happens after the PR is done.
* **Merge Queue Time** – entering a GitHub merge queue to merged.
* **Deployment Success Rate** – share of production deploy runs that succeeded.
* **PRs per Deployment** – how many pull requests each deployment carried.

<Note>
  Every account sees all four metrics from day one. Deployment Frequency and Lead Time are populated as soon as version control is connected. Change Failure Rate and Time to Restore are populated from reverts, hotfixes and failed deploy runs straight away, and become incident-based once Incident.io is connected. A metric showing 0 usually means the signal is not connected yet, not that nothing happened. See [Getting accurate numbers](#getting-accurate-numbers).
</Note>

## How Bilanc detects a production deployment

Bilanc looks for production deployments per repository, using the first signal in this list that the repository emits:

<Steps>
  <Step title="Bitbucket Deployments">
    Bitbucket's own deployment records, at the production environment.
  </Step>

  <Step title="Published releases">
    GitHub releases. Drafts and pre-releases are excluded. The published time is the deploy time. GitLab releases are not ingested yet (see Coming soon).
  </Step>

  <Step title="Production deploy workflow runs">
    CI/CD runs from GitHub Actions, CircleCI, GitLab CI or Azure DevOps whose workflow name says it deploys to production, for example **Deploy to Production**, **Deploy Container to ECS (Prod)** or **Prod Continuous Deployment**. Runs triggered by pull requests, merge queues or schedules never count, whatever they are called, and names that mention a lower environment (staging, dev, qa, preview, sandbox, test) are excluded. Failed runs are kept so Deployment Success Rate and Time to Restore can use them.
  </Step>

  <Step title="Merge to the default branch">
    If a repository emits none of the above, merging to its default branch counts as a deployment. This is the "trunk is production" proxy and is labelled `merge_to_default`.
  </Step>
</Steps>

If a repository emits more than one signal, Bilanc uses the one with the most deployments in the last 180 days, with ties broken in the order above. A signal only counts while it is alive: a repository whose last release was more than 180 days ago falls back to merge-to-default instead of waiting for a release that never comes, and a deploy workflow that has not succeeded once in 180 days is ignored (it is almost always a misnamed or abandoned job, and would otherwise read as 0% success and 100% failure). Every deployment carries a `deployment_source` label (`bitbucket_deployment`, `github_release`, `workflow_run` or `merge_to_default`) so you can always see which rule applied, and group or filter by it. You can override any of this per repository, see [Rules](/pull-request-exclusion-rules#deployment-rules).

### How Bilanc reads your branching model

"Merge to the default branch" is only right for repositories that ship from trunk. Bilanc looks at a year of merged pull requests per repository and works out which branches are **production** and which are **integration** branches, then uses that everywhere below. The Repositories tab of the Rules page shows what was detected, with the evidence, and lets you override it.

| Model                             | What Bilanc saw                                                                                | Production branch     | What ships a PR into production                                                  |
| --------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------- |
| **Trunk**                         | 80%+ of merges land on `main`, `master`, `production`, `prod` or `trunk`                       | that branch           | the merge itself (no signal), or the next release / deploy run                   |
| **Gitflow**                       | most merges land on `dev` or `develop` and pull requests promote it into `main`                | `main`                | the next promotion PR, then that promotion's deployment                          |
| **Release branches**              | `release/*`, `hotfix/*` or `rc/*` branches are merged into production                          | `main`                | the next release-branch merge into production, or the next deployment            |
| **Trunk on a non-default branch** | a branch such as `develop` takes 70%+ of merges **and** a release or deploy run is cut from it | that branch           | as Trunk                                                                         |
| **Mixed**                         | none of the above fits                                                                         | default branches seen | as Trunk, low confidence; Bilanc suggests a production branch for you to confirm |
| **Insufficient data**             | fewer than 10 merged PRs in a year                                                             | default branches      | as Trunk                                                                         |

Each repository carries a confidence (high, medium, low). Anything below high is worth a glance in the Rules page; a `production_branches` rule fixes it permanently.

### How a pull request gets a deploy time

A pull request merged into a **production** branch is assumed to ship with the **next** production deployment of its repository after it merged (itself, under merge-to-default). A pull request merged into an **integration** branch (`dev`, `develop`, `release/1.2`) ships when that branch is next promoted into production, and then with the promotion's own deployment; if the repository has not promoted by pull request in the last 180 days, it ships with the next deployment instead. That is what Merge to Deploy and Lead Time for Changes measure.

Pull requests merged before Bilanc can see any deployment for that repository are left without a deploy time rather than attributed to a later deployment, so a repository's first tracked release does not inflate its lead time.

### Pull requests without a deploy time

Every merged pull request carries a `deployment_attribution_status` in Data Explorer and Data Studio, and the DORA dashboard shows the share of merged pull requests with a deploy time. The reasons a PR has none:

| Status                  | Meaning                                                                                                                                        | What to do                                                                            |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `awaiting_deploy`       | Merged, and no later deployment or promotion has happened yet. Work waiting to ship.                                                           | Nothing. Deploy.                                                                      |
| `before_first_signal`   | Merged before the repository's first visible release or deploy run. Common in the first weeks after connecting CI/CD, while history backfills. | Wait for the backfill, or nothing.                                                    |
| `non_production_branch` | Merged into a branch that is neither a production nor an integration branch (a feature branch, a personal branch).                             | Nothing, unless that branch is actually production: add a `production_branches` rule. |
| `ignored`               | The repository is excluded from deployment tracking by a rule.                                                                                 | Nothing.                                                                              |

<Note>
  Excluding a promotion PR (for example `dev` → `main`) with a PR exclusion rule removes it from Bilanc entirely, so it can no longer serve as the deployment event for the pull requests it carried. Prefer leaving promotions in and filtering on `is_promotion_pr` in Data Explorer.
</Note>

## Metric definitions

| Metric                  | What it measures                               | How it is calculated                                                                                                                                              | Table           | API name                                             |
| ----------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ---------------------------------------------------- |
| Deployment Frequency    | How often you deploy to production             | Count of distinct deployments in the period, by day, week or month                                                                                                | `deployments`   | `deployment-frequency`                               |
| Lead Time for Changes   | First commit to running in production          | `deployed_at − first commit`. Falls back to PR open time when no commit is known. Median via `lead-time-for-changes-p50`; AVG/MIN/MAX via `lead-time-for-changes` | `pull_requests` | `lead-time-for-changes-p50`, `lead-time-for-changes` |
| Change Failure Rate     | Share of deployments that led to a failure     | Failed changes ÷ deployments, excluding remediation deployments from both                                                                                         | `deployments`   | `change-failure-rate`                                |
| Time to Restore Service | How long production stayed degraded            | `restored_at − failure_detected_at`, over failed changes that have been restored. Median via `time-to-restore-p50`                                                | `deployments`   | `time-to-restore-p50`, `time-to-restore`             |
| Merge to Deploy         | Merge to running in production                 | `deployed_at − pr_merged_at`. Median via `merge-to-deploy-p50`                                                                                                    | `pull_requests` | `merge-to-deploy-p50`, `merge-to-deploy`             |
| Open to Deploy          | PR opened to running in production             | `deployed_at − pr_created_at`                                                                                                                                     | `pull_requests` | `open-to-deploy`                                     |
| Merge Queue Time        | Entering a GitHub merge queue to merged        | `pr_merged_at − queue_entered_at`                                                                                                                                 | `pull_requests` | `queue-time`                                         |
| Deployment Success Rate | Share of production deploy runs that succeeded | Successful ÷ all deployments                                                                                                                                      | `deployments`   | `deployment-success-rate`                            |
| PRs per Deployment      | Batch size of each deployment                  | Average pull requests carried per deployment                                                                                                                      | `deployments`   | `prs-per-deployment`                                 |

Time metrics are returned in seconds. Rates are returned as a fraction between 0 and 1. The dashboard shows **medians** for the time metrics: lead time and time to restore are heavy-tailed (one change that waited a month for a release will double an average), and the DORA report's own thresholds are stated as typical values.

## How confident should I be in each number?

Each card on the DORA dashboard carries one of three labels, computed from your data, and the same logic applies to the API:

| Label        | Meaning                                                                                                               |
| ------------ | --------------------------------------------------------------------------------------------------------------------- |
| **Measured** | The number comes from a direct signal: releases or deploy runs for Deployment Frequency, incidents for failures.      |
| **Partial**  | The signal is direct but incomplete, for example fewer than 80% of merged pull requests have a deploy time yet.       |
| **Proxy**    | The number is inferred. Merge-to-default counting as a deployment, or reverts and hotfixes standing in for incidents. |

What each connection unlocks:

|                                                       | Version control only                  | + Releases or named deploy workflows           | + Incident.io                         |
| ----------------------------------------------------- | ------------------------------------- | ---------------------------------------------- | ------------------------------------- |
| Deployment Frequency                                  | Proxy (merges to production branches) | **Measured**                                   | Measured                              |
| Lead Time for Changes                                 | Proxy (merge time = deploy time)      | **Measured** (Partial while history backfills) | Measured                              |
| Change Failure Rate                                   | Proxy (reverts, hotfixes)             | Proxy + failed deploy runs                     | **Measured**                          |
| Time to Restore                                       | Proxy (bad change live → fix shipped) | Proxy + failed run → next success              | **Measured** (incident open → closed) |
| Merge to Deploy, Merge Queue Time, PRs per Deployment | Proxy / n/a                           | **Measured**                                   | Measured                              |

## Performance bands

The dashboard colours each of the four metrics with the band from the 2023 State of DevOps report. They are indicative: a repository on merge-to-default will look Elite on frequency by construction, so read the band together with the confidence label.

| Band   | Deployment Frequency       | Lead Time for Changes | Change Failure Rate | Time to Restore |
| ------ | -------------------------- | --------------------- | ------------------- | --------------- |
| Elite  | On demand (at least daily) | Under a day           | 5% or less          | Under an hour   |
| High   | Between daily and weekly   | A day to a week       | 10% or less         | Under a day     |
| Medium | Between weekly and monthly | A week to a month     | 15% or less         | Under a week    |
| Low    | Less than monthly          | Over a month          | Over 15%            | Over a week     |

## Change Failure Rate and Time to Restore: how the proxies work

DORA defines a failed change as a deployment that degrades service and needs remediation. Bilanc has no direct view of your production health, so it infers failures from the signals it does have. A deployment counts as a failed change when any of these is true:

1. **The deploy run itself failed.** Only possible for the `workflow_run` source. `failure_reason = failed_deployment`.
2. **An incident opened within 48 hours** and this was the most recent deployment before it (Incident.io connected). `failure_reason = incident`.
3. **A revert names a change it shipped.** A GitHub-style `Revert "…"` pull request whose title names a pull request that shipped in this deployment, within 30 days. `failure_reason = revert_pr`.
4. **A revert, hotfix or rollback landed shortly after.** Any other PR with revert, hotfix or rollback in its title or branch, merged to the same repository within 48 hours, attributed only to the single most recent deployment before it. `failure_reason = revert_pr` or `hotfix_pr`.

A deployment that is itself a revert or hotfix is a remediation. It is never counted as a failure and is left out of the Change Failure Rate denominator, because it is the fix rather than a change.

Time to Restore runs from the moment the bad change was live to the moment production was restored:

| Failure reason            | Detected at (`failure_detected_at`)    | Restored at (`restored_at`)                             |
| ------------------------- | -------------------------------------- | ------------------------------------------------------- |
| `incident`                | Incident opened                        | Incident closed, using the duration Incident.io reports |
| `revert_pr` / `hotfix_pr` | The failed deployment itself went live | Remediation PR deployed                                 |
| `failed_deployment`       | The failed run                         | Next successful run of the same deploy workflow         |

For reverts and hotfixes the `deployments` table also carries `remediation_merged_at`, so you can split Time to Restore into time-to-notice (deployed → fix merged) and time-to-ship-the-fix (fix merged → fix deployed) in Data Explorer. A revert that lands before the reverted change ever reached production is not counted as a failure.

<Warning>
  Rules 3 and 4 are proxies. They will miss failures that were fixed forward without a revert or hotfix, and they can occasionally flag a routine revert that was not a production problem. Connecting Incident.io gives Bilanc the most faithful Change Failure Rate and Time to Restore, because real incidents replace inferred ones.
</Warning>

## Getting accurate numbers

Work through this list in order. Each step improves the metrics above it.

<Steps>
  <Step title="Connect version control (required)">
    GitHub, GitLab, Bitbucket or Azure DevOps. This alone gives you Deployment Frequency and Lead Time via releases, or via the merge-to-default proxy. See [Version Control](/connect/version-control).
  </Step>

  <Step title="Connect CI/CD">
    GitHub Actions comes with your GitHub connection. CircleCI is connected with a personal API token, see [CircleCI](/connect/project-and-delivery#circleci). GitLab CI and Azure Pipelines come with their version control connections.
  </Step>

  <Step title="Publish releases, or name your production deploy workflows clearly">
    If you cut releases for production, keep publishing them and mark release candidates as pre-releases. If you deploy from CI, make sure the production workflow name contains **prod** or **production** and one of **deploy**, **release**, **rollout** or **ship**, and does not contain **staging**, **dev**, **qa**, **test** or **preview**. "Deploy to Production" is ideal. "CD" alone, or a name that also runs against staging, will not be picked up.
  </Step>

  <Step title="Check what Bilanc detected, accept its suggestions">
    Open the Rules page, Repositories tab. Repositories with medium or low confidence, and any pending suggestions (a production branch Bilanc could not confirm, a deploy workflow that does not say "prod", a sandbox repository counted as production), are listed with the evidence. Accepting takes effect on the next hourly refresh. See [Rules](/pull-request-exclusion-rules#deployment-rules).
  </Step>

  <Step title="Connect Incident.io (beta)">
    Turns Change Failure Rate and Time to Restore from revert-based proxies into incident-based measurements. See [Incident.io](/connect/project-and-delivery#incidentio). The incident path is in beta while we finish validating it end to end with a customer.
  </Step>
</Steps>

### If you deploy with a tool Bilanc cannot see yet

ArgoCD, Spinnaker, Flux, Harness, Octopus, Vercel, Netlify and similar tools promote to production inside themselves, and Bilanc only sees what reaches your version control system or CI. Until native integrations land (below), the fastest way to get measured numbers is to leave a trace where Bilanc looks:

| Tool                            | What to do today                                                                                                                                                                                                                       | What Bilanc then sees                              |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| ArgoCD, Flux (GitOps)           | Merges to the GitOps repository's production path already count as deployments if that repository is in Bilanc. If the manifests live next to the application code, add a `production_branches` rule for the branch ArgoCD syncs from. | `merge_to_default` deployments on the right branch |
| Spinnaker, Harness, Octopus     | Have the production stage publish a GitHub release, or trigger a tiny GitHub Actions / GitLab CI job named `Deploy to Production` that records the promotion.                                                                          | `github_release` or `workflow_run`                 |
| Vercel, Netlify                 | Production deploys happen on merge to the production branch, so merge-to-default is accurate; tell Bilanc the branch with a `production_branches` rule if it is not `main`.                                                            | `merge_to_default`                                 |
| Kubernetes jobs, custom scripts | Same as Spinnaker: publish a release or run a named workflow from the promotion step.                                                                                                                                                  | `github_release` or `workflow_run`                 |

<Note>
  **Coming soon.** Native deployment events from the GitHub Deployments API and GitHub Environments, GitLab Releases and Deployments, and incident sources beyond Incident.io: PagerDuty, Opsgenie and FireHydrant. We are also adding a custom deployment webhook for tools that do not leave a trace in your version control system, such as ArgoCD, Spinnaker or Vercel. Email [sam@bilanc.co](mailto:sam@bilanc.co) to be first in line, and tell us which deployment tool you use so we prioritise it.
</Note>

## Where to see them

* **DORA Metrics dashboard** in the Bilanc app. The strip at the top shows the mix of deployment sources, the share of merged pull requests with a deploy time and whether incidents are connected; each card carries its Measured / Partial / Proxy label and performance band; the Repositories, Teams and Sources tabs break every metric down; the deployments explorer lists each deployment with the pull requests it carried and why it counted as a failure.
* **Rules page** (Admin tools → Rules), Repositories tab: what Bilanc detected per repository, with confidence and evidence, and the rules and suggestions that change it.
* **Data Explorer and Data Studio**, on the `pull_requests` table (`deployed_at`, `deployment_source`, `deployment_attribution_status`, `branching_model`, `is_production_merge`, `is_promotion_pr`, `merge_to_deploy`, `lead_time_for_changes`, `open_to_deploy`, `queue_time`) and the `deployments` table (one row per deployment, with `status`, `workflow_name`, `is_failed_change`, `failure_reason`, `failure_detected_at`, `remediation_merged_at`, `restored_at` and the pull requests it carried).
* **Metrics API**, see [Deployment & DORA Metrics](/api-reference/endpoint/deployment-metrics).

## FAQ

<AccordionGroup>
  <Accordion title="Why is my lead time so much higher than my pipeline time?">
    Because lead time includes waiting. A pipeline that takes an hour to build, test and deploy is often a small part of the total: a change merged in the afternoon waits for tomorrow's release train, a weekly release, or the next time someone runs the deploy. Merge to Deploy shows you that wait on its own. If it dominates, deploying more often moves lead time far more than speeding up CI does.
  </Accordion>

  <Accordion title="Why does a repository show merge_to_default?">
    Bilanc found no live releases, deployment records or production-named deploy workflows for that repository (nothing in the last 180 days), so merging into its production branch is being treated as the deployment. For repositories that genuinely deploy on merge this is accurate. Otherwise, publish releases or rename the production deploy workflow as described above and the repository will switch automatically, or add a `deploy_workflow_include` rule naming the workflow.
  </Accordion>

  <Accordion title="A repository used to show releases and now shows merge_to_default">
    Its last release is more than 180 days old, so Bilanc stopped waiting for the next one. Pull requests merged since are counted as deployed on merge instead of sitting without a deploy time forever. If releases resume, the repository switches back on the next refresh. To pin a repository to one signal regardless, add a `deployment_source` rule.
  </Accordion>

  <Accordion title="Why is the median so different from the average?">
    Lead time and time to restore are heavy-tailed. A handful of changes that waited weeks for a release train (or one revert matched to a month-old deployment) pull the average far above what most changes experience. The dashboard shows medians (`-p50` metrics) for that reason; the API offers both. When they disagree a lot, look at the distribution by repository in the Repositories tab.
  </Accordion>

  <Accordion title="Bilanc got my production branch wrong">
    Open Rules → Repositories, find the repository and press Override, or accept the suggestion if one is waiting. A `production_branches` rule replaces the inference for that repository from the next refresh. Common cases: gitflow repositories that promote `develop` into `main` outside pull requests, repositories whose production branch is `release`, `k8s-main` or `staging`, and infrastructure repositories that should be ignored (`deployment_source` = `ignore`).
  </Accordion>

  <Accordion title="Why are Change Failure Rate and Time to Restore 0?">
    No failed deploy runs, incidents, reverts or hotfixes were found for the period. That can be true, but check two things first: production deploy workflows are being detected (a repository on `merge_to_default` never has a failed deploy run), and Incident.io is connected if you run incidents there. Time to Restore also stays empty until a failed change has actually been restored.
  </Accordion>

  <Accordion title="We deploy with ArgoCD, Spinnaker or Vercel. Will it work?">
    Today, yes if the promotion leaves a trace in version control: a published release, a production deploy workflow, or a merge to a deployment branch. Bilanc cannot yet see a promotion that happens only inside those tools. Native deployment events and a custom webhook are on the way, so tell us which tool you use.
  </Accordion>

  <Accordion title="Which aggregation should I use for the time metrics?">
    Use the `-p50` metrics (`lead-time-for-changes-p50`, `merge-to-deploy-p50`, `time-to-restore-p50`) for a headline figure; they take no aggregation parameter. The base metrics accept `AVG`, `MIN`, `MAX` and `SUM` when you need them.
  </Accordion>
</AccordionGroup>
