How to use the DORA API to send deployment activity to Span
Last updated: October 27, 2026
Span's DORA API lets your deploy pipeline and incident tooling send deployments, incidents, and services to Span. Span uses them for Deployments / week, Change failure rate, Mean time to recover, and lead time for changes.
Why it matters
Span has no connector that reads deployments from GitHub Actions, GitLab CI/CD, Azure DevOps, or other CI tools. The DORA API (or a $deploy event) is how deployments reach Span. The same API lets you:
Send incidents from tools Span doesn't connect to, such as PagerDuty or Opsgenie, or record failures your team fixes with a hotfix and never opens an incident for.
Define services and their owning teams so deployments count toward the right team.
How it works
All endpoints live under https://span.app/api/dora/v1 and take and return JSON.
Resource | Endpoints |
|---|---|
Deployments |
|
Incidents |
|
Services |
|
Authentication. Send Authorization: Bearer <token> on every request. Two kinds of token work:
Service account token (recommended for CI): in Span, go to Settings → Service Accounts, open the menu on the service account's row, choose Manage Token, then Generate. Only people who can manage service accounts see this page. A service account token keeps working when people change roles or leave.
Personal access token: from Settings → Access Token. It acts as the person who created it.
A missing or invalid token returns 401 Unauthorized. Store tokens as CI secrets, never in workflow files.
Request headers.
X-Request-ID: optional. If you don't set it, Span generates one. Every response includes the request ID in this header.Idempotency-Key: optional, onPOSTandPATCH. Span processes a request with a given key once in 24 hours. A repeat with the same key, token, method, and path returns the original response withX-Replayed-Request: true. Span saves only successful responses, so a failed request can be retried with the same key. Other methods ignore the key.
Timestamps must be ISO-8601 in UTC and end in Z, such as 2026-03-25T18:00:00Z. Offsets like +00:00 return a 400 error.
Pagination. List endpoints use cursor pagination with before, after, and limit query parameters. limit defaults to 10, with a minimum of 1 and a maximum of 100; larger values are rejected. Responses include meta.page with startCursor, endCursor, hasNextPage, and hasPreviousPage. Pass endCursor as after to get the next page.
Deployments
Field | Required | Description |
|---|---|---|
| Yes | A short name for the deployment. |
| Yes | When the deployment started. Span dates the deployment by this time. |
| No | When the deployment finished. Lead time ends at completion, so send it. |
| No |
|
| No |
|
| No | For example |
| No | Extra context. |
| No | Service slugs. Span credits the deployment to the services' owning teams. A slug Span hasn't seen creates a new service with no owner. |
| No |
|
| No |
|
| No | PR numbers shipped in this deployment, for monorepos or partial deploys. Needs a GitHub ( |
| No | An object of extra key-value pairs. Values must be strings. |
curl -X POST https://span.app/api/dora/v1/deployments \
-H "Authorization: Bearer $SPAN_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: deploy-12345" \
-d '{
"title": "billing-api 2fd4e1c",
"triggeredAt": "2026-03-25T18:00:00Z",
"completedAt": "2026-03-25T18:10:00Z",
"type": "deploy",
"status": "success",
"environment": "production",
"version": "2fd4e1c",
"services": ["billing-api"],
"deployer": {"name": "First Last", "email": "first.last@example.com"},
"git": {
"repoUrl": "https://github.com/example/billing-api",
"refName": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"
},
"pullRequests": [101, 102, 103],
"metadata": {"pipeline": "release"}
}'
A successful call returns the deployment in data, including its id, with a meta.cursor. GET /deployments/:id returns the same shape, and GET /deployments returns a page of them.
Update a deployment with PATCH /deployments/:id. Every field is optional. Fields you leave out stay unchanged, and null clears a field that allows it. metadata is merged: keys you send are added or updated, and keys you leave out stay. A common pattern is to post with status: "pending" when a deploy starts, then PATCH it to success or failure with completedAt when it ends.
Delete a deployment with DELETE /deployments/:id. Span returns 204 No Content.
Incidents
Field | Required | Description |
|---|---|---|
| Yes | A short name for the incident. |
| Yes | A whole number, 0 or higher, using your own severity scale. Span stores it; no DORA metric filters on it. |
| Yes | When the incident was declared. Span places the incident in a date range by this time. |
| No, but send it | When the incident started. Mean time to recover runs from here. Without it, the incident counts as 0 in MTTR. |
| No | When the incident was resolved. Until it's set, the incident counts as 0 in MTTR. |
| No | Total incidents and Change failure rate count an incident when this is blank or contains "prod" or "prd". MTTR counts every environment. |
| No | Teams that own the incident, as |
| No | Service slugs affected by the incident. |
| No | Extra context. |
| No | IDs of deployments that caused or resolved the incident. Span stores them; Change failure rate doesn't use them today. |
| No | An object of extra key-value pairs. Values must be strings. |
curl -X POST https://span.app/api/dora/v1/incidents \
-H "Authorization: Bearer $SPAN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Checkout errors",
"severity": 1,
"issuedAt": "2026-03-25T18:00:00Z",
"startedAt": "2026-03-25T17:45:00Z",
"environment": "production",
"owners": [{"type": "team", "slug": "payments"}]
}'
Update an incident with PATCH /incidents/:id, for example to send {"endedAt": "2026-03-25T19:00:00Z", "owners": [{"type": "team", "slug": "payments"}]} when it resolves. Fields you leave out stay unchanged, except owners: a PATCH without owners removes the incident's owning teams, so always send the full owners list. null clears a field that allows it. Delete one with DELETE /incidents/:id (204 No Content).
Services
Field | Required | Description |
|---|---|---|
| Yes | A display name for the service. |
| No | The value you send in a deployment's |
| No | Owning teams, as |
| No | Extra context. |
curl -X POST https://span.app/api/dora/v1/services \
-H "Authorization: Bearer $SPAN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Billing API", "slug": "billing-api", "owners": [{"type": "team", "slug": "payments"}]}'
Update a service with PATCH /services/:id and delete one with DELETE /services/:id. Always include the full owners list when you PATCH a service; a PATCH without owners removes the service's owning teams.
Ownership: connecting deployments and incidents to teams
Deployments, by default: Span credits each deployment to the teams that own the services in its services list. The API is the only way to set service owners; there is no Catalog screen for it. To give an existing or auto-created service an owner, find its id with GET /services (match on slug), then:
curl -X PATCH https://span.app/api/dora/v1/services/<serviceId> \
-H "Authorization: Bearer $SPAN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"owners": [{"type": "team", "slug": "team-a"}]}'
Deployments, with deploy-to-PR linking on: Span turns this on for your organization on request. Span then links each deployment to its PRs (from pullRequests, or inferred from git.refName) and credits the teams of those PRs' authors instead of service owners. A deployment Span can't place (no linked PRs, or authors who aren't on a team) counts in your organization total only.
Incidents: Span credits each incident to the teams in its owners list.
A deployment or incident with no team still counts in your organization total. "Deployments in the Catalog but not under my team" almost always means the service has no owner.
Setup
Create a service account token in Settings → Service Accounts and store it as a CI secret.
Create each service once with its owning team (
POST /services).Add a step to your production deploy pipeline that sends
POST /deploymentswithstatus: "success",services, andgitwith the full commit SHA. Add anIdempotency-Keyso retries don't create duplicates.If your incidents don't come from incident.io or Rootly, send them with
POST /incidents, includingstartedAt,owners, and laterendedAt.Check Catalog → Deployments and Catalog → Incidents, then the DORA page under Insights → Productivity → DORA.
Things to know
Status defaults to
pending. Onlysuccessdeployments count. A deployment sent without a status never counts until youPATCHit.Production means blank or containing "prod" or "prd". Matching ignores case and checks for the substring, so
production,PRD-us-east-1, andpre-prodcount, whilestaging,dev,test, andqadon't. Avoid those substrings in names you don't want counted.Without an
Idempotency-Key, everyPOSTcreates a new record. A re-run step or automatic retry then counts the same deployment twice. Delete extras withDELETE /deployments/:id.Deploy-to-PR linking has requirements. Span must turn it on for your organization. The repository in
git.repoUrlmust be one Span already syncs from your git connection, using ahttps://github.com/owner/repo,https://<name>.ghe.com/owner/repo, orhttps://dev.azure.com/org/project/_git/repoURL. Inferred linking matches the full commit SHA ingit.refNameagainst PR merge commits. PRs that haven't synced yet link once they arrive.The deployer field doesn't decide team credit. Services or linked PR authors do.
Team totals can exceed the organization total. A deployment or incident with two owning teams counts once for each team and once for the organization. With deploy-to-PR linking on, a deployment that ships PRs from authors on two teams also counts for both. Don't add team rows together.
Processing time. Records appear in the Catalog as soon as the call succeeds. PR linking and lead time are calculated in the background; expect them on the DORA page within about 3 hours.
Change failure rate is incidents ÷ deployments. Span doesn't trace each incident to the deployment that caused it, so treat CFR as a trend indicator.
Worked example. In March you send 40 successful production deployments for
billing-api, owned by the payments team, and 2 production incidents withownersset to payments, each withstartedAtandendedAt30 and 90 minutes apart. For March, payments shows about 9.0 deployments per week (40 ÷ 31 days × 7), a change failure rate of 5% (2 ÷ 40), and an average MTTR of 60 minutes.
What each DORA metric needs:
Metric | Needs |
|---|---|
Deployments / week | Production deployments with |
Lead time for changes | Production deployments linked to PRs, which needs Span to turn on deploy-to-PR linking for your organization |
Change failure rate | Production deployments and production incidents in the same range |
Mean time to recover | Incidents with a start and resolved time ( |
Incidents alone (for example from incident.io) power incident counts and MTTR. Deployments / week, lead time, and change failure rate also need deployments sent through this API or a $deploy event.
Related articles
Send deployments to Span from GitHub Actions or AWS CodePipeline
Lead Time to Production in Span
Mean Time To Recover (MTTR) Report
Need help?
Contact your Span account team or reach out to support@span.app.