Endpoints

Deployments

GET/api/v1/deployments

Deployments in the active organization, paginated, newest-started- first. Matches list_services' (app/api/v1/services.py) own page/per_page + filter shape.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Query Parameters

page?Page

Page number

Range1 <= value
Default1
per_page?Per Page

Items per page

Range1 <= value <= 100
Default20
service?|

Filter by service name

environment?|

Filter by environment

status?|

Filter by status

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/deployments"
{  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",      "service_name": "string",      "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",      "version": "string",      "commit_sha": "string",      "environment": "string",      "started_at": "2019-08-24T14:15:22Z",      "finished_at": "2019-08-24T14:15:22Z",      "status": "string",      "source": "string",      "idempotency_key": "string",      "metadata": {},      "created_at": "2019-08-24T14:15:22Z",      "updated_at": "2019-08-24T14:15:22Z"    }  ],  "total": 0,  "page": 0,  "per_page": 0}
POST/api/v1/deployments

Create a deployment. org_id comes only from org (the authenticated principal's resolved organization) — see this module's schema, app.schemas.deployment, for why the request body has no field for it at all.

Replaying the same (source, idempotency_key) returns the existing row unchanged with 200 OK rather than creating a second one, which is why the status code is set explicitly below instead of relying on the decorator's default 201. See DeploymentRepository.create_or_get for how replay is enforced.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/deployments" \  -H "Content-Type: application/json" \  -d '{    "service_name": "string",    "version": "string",    "commit_sha": "string",    "environment": "string",    "started_at": "2019-08-24T14:15:22Z",    "idempotency_key": "string"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "service_name": "string",  "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",  "version": "string",  "commit_sha": "string",  "environment": "string",  "started_at": "2019-08-24T14:15:22Z",  "finished_at": "2019-08-24T14:15:22Z",  "status": "string",  "source": "string",  "idempotency_key": "string",  "metadata": {},  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z"}
GET/api/v1/deployments/markers

The marker-overlay hot path (#876's own reason to exist as a separate endpoint from GET "" — see module docstring). Registered ahead of GET "/{deployment_id}" so "markers" is never parsed as a UUID path parameter.

to before from is rejected the same way services.py::_validate_range rejects it. The window-too-wide case is DeploymentRepository.get_marker_window's own DeploymentMarkerWindowTooWide, caught here and turned into a 400 — matching get_service_signals' SignalsRangeTooWide handling.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Query Parameters

service*array<string>

One or more service names to fetch markers for

from*From

Window start (inclusive)

Formatdate-time
to*To

Window end (inclusive)

Formatdate-time

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/deployments/markers?service=string&from=2019-08-24T14%3A15%3A22Z&to=2019-08-24T14%3A15%3A22Z"
[  {    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",    "service": "string",    "version": "string",    "short_commit": "string",    "environment": "string",    "status": "string",    "started_at": "2019-08-24T14:15:22Z"  }]
GET/api/v1/deployments/changed-since

What shipped for service between the deployment before an anchor and the anchor itself (#885, epic deployment-attribution, M09) — the deployment-attribution half of "changed since last deploy"; the objectives half is out of scope (epic.md's Dependencies section).

The anchor is given directly (deployment_id) or resolved from a point in time (at) — exactly one of the two, or a 400. DeploymentRepository.get_change_window does the resolution; see that method's docstring for exactly how each input maps to an anchor, and for why from_deployment (the "lower bound") comes back None for the first-ever deployment of a service rather than an empty range.

Registered ahead of GET "/{deployment_id}" for the same reason /markers is: deployment_id's uuid.UUID path-param type does not stop FastAPI matching "changed-since" against that pattern first if it were registered later — it would 422 attempting to parse the literal segment as a UUID rather than falling through to this route.

Commit range comes from CommitLinkService.resolve_commit_range (#881) — never called at all for the first-ever-deployment case (there is no from_deployment to diff against), and returns None (not an error) when the service has no connected source repo. See DeploymentChangedSinceResponse's docstring for the four shapes this produces and how a caller tells them apart without inferring anything from an absence.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Query Parameters

service*Service

Service name

deployment_id?|

Anchor on this deployment

at?|

Anchor on the deployment in effect at this point in time

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/deployments/changed-since?service=string"
{  "service_name": "string",  "first_deployment": true,  "from_deployment": {    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",    "version": "string",    "commit_sha": "string",    "started_at": "2019-08-24T14:15:22Z",    "status": "string"  },  "to_deployment": {    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",    "version": "string",    "commit_sha": "string",    "started_at": "2019-08-24T14:15:22Z",    "status": "string"  },  "commit_range": {    "compare_url": "string",    "commits": [      {        "sha": "string",        "url": "string",        "subject": "string",        "author": "string"      }    ],    "total_commits": 0,    "truncated": true,    "degraded": true  }}
GET/api/v1/deployments/{deployment_id}

One deployment by id, within the active organization. 404 across tenants, matching finish_deployment's own get_by_id precedent.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

deployment_id*Deployment Id
Formatuuid

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/deployments/497f6eca-6276-4993-bfeb-53cbbbba6f08"
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "service_name": "string",  "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",  "version": "string",  "commit_sha": "string",  "environment": "string",  "started_at": "2019-08-24T14:15:22Z",  "finished_at": "2019-08-24T14:15:22Z",  "status": "string",  "source": "string",  "idempotency_key": "string",  "metadata": {},  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z"}
PATCH/api/v1/deployments/{deployment_id}

Finish a deployment: sets finished_at and a terminal status. 404 across tenants, matching SilenceRepository.get_by_id's own org-scoped lookup precedent. 409 when the deployment already has a finished_at — a rollback is reported as a new deployment (its own POST, its own idempotency key), never a second PATCH here; see DeploymentAlreadyFinishedError.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

deployment_id*Deployment Id
Formatuuid

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/api/v1/deployments/497f6eca-6276-4993-bfeb-53cbbbba6f08" \  -H "Content-Type: application/json" \  -d '{    "status": "succeeded"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "service_name": "string",  "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",  "version": "string",  "commit_sha": "string",  "environment": "string",  "started_at": "2019-08-24T14:15:22Z",  "finished_at": "2019-08-24T14:15:22Z",  "status": "string",  "source": "string",  "idempotency_key": "string",  "metadata": {},  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z"}
GET/api/v1/deployments/{deployment_id}/issues

Issues whose first occurrence was attributed to deployment_id. 404s if the deployment itself is not in this org — checked first, via DeploymentRepository.get_by_id's own org-scoped precedent — so a cross-org deployment id and one that does not exist at all read identically, before the issue query ever runs.

Attribution is a candidate, not a verdict (epic architecture decision): this is exactly Issue.deployment_id == deployment_id, the same column the forward read exposes, never a confidence-ranked or causally- asserted subset. A deployment nothing has attributed to yet returns a clean empty page, not an error — the normal case for a fresh deploy.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

deployment_id*Deployment Id
Formatuuid

Query Parameters

page?Page

Page number

Range1 <= value
Default1
per_page?Per Page

Items per page

Range1 <= value <= 100
Default20

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/deployments/497f6eca-6276-4993-bfeb-53cbbbba6f08/issues"
{  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",      "title": "string",      "culprit": "string",      "status": "unresolved",      "first_seen_at": "2019-08-24T14:15:22Z",      "last_seen_at": "2019-08-24T14:15:22Z",      "total_count": 0,      "attribution_offset_seconds": 0    }  ],  "total": 0,  "page": 0,  "per_page": 0}