Endpoints

Issues

GET/api/v1/issues

Issues in the active organization, paginated.

Default view (no status param) is a work queue, not an archive: ignored issues are excluded. Pass ?status=ignored explicitly to see them, or ?status=any (#833 review) for every status at once — the console's "All" built-in view sends this rather than reusing the omitted-param default, which would silently drop ignored issues from a view labelled "All".

from/to (#832) filter on last_seen_at, so an issue first seen long ago but active within the window still shows up. Neither is required, and omitting both means no time filter — the console always sends a resolved window (a bounded default, per ADR-0004), but a direct API caller that sends neither gets the same unbounded result the endpoint always returned.

assignee_type='me' (#833) resolves to the CALLER's own id from current_user, exactly as get_current_user derived it from the session JWT — the query string never carries an id for this case, so a shared ?assignee_type=me link cannot be repointed at someone else by editing the URL, and it stays correct for whoever opens it. 'user'/ 'team' require assignee_id (400 otherwise); 'unassigned' ignores it.

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
status?|

Filter by status. Omitted = default view: excludes 'ignored', includes everything else (a regressed issue's status is already back to 'unresolved', so it needs no special case). 'any' (#833) means literally every status, 'ignored' included — the one request 'omitted' cannot make.

service_id?|

Filter by service

regression?|

Filter to issues with (true) or without (false) a regression history

from?|

Window start (inclusive), filtered on last_seen_at. Matches GET /services/{name}/signals' range convention — a real ISO instant, not a relative expression (the console resolves 'now-7d' etc. client-side before sending it). Omitted = open start.

to?|

Window end (inclusive), filtered on last_seen_at. Omitted = open end. Omitting both from and to applies no time filter at all — the endpoint's pre-#832 'all time' behavior, unchanged for a caller that sends neither.

assignee_type?|

Filter by assignee: 'user' or 'team' (with assignee_id), 'me' (resolved from the session — never a client-supplied id), or 'unassigned'

assignee_id?|

Required when assignee_type is 'user' or 'team'; ignored otherwise

text?|

Case-insensitive substring match over title and culprit (#834, the filter language's 'text ~ ...' predicate). Omitted applies no text filter. LIKE wildcards ('%', '_') in the value are matched literally, not as patterns.

sort?Sort

Sort field

Default"last_seen_at"

Value in

  • "last_seen_at"
  • "total_count"
order?Order

Sort order

Match^(asc|desc)$
Default"desc"

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/issues"
{  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",      "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",      "fingerprint": "string",      "fingerprint_version": "string",      "title": "string",      "culprit": "string",      "first_seen_at": "2019-08-24T14:15:22Z",      "last_seen_at": "2019-08-24T14:15:22Z",      "status": "unresolved",      "resolved_at": "2019-08-24T14:15:22Z",      "assignee_type": "user",      "assignee_id": "e209ca2d-190b-4818-b659-67d4ef4f1ce8",      "assigned_at": "2019-08-24T14:15:22Z",      "assigned_by": "dd7cba77-7080-4b0e-9a94-fdfd1232f6b4",      "total_count": 0,      "created_at": "2019-08-24T14:15:22Z",      "updated_at": "2019-08-24T14:15:22Z",      "deployment_id": "6ef0ac85-9892-4664-a2a5-58bf2af5a8a6",      "attribution_offset_seconds": 0,      "is_regression": true,      "occurrence_series": [        {          "bucket_start": "2019-08-24T14:15:22Z",          "bucket_end": "2019-08-24T14:15:22Z",          "count": 0        }      ]    }  ],  "total": 0,  "page": 0,  "per_page": 0}
POST/api/v1/issues/bulk-transition

"Select all N matching this filter" (#839, epic console-issues-ux) — sets to_status on every issue the SAME filter list_issues accepts matches, in one server-side write. Editor-or-above, same gate as patch_issue's single-issue transition this fans out.

Takes the filter, never a list of ids (#839's problem statement: fanning a "resolve everything matching this filter" action out as N client-side PATCHes means paging the whole matched set into the browser first to collect ids — the exact cost this endpoint exists to avoid). status/ service_id/regression/from/to/assignee_type/assignee_id/ text are identical in name and meaning to list_issues's own query parameters — see that route's docstring for the exact semantics — so a console caller builds this body from the same IssuesFilter state that already drives the list view.

Bounded at app.services.issue_service.BULK_TRANSITION_LIMIT: a filter matching more issues than that is refused outright with a 422 naming the actual count, and NOTHING is written — never applied to only the first BULK_TRANSITION_LIMIT matches, which would be a silent truncation of exactly the kind #839 rules out. The response's affected_count is the real number of rows the write touched, not an assumption equal to whatever the pre-write filter count was.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

POST /api/v1/issues/bulk-transition body (#839, epic console-issues-ux) — "select everything matching the current filter, across pages" resolved/ignored/unresolved in one write.

Carries the FILTER, never a list of ids — sending ids would re-introduce the paging-the-whole-result-set-into-the-browser problem this endpoint exists to avoid. Every field below except to_status is the identical name and meaning GET /api/v1/issues already gives its own query parameters (list_issues's own docstring has the exact semantics of status/'any', from/to, assignee_type/assignee_id, and text) — deliberately not renamed, so a console caller building this body from the SAME IssuesFilter state that drives the list view has nothing to translate.

to_status is the one field with no read-side counterpart: the target status every matched issue is set to, same three values IssuePatchRequest .status accepts (never 'any' — that is a filter-only value, meaningless as something to set an issue TO).

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/issues/bulk-transition" \  -H "Content-Type: application/json" \  -d '{    "to_status": "unresolved"  }'
{  "affected_count": 0}
GET/api/v1/issues/{issue_id}

One issue: both counts, its sample events, and its regression history. 404s if issue_id does not exist in this organization — scoped in the query itself, so an id belonging to another tenant is indistinguishable from one that does not exist at all.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

issue_id*Issue Id
Formatuuid

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/issues/497f6eca-6276-4993-bfeb-53cbbbba6f08"
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",  "fingerprint": "string",  "fingerprint_version": "string",  "title": "string",  "culprit": "string",  "first_seen_at": "2019-08-24T14:15:22Z",  "last_seen_at": "2019-08-24T14:15:22Z",  "status": "unresolved",  "resolved_at": "2019-08-24T14:15:22Z",  "assignee_type": "user",  "assignee_id": "e209ca2d-190b-4818-b659-67d4ef4f1ce8",  "assigned_at": "2019-08-24T14:15:22Z",  "assigned_by": "dd7cba77-7080-4b0e-9a94-fdfd1232f6b4",  "total_count": 0,  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z",  "deployment_id": "6ef0ac85-9892-4664-a2a5-58bf2af5a8a6",  "attribution_offset_seconds": 0,  "in_retention_count": 0,  "samples": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "occurred_at": "2019-08-24T14:15:22Z",      "signal_type": "log",      "body": "string",      "span_name": "string",      "severity": "string",      "status": "string",      "attributes": {},      "trace_id": "string",      "span_id": "string",      "trace_link": {        "available": true,        "reason": "missing"      }    }  ],  "regressions": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "resolved_at": "2019-08-24T14:15:22Z",      "regressed_at": "2019-08-24T14:15:22Z",      "deployment_id": "6ef0ac85-9892-4664-a2a5-58bf2af5a8a6",      "attribution_offset_seconds": 0,      "deployment": {        "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",        "version": "string",        "commit_sha": "string",        "started_at": "2019-08-24T14:15:22Z",        "status": "string"      }    }  ],  "occurrence_series": [    {      "bucket_start": "2019-08-24T14:15:22Z",      "bucket_end": "2019-08-24T14:15:22Z",      "count": 0    }  ],  "pattern_hash": "string",  "deployment": {    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",    "version": "string",    "commit_sha": "string",    "started_at": "2019-08-24T14:15:22Z",    "status": "string"  }}
PATCH/api/v1/issues/{issue_id}

Status transitions (resolve/ignore/unresolve) and assignment, both optional so a caller may send either or both in one request.

status: sets Issue.status directly. Setting it to 'resolved' stamps resolved_at = now(); any other value clears it, matching every non-resolved issue's shape elsewhere in this schema (test_issue_model.py's TestIssueSchema). This is a human-driven transition, not an occurrence arriving — it does NOT go through app.services.issue_regression_service.record_occurrence (that path is for #570's derivation to call when a signal reopens a resolved issue) and does not create an IssueRegression row.

assignee: omitted leaves assignment untouched; null unassigns; an object assigns to that user or team, validated by app.services.assignment_service.validate_assignee — 400 if assignee_id does not resolve to a live user or team in this org.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

issue_id*Issue Id
Formatuuid

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

PATCH /api/v1/issues/{id} body — status transitions and assignment, both optional so a caller can send either or both.

assignee follows the standard partial-update convention: omitted means "leave assignment alone", an explicit null means "unassign", and an object means "assign to this user or team". The route distinguishes omitted from explicit-null via model_fields_set (exclude_unset=True), the same technique ServiceCatalogService.update_annotations uses.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/api/v1/issues/497f6eca-6276-4993-bfeb-53cbbbba6f08" \  -H "Content-Type: application/json" \  -d '{}'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "service_id": "641e839f-864e-4cce-98f9-40f6cbb3e9e0",  "fingerprint": "string",  "fingerprint_version": "string",  "title": "string",  "culprit": "string",  "first_seen_at": "2019-08-24T14:15:22Z",  "last_seen_at": "2019-08-24T14:15:22Z",  "status": "unresolved",  "resolved_at": "2019-08-24T14:15:22Z",  "assignee_type": "user",  "assignee_id": "e209ca2d-190b-4818-b659-67d4ef4f1ce8",  "assigned_at": "2019-08-24T14:15:22Z",  "assigned_by": "dd7cba77-7080-4b0e-9a94-fdfd1232f6b4",  "total_count": 0,  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z",  "deployment_id": "6ef0ac85-9892-4664-a2a5-58bf2af5a8a6",  "attribution_offset_seconds": 0}
GET/api/v1/issues/{issue_id}/related-incidents

Incidents on this issue's service with an overlapping activity window (#838) — see app/services/issue_incident_correlation.py for the relation rule. 404s the same way get_issue does: not found in this org reads the same as not found at all.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

issue_id*Issue Id
Formatuuid

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/issues/497f6eca-6276-4993-bfeb-53cbbbba6f08/related-incidents"
{  "available": true,  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "title": "string",      "status": "string",      "severity": "string",      "started_at": "2019-08-24T14:15:22Z",      "resolved_at": "2019-08-24T14:15:22Z"    }  ],  "total": 0}