@shipeasy/openapi 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-RTN572TI.js → chunk-JJA7XEPU.js} +91 -1
  3. package/dist/chunk-JJA7XEPU.js.map +1 -0
  4. package/dist/{chunk-DW33MG4B.js → chunk-Z3CY5Y44.js} +514 -126
  5. package/dist/chunk-Z3CY5Y44.js.map +1 -0
  6. package/dist/{client-BPVn4hMC.d.ts → client-CkRbr8I2.d.ts} +1544 -212
  7. package/dist/client.d.ts +1 -1
  8. package/dist/client.js +23 -1
  9. package/dist/custom.d.ts +5 -5
  10. package/dist/custom.js +128 -84
  11. package/dist/custom.js.map +1 -1
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +24 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/schemas.d.ts +1935 -963
  16. package/dist/schemas.js +141 -1
  17. package/openapi-sdk.json +31 -9
  18. package/openapi.json +10449 -8118
  19. package/openapi.yaml +2274 -634
  20. package/package.json +1 -1
  21. package/spec/components/schemas/alert-rules.yaml +434 -10
  22. package/spec/components/schemas/alerts.yaml +9 -7
  23. package/spec/components/schemas/common.yaml +2 -2
  24. package/spec/components/schemas/connectors.yaml +69 -6
  25. package/spec/components/schemas/events.yaml +20 -9
  26. package/spec/components/schemas/keys.yaml +39 -0
  27. package/spec/components/schemas/metrics.yaml +131 -251
  28. package/spec/components/schemas/ops.yaml +471 -29
  29. package/spec/components/schemas/projects.yaml +18 -0
  30. package/spec/components/schemas/search.yaml +1 -0
  31. package/spec/components/schemas/tags.yaml +91 -0
  32. package/spec/openapi.yaml +56 -0
  33. package/spec/paths/alerts.yaml +5 -3
  34. package/spec/paths/i18n.yaml +2 -1
  35. package/spec/paths/keys.yaml +79 -0
  36. package/spec/paths/metrics.yaml +26 -17
  37. package/spec/paths/ops.yaml +514 -11
  38. package/spec/paths/tags.yaml +270 -0
  39. package/dist/chunk-DW33MG4B.js.map +0 -1
  40. package/dist/chunk-RTN572TI.js.map +0 -1
package/openapi.yaml CHANGED
@@ -216,6 +216,35 @@ tags:
216
216
  the AI-write seam the cockpit's detail panel renders: `list` reads what a
217
217
  previous run found, `create` appends a new record, and `update` fills in the
218
218
  `working` run record you were handed (findings, PR, confidence) as you work.
219
+ - name: Questions
220
+ parent: Ops
221
+ description: |-
222
+ Structured questions an agent asks the team about a queue item — the
223
+ `AskUserQuestion` shape: a short `header`, the `question`, 2–4 `options`
224
+ (label + description), and `multiSelect` for checkboxes instead of radios.
225
+ Every question also accepts a free-text "Other" answer. Open questions sit
226
+ at the very top of the item's detail panel and mark the row in the queue.
227
+
228
+ **Asking.** `ask` posts 1–4 questions at once. Several agents (or several
229
+ runs) may have questions open on the same item at the same time.
230
+
231
+ **Getting the answer back.** `delivery: resume` (the default for a run
232
+ launched through a trigger connector) re-fires that same agent on the
233
+ item once every question its run asked is answered, continuing the same
234
+ run record with the answers in the prompt. `delivery: poll` (the default
235
+ for a live, connector-less session such as Claude Code in a terminal)
236
+ fires nothing — call `list` until the status reads `completed`.
237
+ - name: QA notes
238
+ parent: Ops
239
+ description: |-
240
+ Typed QA notes on a queue item — the QA follow-up that is not discussion.
241
+ Each note is one of four kinds: `narrowing` (only part of the bug still
242
+ fails), `clarification` (changes what "fixed" means), `verification` (a
243
+ `pass` / `fail` verdict on a fix) or `correction` (withdraws an earlier
244
+ framing). Notes are append-only, carry their author and time, and render in
245
+ their own section of the item's detail panel, separate from the comment
246
+ thread. A failing verification offers a "Reopen item" action in the
247
+ dashboard; it is always an explicit click, never a side effect of `create`.
219
248
  - name: Projects
220
249
  description: |-
221
250
  Projects: the account-level container every other resource is scoped to.
@@ -258,6 +287,19 @@ tags:
258
287
  Continue) schedule on their own surface — typically a GitHub Actions
259
288
  `schedule:` cron running the platform's headless CLI with the shared
260
289
  trigger prompt.
290
+ - name: Tags
291
+ description: |-
292
+ Tags: free-form, project-scoped labels any item can carry — queue items
293
+ (bugs, feature requests, error/alert tickets), flags, configs, kill
294
+ switches, metrics, tracked errors, alert rules, and experiments all share
295
+ one tag roster.
296
+
297
+ **Identity.** A tag is its name, unique case-insensitively per project. A
298
+ tag is born the first time it's attached — there is no separate create.
299
+
300
+ **Addressing.** An item is named by `entityType` + its handle (an ops
301
+ item's number, a resource's name, or any id). `set` replaces the full tag
302
+ set; `update` adds/removes names incrementally.
261
303
  - name: Connectors
262
304
  description: |-
263
305
  Outbound integrations: the per-project destinations feedback + trigger
@@ -4561,12 +4603,15 @@ paths:
4561
4603
  name: checkouts
4562
4604
  folder: null
4563
4605
  eventName: checkout_completed
4564
- query: count_users(checkout_completed)
4606
+ query: count(checkout_completed)
4565
4607
  queryIr:
4566
- agg:
4567
- kind: count_users
4568
- metric: checkout_completed
4569
- filters: []
4608
+ v: 2
4609
+ expr:
4610
+ node: agg
4611
+ event: checkout_completed
4612
+ agg:
4613
+ kind: count
4614
+ filters: []
4570
4615
  direction: higher_better
4571
4616
  version: 1
4572
4617
  '400':
@@ -4600,7 +4645,7 @@ paths:
4600
4645
 
4601
4646
  **Use cases**
4602
4647
 
4603
- - **Track an event** — `count_users(<event>)` for unique-user counts.
4648
+ - **Track an event** — `count(<event>)` for event counts.
4604
4649
  - **Sum a value** — `sum(<event>, <label>)` for revenue / quantity metrics.
4605
4650
  - **Experiment success metric** — create the metric, then attach its id to an experiment.
4606
4651
  tags:
@@ -4614,13 +4659,13 @@ paths:
4614
4659
  schema:
4615
4660
  $ref: '#/components/schemas/CreateMetricRequest'
4616
4661
  examples:
4617
- countUsers:
4618
- summary: Count unique users (DSL)
4619
- description: Unique users who completed checkout, expressed with the query DSL.
4662
+ countEvents:
4663
+ summary: Count an event (DSL)
4664
+ description: Completed checkouts, expressed with the query DSL.
4620
4665
  value:
4621
4666
  name: checkouts
4622
4667
  event_name: checkout_completed
4623
- query: count_users(checkout_completed)
4668
+ query: count(checkout_completed)
4624
4669
  sumRevenue:
4625
4670
  summary: Sum a value label (DSL)
4626
4671
  description: Total revenue, summing the `amount` property of the `purchase` event.
@@ -4637,10 +4682,13 @@ paths:
4637
4682
  name: checkouts
4638
4683
  event_name: checkout_completed
4639
4684
  query_ir:
4640
- agg:
4641
- kind: count_users
4642
- metric: checkout_completed
4643
- filters: []
4685
+ v: 2
4686
+ expr:
4687
+ node: agg
4688
+ event: checkout_completed
4689
+ agg:
4690
+ kind: count
4691
+ filters: []
4644
4692
  responses:
4645
4693
  '201':
4646
4694
  description: Create a metric
@@ -4721,11 +4769,14 @@ paths:
4721
4769
  eventName: purchase
4722
4770
  query: sum(purchase, amount)
4723
4771
  queryIr:
4724
- agg:
4725
- kind: sum
4726
- metric: purchase
4727
- valueLabel: amount
4728
- filters: []
4772
+ v: 2
4773
+ expr:
4774
+ node: agg
4775
+ event: purchase
4776
+ agg:
4777
+ kind: sum
4778
+ valueLabel: amount
4779
+ filters: []
4729
4780
  direction: higher_better
4730
4781
  winsorizePct: 99
4731
4782
  version: 1
@@ -5431,7 +5482,11 @@ paths:
5431
5482
  operationId: listOpsItems
5432
5483
  summary: List the operational queue
5433
5484
  description: |-
5434
- Returns the unified ops queue (bugs, feature requests, errors, alerts) in work order — highest priority first, oldest first within a priority — so consumers work it top-down. Filter by `type` and/or `status`, and cap with `limit`. Human-gated holding states (items awaiting human sign-off in the dashboard) are never returned by `all`/default status.
5485
+ Returns the unified ops queue (bugs, feature requests, errors, alerts, measurement plans) in work order — highest priority first, oldest first within a priority — so consumers work it top-down. Every filter is applied in SQL before paging, and filters combine with AND. Human-gated holding states (items awaiting human sign-off in the dashboard) are never returned by `all`/default status.
5486
+
5487
+ **Filters:** `type` (one item type), `status` (one lifecycle status, or `unresolved` — everything not yet `resolved`/`wont_fix`), `priority` (one triage priority), `owner` (one person or agent), and `scope=ready` — the items ready to be picked up right now: `open`, with nobody working them (no open run), and either unowned or reopened (sent back to `open` after an earlier run finished).
5488
+
5489
+ **Paging:** `limit` is the page size and `offset` the number of items to skip. The order is stable, so walk the queue with `offset += limit`; a page shorter than `limit` is the last one.
5435
5490
 
5436
5491
  **Use case:** Pull the open queue to triage — e.g. every `bug` still `open` — before working items down one by one.
5437
5492
  tags:
@@ -5441,8 +5496,9 @@ paths:
5441
5496
  - name: type
5442
5497
  in: query
5443
5498
  required: false
5444
- description: Filter by item type (`bug`/`feature_request`/`error`/`alert`), or `all`.
5499
+ description: Filter by item type, or `all` (the default). Every type a returned item can carry is filterable, including the auto-filed ones.
5445
5500
  schema:
5501
+ default: all
5446
5502
  anyOf:
5447
5503
  - type: string
5448
5504
  enum:
@@ -5450,30 +5506,70 @@ paths:
5450
5506
  - feature_request
5451
5507
  - error
5452
5508
  - alert
5453
- description: Queue item type. `bug` and `feature_request` are user-fileable; `error` and `alert` tickets are auto-filed by the platform.
5509
+ - measure_plan
5510
+ description: Queue item type. `bug` and `feature_request` are user-fileable; `error`, `alert`, and `measure_plan` tickets are auto-filed by the platform.
5454
5511
  - type: string
5455
5512
  const: all
5456
5513
  - name: status
5457
5514
  in: query
5458
5515
  required: false
5459
- description: Filter by lifecycle status, or `all`. The human-gated holding state (`pending_approval`) is excluded from `all`/default and returned only when requested as the exact status.
5516
+ description: Filter by one lifecycle status, `unresolved` for every status that is not `resolved` or `wont_fix`, or `all` for every status. Defaults to `all` over HTTP; the CLI and MCP tool default to `unresolved`, so pass `all` there to include resolved and won't-fix items. The human-gated holding state (`pending_approval`) is excluded from `unresolved` and `all`, and returned only when requested as the exact status.
5460
5517
  schema:
5518
+ default: all
5461
5519
  anyOf:
5462
5520
  - $ref: '#/components/schemas/OpsItemStatus'
5521
+ - type: string
5522
+ const: unresolved
5523
+ - type: string
5524
+ const: all
5525
+ x-cli:
5526
+ default: unresolved
5527
+ x-mcp:
5528
+ default: unresolved
5529
+ - name: priority
5530
+ in: query
5531
+ required: false
5532
+ description: Filter by triage priority, or `all` (the default). Items with no priority set are returned only under `all`.
5533
+ schema:
5534
+ default: all
5535
+ anyOf:
5536
+ - $ref: '#/components/schemas/OpsItemPriority'
5463
5537
  - type: string
5464
5538
  const: all
5539
+ - name: scope
5540
+ in: query
5541
+ required: false
5542
+ description: '`ready` narrows to the items ready to be picked up right now: status `open`, no open run (nobody has acked it and is still working), and either unowned (no person and no agent) or reopened (sent back to `open` after an earlier run finished). Leave `status` unset with it. `all` (the default) applies no such narrowing.'
5543
+ schema:
5544
+ type: string
5545
+ enum:
5546
+ - all
5547
+ - ready
5548
+ default: all
5465
5549
  - name: limit
5466
5550
  in: query
5467
5551
  required: false
5468
- description: Max items to return (1–500).
5552
+ description: Page size — max items to return (1–500). A page shorter than `limit` is the last one.
5469
5553
  schema:
5470
5554
  type: integer
5471
5555
  minimum: 1
5472
5556
  maximum: 500
5557
+ default: 200
5558
+ x-mcp:
5559
+ required: true
5560
+ - name: offset
5561
+ in: query
5562
+ required: false
5563
+ description: Number of items to skip before the page starts. Defaults to 0. The order is stable, so `offset += limit` walks the queue page by page.
5564
+ schema:
5565
+ type: integer
5566
+ minimum: 0
5567
+ maximum: 100000
5568
+ default: 0
5473
5569
  - name: owner
5474
5570
  in: query
5475
5571
  required: false
5476
- description: Narrow to items owned by one person OR one agent. Matches a person by `users.id`, email, or display name, and an agent by connector id, display name, or kebab-case handle — e.g. `owner=Claude` or `owner=alice@acme.dev`. Case-insensitive exact match, applied over the returned page.
5572
+ description: Narrow to items owned by one person OR one agent. Matches a person by `users.id`, email, or display name, and an agent by connector id, display name, or kebab-case handle — e.g. `owner=Claude` or `owner=alice@acme.dev`. Case-insensitive exact match.
5477
5573
  schema:
5478
5574
  type: string
5479
5575
  responses:
@@ -5519,6 +5615,8 @@ paths:
5519
5615
  - FORBIDDEN
5520
5616
  x-cli:
5521
5617
  name: list
5618
+ x-mcp:
5619
+ description: 'List the operational queue (bugs, feature requests, errors, alerts, measurement plans) in work order — highest priority first, oldest first within a priority. PAGING: `limit` (page size, 1–500) is REQUIRED — the call fails without it. Start at `offset: 0` and add `limit` to `offset` for the next page; a page shorter than `limit` is the last one. FILTERS (all optional, combined with AND, applied before paging): `type` = bug | feature_request | error | alert | measure_plan; `status` = open | in_progress | investigating_by_ai | blocked | ready_for_qa | resolved | wont_fix | pending_approval | unresolved | all — DEFAULTS TO `unresolved` (omitted = items still being worked: open, in_progress, blocked, ready_for_qa…; no resolved or wont_fix ones). Pass `status: "all"` to include resolved and wont_fix too (pending_approval is only returned when asked for exactly); `priority` = critical | high | medium | nice_to_have; `owner` = a person (id, email or name) or an agent (connector id, name or handle, e.g. "Claude"); `scope: "ready"` = what to work on now — open items nobody is working (no open run) that are unowned or were reopened after an earlier run. Use `scope: "ready"` to pick the next item, then `ops_ack` it.'
5522
5620
  post:
5523
5621
  operationId: createOpsItem
5524
5622
  summary: File a queue item
@@ -5615,6 +5713,8 @@ paths:
5615
5713
 
5616
5714
  The project is the key's own project; there is no `X-Project-Id` to pass and no way to file into someone else's queue. Repeat submissions of the same title dedupe against the open ticket already tracking it, which returns `200` with `deduped: true` instead of filing again.
5617
5715
 
5716
+ Send a `dedupKey` to own that grain yourself — the same key from a monitor, a job, or a retried install re-triggers its open ticket: the ticket's fields are refreshed from the new payload and a "re-triggered" comment is appended (`200` with `deduped: true, updated: true`), so one recurring failure stays one ticket with its history intact.
5717
+
5618
5718
  This endpoint is served by the Shipeasy **edge worker** (`api.shipeasy.ai`), not the admin API — see `servers` below.
5619
5719
 
5620
5720
  **Use case:** `shipeasy setup` fails on a customer's machine and self-reports the failure with the user's consent — `{ "title": "Setup failed at Feature installs", "stepsToReproduce": "…", "actualResult": "…" }`.
@@ -5694,7 +5794,7 @@ paths:
5694
5794
  description: |-
5695
5795
  Files one feature request onto a project's queue, awaiting human approval. The feature-request counterpart to `POST /ops/bug`, with the same three gates: a `client` key carrying `tickets:public_create`, a project that has opted in, and a `pending_approval` state forced server-side.
5696
5796
 
5697
- The project is the key's own project; there is no `X-Project-Id` to pass. Repeat submissions of the same title dedupe against the open ticket already tracking it, which returns `200` with `deduped: true` instead of filing again.
5797
+ The project is the key's own project; there is no `X-Project-Id` to pass. Repeat submissions of the same title dedupe against the open ticket already tracking it, which returns `200` with `deduped: true` instead of filing again. A `dedupKey` works exactly as on `POST /ops/bug`: a repeat carrying the same key refreshes the open ticket and comments that it was re-triggered.
5698
5798
 
5699
5799
  This endpoint is served by the Shipeasy **edge worker** (`api.shipeasy.ai`), not the admin API — see `servers` below.
5700
5800
 
@@ -6013,6 +6113,11 @@ paths:
6013
6113
  list` and use one of those (or connect the agent under Settings →
6014
6114
  Triggers).
6015
6115
 
6116
+ **Launching.** When the named agent has a connected trigger and you pass
6117
+ no `sessionId`, the ack also fires that trigger — a new provider run
6118
+ starts working the item. Pass `launch: false` to only claim the item and
6119
+ record the run (you are doing the work yourself), without starting one.
6120
+
6016
6121
  **Completion.** The run closes automatically on the loop's final actions —
6017
6122
  linking the fixing PR (`link-pr`), an ops-notify escalation, or a
6018
6123
  completion status change (`ready_for_qa`/`resolved`) — and the dashboard
@@ -6134,7 +6239,9 @@ paths:
6134
6239
  (`kind: investigated`), a blocking question for the team (`kind:
6135
6240
  question`), or how to verify the fix (`kind: ready_for_qa` with
6136
6241
  `qaNotes`). Append-only and create-only, so it is safe for restricted ops
6137
- keys.
6242
+ keys. For a decision between options you can name, ask a structured
6243
+ question instead (`ops questions ask`) — it is answerable in one tap and
6244
+ the answer comes back to your run.
6138
6245
 
6139
6246
  **Use case:** After working an item, leave a findings write-up (summary,
6140
6247
  markdown findings, sources inspected, confidence) so the team — and the
@@ -6277,53 +6384,88 @@ paths:
6277
6384
  positional:
6278
6385
  - handle
6279
6386
  - investigationId
6280
- /api/admin/agent-profiles:
6387
+ /api/admin/ops/{handle}/questions:
6281
6388
  get:
6282
- operationId: listOpsAgents
6283
- summary: List connected AI agents
6389
+ operationId: listOpsQuestions
6390
+ summary: List an item's questions
6284
6391
  description: |-
6285
- The project's connected AI agents — one per authenticated trigger
6286
- connector (Claude / Cursor / Copilot / Jules). These are the agent types
6287
- `ops ack` accepts and the agents a queue item can be assigned to.
6288
-
6289
- **Use case:** Discover which `agent` values an AI ack can use, e.g. after
6290
- an `AGENT_NOT_CONNECTED` error.
6392
+ The structured questions asked on a queue item, with their answers —
6393
+ newest first (max 100). Pull this to read the answers to questions you
6394
+ asked: an answered question reads `status: completed` and carries
6395
+ `answer.selected` (the option labels picked) and `answer.other` (the
6396
+ free-text "Other" reply, or `null`).
6397
+
6398
+ **Use case:** You asked with `delivery: poll` from a live session — poll
6399
+ this until your questions read `completed`, then continue with the answers.
6291
6400
  tags:
6292
- - Agents
6401
+ - Questions
6293
6402
  parameters:
6294
6403
  - $ref: '#/components/parameters/ProjectId'
6404
+ - name: handle
6405
+ in: path
6406
+ required: true
6407
+ description: Per-project item number (e.g. `7`) or the full ops item id.
6408
+ schema:
6409
+ $ref: '#/components/schemas/ResourceId'
6410
+ - name: status
6411
+ in: query
6412
+ required: false
6413
+ description: Filter by question status, or `all` (the default).
6414
+ schema:
6415
+ default: all
6416
+ anyOf:
6417
+ - $ref: '#/components/schemas/OpsQuestionStatus'
6418
+ - type: string
6419
+ const: all
6295
6420
  responses:
6296
6421
  '200':
6297
- description: List connected AI agents
6422
+ description: List an item's questions
6298
6423
  content:
6299
6424
  application/json:
6300
6425
  schema:
6301
- $ref: '#/components/schemas/ListOpsAgentsResponse'
6426
+ $ref: '#/components/schemas/ListOpsQuestionsResponse'
6302
6427
  '400':
6303
6428
  $ref: '#/components/responses/BadRequest'
6304
6429
  '401':
6305
6430
  $ref: '#/components/responses/Unauthorized'
6306
6431
  '403':
6307
6432
  $ref: '#/components/responses/Forbidden'
6433
+ '404':
6434
+ $ref: '#/components/responses/NotFound'
6435
+ '422':
6436
+ $ref: '#/components/responses/UnprocessableEntity'
6308
6437
  x-error-codes:
6309
6438
  - BAD_REQUEST
6310
6439
  - UNAUTHORIZED
6311
6440
  - FORBIDDEN
6441
+ - NOT_FOUND
6312
6442
  x-cli:
6313
6443
  name: list
6314
- /api/admin/ops/{handle}/comments:
6315
- get:
6316
- operationId: listOpsComments
6317
- summary: List an item's comments
6444
+ positional:
6445
+ - handle
6446
+ post:
6447
+ operationId: askOpsQuestions
6448
+ summary: Ask the team questions about an item
6318
6449
  description: |-
6319
- List the comment thread on a queue item, oldest first. Each comment
6320
- carries its author (a teammate email, or `system` for a comment authored by
6321
- Jarvis — the AI agent), its markdown body, and `parentId` for the single
6322
- level of threaded replies. Removed comments are omitted.
6323
-
6324
- **Use case:** Read the discussion on an item before replying or acting on it.
6450
+ Ask 1–4 structured questions about a queue item — the way to get a
6451
+ decision from a person when the implementation needs one. Each question
6452
+ is the `AskUserQuestion` shape: a short `header` chip, the full
6453
+ `question`, 2–4 `options` (`label` + `description`), and `multiSelect`
6454
+ (checkboxes instead of radios). The person can always answer "Other" in
6455
+ free text, so never add an "Other" option yourself. The questions render
6456
+ at the very top of the item's detail panel and mark the row in the queue.
6457
+
6458
+ **Delivery.** `resume` re-fires your trigger-connector run on the item
6459
+ once every question it asked is answered, continuing the same run record
6460
+ with the answers in the prompt — stop after asking. `poll` fires nothing;
6461
+ poll `GET .../questions` instead. Omitted, it is `resume` when `runId`
6462
+ names a run launched through a trigger connector, else `poll`.
6463
+
6464
+ **Use case:** Blocked on a product decision (which behaviour to keep,
6465
+ which of two fixes to ship) — ask instead of guessing, then wait for the
6466
+ answer.
6325
6467
  tags:
6326
- - Comments
6468
+ - Questions
6327
6469
  parameters:
6328
6470
  - $ref: '#/components/parameters/ProjectId'
6329
6471
  - name: handle
@@ -6332,13 +6474,33 @@ paths:
6332
6474
  description: Per-project item number (e.g. `7`) or the full ops item id.
6333
6475
  schema:
6334
6476
  $ref: '#/components/schemas/ResourceId'
6477
+ requestBody:
6478
+ required: true
6479
+ content:
6480
+ application/json:
6481
+ schema:
6482
+ $ref: '#/components/schemas/AskOpsQuestionsRequest'
6483
+ examples:
6484
+ single:
6485
+ summary: Ask one radio question
6486
+ value:
6487
+ runId: 0d3f4c8e-7a51-4c2e-9b1f-1e2d3c4b5a69
6488
+ questions:
6489
+ - header: Legacy param
6490
+ question: Should the fix keep honouring the legacy `?v=1` query param?
6491
+ multiSelect: false
6492
+ options:
6493
+ - label: Keep it
6494
+ description: Old links keep working; the redirect stays
6495
+ - label: Drop it
6496
+ description: Simpler code; old links land on the default view
6335
6497
  responses:
6336
6498
  '200':
6337
- description: List an item's comments
6499
+ description: The created questions, in the order asked
6338
6500
  content:
6339
6501
  application/json:
6340
6502
  schema:
6341
- $ref: '#/components/schemas/ListOpsCommentsResponse'
6503
+ $ref: '#/components/schemas/AskOpsQuestionsResponse'
6342
6504
  '400':
6343
6505
  $ref: '#/components/responses/BadRequest'
6344
6506
  '401':
@@ -6347,28 +6509,33 @@ paths:
6347
6509
  $ref: '#/components/responses/Forbidden'
6348
6510
  '404':
6349
6511
  $ref: '#/components/responses/NotFound'
6512
+ '422':
6513
+ $ref: '#/components/responses/UnprocessableEntity'
6350
6514
  x-error-codes:
6351
6515
  - BAD_REQUEST
6352
6516
  - UNAUTHORIZED
6353
6517
  - FORBIDDEN
6354
6518
  - NOT_FOUND
6519
+ - VALIDATION
6355
6520
  x-cli:
6356
- name: list
6521
+ name: ask
6357
6522
  positional:
6358
6523
  - handle
6524
+ /api/admin/ops/{handle}/questions/{questionId}/answer:
6359
6525
  post:
6360
- operationId: createOpsComment
6361
- summary: Comment on an item
6526
+ operationId: answerOpsQuestion
6527
+ summary: Answer a question
6362
6528
  description: |-
6363
- Append a comment to a queue item's thread. The body is markdown (mentions
6364
- like `@teammate` notify that person; `@shipeasy` asks Jarvis, the AI agent,
6365
- to reply). Pass `parentId` to reply under an existing top-level comment
6366
- (one level of threading — a reply to a reply attaches to the same parent).
6367
-
6368
- Create-only and append-only, so it is safe for restricted ops keys — the
6369
- same channel the ops loop and Jarvis use to leave a note on an item.
6529
+ Answer one open question on a queue item — pick option labels in
6530
+ `selected` (exactly one unless the question is `multiSelect`) and/or
6531
+ write a free-text `other`. The question moves to `completed`. When it was
6532
+ the last open question of a `resume` run, that agent is re-fired on the
6533
+ item with every answer. A person's action: never allow-listed for
6534
+ restricted ops keys.
6535
+
6536
+ **Use case:** Unblock an agent from the CLI instead of the dashboard.
6370
6537
  tags:
6371
- - Comments
6538
+ - Questions
6372
6539
  parameters:
6373
6540
  - $ref: '#/components/parameters/ProjectId'
6374
6541
  - name: handle
@@ -6377,31 +6544,35 @@ paths:
6377
6544
  description: Per-project item number (e.g. `7`) or the full ops item id.
6378
6545
  schema:
6379
6546
  $ref: '#/components/schemas/ResourceId'
6547
+ - name: questionId
6548
+ in: path
6549
+ required: true
6550
+ description: The question id (from `ask` or `list`).
6551
+ schema:
6552
+ $ref: '#/components/schemas/ResourceId'
6380
6553
  requestBody:
6381
6554
  required: true
6382
6555
  content:
6383
6556
  application/json:
6384
6557
  schema:
6385
- $ref: '#/components/schemas/CreateOpsCommentRequest'
6558
+ $ref: '#/components/schemas/AnswerOpsQuestionRequest'
6386
6559
  examples:
6387
- comment:
6388
- summary: Leave a comment
6560
+ option:
6561
+ summary: Pick an option
6389
6562
  value:
6390
- body: Repro'd on iOS Safari 17 — the button overflows its container. cc @alice
6391
- reply:
6392
- summary: Reply under an existing comment
6563
+ selected:
6564
+ - Keep it
6565
+ other:
6566
+ summary: Answer in free text
6393
6567
  value:
6394
- body: Agreed, let's ship the fix behind @shipeasy's suggested flag.
6395
- parentId: cm_01j7w8a1b2c3d4e5f6g7h8i9j0
6568
+ other: Keep it for a release, then drop it
6396
6569
  responses:
6397
- '201':
6398
- description: Comment on an item
6570
+ '200':
6571
+ description: The answered question
6399
6572
  content:
6400
6573
  application/json:
6401
6574
  schema:
6402
- $ref: '#/components/schemas/CreateOpsCommentResponse'
6403
- example:
6404
- id: cm_01j7w8a1b2c3d4e5f6g7h8i9j0
6575
+ $ref: '#/components/schemas/OpsQuestion'
6405
6576
  '400':
6406
6577
  $ref: '#/components/responses/BadRequest'
6407
6578
  '401':
@@ -6410,6 +6581,8 @@ paths:
6410
6581
  $ref: '#/components/responses/Forbidden'
6411
6582
  '404':
6412
6583
  $ref: '#/components/responses/NotFound'
6584
+ '409':
6585
+ $ref: '#/components/responses/Conflict'
6413
6586
  '422':
6414
6587
  $ref: '#/components/responses/UnprocessableEntity'
6415
6588
  x-error-codes:
@@ -6417,48 +6590,48 @@ paths:
6417
6590
  - UNAUTHORIZED
6418
6591
  - FORBIDDEN
6419
6592
  - NOT_FOUND
6593
+ - INVALID_TRANSITION
6420
6594
  - VALIDATION
6421
6595
  x-cli:
6422
- name: create
6596
+ name: answer
6423
6597
  positional:
6424
6598
  - handle
6425
- /api/admin/notifications:
6599
+ - questionId
6600
+ /api/admin/ops/{handle}/questions/{questionId}/cancel:
6426
6601
  post:
6427
- operationId: notifyOps
6428
- summary: Raise an attention notification
6602
+ operationId: cancelOpsQuestion
6603
+ summary: Withdraw a question
6429
6604
  description: |-
6430
- Raise a 'needs your attention' bell notification. Create-only and idempotent on `dedupeKey` — re-raising with the same key updates the one card instead of stacking duplicates. It never reads, marks read, or deletes the feed, so it is safe for restricted ops keys.
6605
+ Withdraw one open question you asked — it no longer needs an answer
6606
+ (you found it out yourself, or the item moved on). The question moves to
6607
+ `cancelled` and leaves the detail panel. Withdrawing the last open
6608
+ question of a run delivers nothing.
6431
6609
 
6432
- **Use case:** Escalate work that can't land in code (a product decision, a credential only a human has, a resource only a human may edit), deduped so repeats don't spam the bell.
6610
+ **Use case:** You asked, then answered it yourself from the code — take
6611
+ it off the person's plate.
6433
6612
  tags:
6434
- - Ops
6613
+ - Questions
6435
6614
  parameters:
6436
6615
  - $ref: '#/components/parameters/ProjectId'
6437
- requestBody:
6438
- required: true
6439
- content:
6440
- application/json:
6441
- schema:
6442
- $ref: '#/components/schemas/NotifyOpsRequest'
6443
- example:
6444
- title: 'Alert rule @alert:checkout-error-rate needs a human edit for #42'
6445
- summary: '#42 is a false positive — the rule fires on the expected nightly batch spike, and rule definitions can only be edited by a human.'
6446
- steps:
6447
- - Open @alert:checkout-error-rate in the dashboard alerts page.
6448
- - Raise the threshold from `2%` to `5%` and widen the window to `1h`.
6449
- - 'Re-run the shipeasy-ops-work loop — #42 will be acknowledged.'
6450
- dedupeKey: feedback:42
6451
- href: /dashboard/prj_0abc/bugs/42
6616
+ - name: handle
6617
+ in: path
6618
+ required: true
6619
+ description: Per-project item number (e.g. `7`) or the full ops item id.
6620
+ schema:
6621
+ $ref: '#/components/schemas/ResourceId'
6622
+ - name: questionId
6623
+ in: path
6624
+ required: true
6625
+ description: The question id (from `ask` or `list`).
6626
+ schema:
6627
+ $ref: '#/components/schemas/ResourceId'
6452
6628
  responses:
6453
- '201':
6454
- description: Raise an attention notification
6629
+ '200':
6630
+ description: The withdrawn question
6455
6631
  content:
6456
6632
  application/json:
6457
6633
  schema:
6458
- $ref: '#/components/schemas/NotifyOpsResponse'
6459
- example:
6460
- dedupeKey: error:checkout:5xx
6461
- dispatched: true
6634
+ $ref: '#/components/schemas/OpsQuestion'
6462
6635
  '400':
6463
6636
  $ref: '#/components/responses/BadRequest'
6464
6637
  '401':
@@ -6469,41 +6642,53 @@ paths:
6469
6642
  $ref: '#/components/responses/NotFound'
6470
6643
  '409':
6471
6644
  $ref: '#/components/responses/Conflict'
6472
- '422':
6473
- $ref: '#/components/responses/UnprocessableEntity'
6474
6645
  x-error-codes:
6475
6646
  - BAD_REQUEST
6476
6647
  - UNAUTHORIZED
6477
6648
  - FORBIDDEN
6478
- - VALIDATION
6649
+ - NOT_FOUND
6650
+ - INVALID_TRANSITION
6479
6651
  x-cli:
6480
- name: notify
6481
- /api/admin/slack/channels:
6652
+ name: cancel
6653
+ positional:
6654
+ - handle
6655
+ - questionId
6656
+ /api/admin/ops/{handle}/qa-notes:
6482
6657
  get:
6483
- operationId: listSlackChannels
6484
- summary: List Slack channels
6658
+ operationId: listOpsQaNotes
6659
+ summary: List an item's QA notes
6485
6660
  description: |-
6486
- List the project's connected Slack channels — used to resolve an alert rule's notification target.
6487
-
6488
- **Use case:** Populate a channel picker, or validate an alert rule's `--slack-channel` before saving.
6661
+ The typed QA notes on a queue item, newest first (max 200) — narrowing,
6662
+ clarification, verification and correction notes, each with its author and
6663
+ time. They are separate from the comment thread: read these to learn the
6664
+ CURRENT QA state of a ticket (what remains failing, what "fixed" now means,
6665
+ the last verification verdict) without parsing prose comments.
6666
+
6667
+ **Use case:** A coding agent picks an item up and checks the latest
6668
+ `verification` verdict and any `narrowing` note before touching the code.
6489
6669
  tags:
6490
- - Alerts
6670
+ - QA notes
6491
6671
  parameters:
6492
6672
  - $ref: '#/components/parameters/ProjectId'
6673
+ - name: handle
6674
+ in: path
6675
+ required: true
6676
+ description: Per-project item number (e.g. `7`) or the full ops item id.
6677
+ schema:
6678
+ $ref: '#/components/schemas/ResourceId'
6679
+ - name: kind
6680
+ in: query
6681
+ required: false
6682
+ description: Only notes of this kind.
6683
+ schema:
6684
+ $ref: '#/components/schemas/OpsQaNoteKind'
6493
6685
  responses:
6494
6686
  '200':
6495
- description: List Slack channels
6687
+ description: List an item's QA notes
6496
6688
  content:
6497
6689
  application/json:
6498
6690
  schema:
6499
- $ref: '#/components/schemas/ListSlackChannelsResponse'
6500
- example:
6501
- connected: true
6502
- channels:
6503
- - id: C0123
6504
- name: alerts
6505
- - id: C0456
6506
- name: general
6691
+ $ref: '#/components/schemas/ListOpsQaNotesResponse'
6507
6692
  '400':
6508
6693
  $ref: '#/components/responses/BadRequest'
6509
6694
  '401':
@@ -6512,49 +6697,68 @@ paths:
6512
6697
  $ref: '#/components/responses/Forbidden'
6513
6698
  '404':
6514
6699
  $ref: '#/components/responses/NotFound'
6515
- '409':
6516
- $ref: '#/components/responses/Conflict'
6517
6700
  '422':
6518
6701
  $ref: '#/components/responses/UnprocessableEntity'
6519
6702
  x-error-codes:
6520
6703
  - BAD_REQUEST
6521
6704
  - UNAUTHORIZED
6522
6705
  - FORBIDDEN
6706
+ - NOT_FOUND
6523
6707
  x-cli:
6524
- name: channels
6525
- /api/admin/alert-rules:
6526
- get:
6527
- operationId: listAlertRules
6528
- summary: List alert rules
6708
+ name: list
6709
+ positional:
6710
+ - handle
6711
+ post:
6712
+ operationId: createOpsQaNote
6713
+ summary: Add a QA note to an item
6529
6714
  description: |-
6530
- Returns every alert rule in the project (not paginated). Each rule carries its bound `metricId`, the denormalised `metricName` (or `null` if the metric was removed), the comparator/threshold/window, severity, enabled flag, and delivery target.
6531
-
6532
- **Use case:** Audit which metrics have alerting configured — for example to confirm an on-call threshold is set before a launch.
6715
+ Append one typed QA note to a queue item. `kind` is `narrowing` (what now
6716
+ passes vs what still fails), `clarification` (what "fixed" now means),
6717
+ `verification` (requires a `verdict` of `pass` or `fail`, and `body` says
6718
+ what was checked) or `correction` (withdraws an earlier framing). Notes
6719
+ are append-only and appear in the item's QA notes section, not in the
6720
+ comment thread. The author is the caller's identity; pass `agent` to
6721
+ record the note as written by that agent type's QA automation. A `fail` verdict never changes the item's
6722
+ status by itself: the dashboard offers an explicit "Reopen item" action,
6723
+ and `ops update --status open` does the same from the CLI.
6724
+
6725
+ **Use case:** QA automation records "verified on production: FAIL, the
6726
+ second facet still errors" as data an agent can read back.
6533
6727
  tags:
6534
- - Alerts
6728
+ - QA notes
6535
6729
  parameters:
6536
6730
  - $ref: '#/components/parameters/ProjectId'
6537
- - $ref: '#/components/parameters/Q'
6731
+ - name: handle
6732
+ in: path
6733
+ required: true
6734
+ description: Per-project item number (e.g. `7`) or the full ops item id.
6735
+ schema:
6736
+ $ref: '#/components/schemas/ResourceId'
6737
+ requestBody:
6738
+ required: true
6739
+ content:
6740
+ application/json:
6741
+ schema:
6742
+ $ref: '#/components/schemas/CreateOpsQaNoteRequest'
6743
+ examples:
6744
+ verification:
6745
+ summary: A failing verification
6746
+ value:
6747
+ kind: verification
6748
+ verdict: fail
6749
+ body: Retested on production after the deploy — the export still drops the second sheet
6750
+ narrowing:
6751
+ summary: Narrow a bug to the facet that still fails
6752
+ value:
6753
+ kind: narrowing
6754
+ body: The list view is fixed; only the detail panel still shows the stale count
6538
6755
  responses:
6539
6756
  '200':
6540
- description: List alert rules
6757
+ description: The created note
6541
6758
  content:
6542
6759
  application/json:
6543
6760
  schema:
6544
- $ref: '#/components/schemas/ListAlertRulesResponse'
6545
- example:
6546
- - id: ar_01j7w8a1b2c3d4e5f6g7h8i9j0
6547
- name: Checkout error rate
6548
- metricId: met_01j6abc2d3e4f5g6h7i8j9k0l1
6549
- metricName: checkout_errors
6550
- comparator: gt
6551
- threshold: 50
6552
- windowHours: 24
6553
- severity: warn
6554
- enabled: true
6555
- notify: null
6556
- createdAt: '2026-04-12T10:14:08.000Z'
6557
- updatedAt: '2026-04-12T10:14:08.000Z'
6761
+ $ref: '#/components/schemas/OpsQaNote'
6558
6762
  '400':
6559
6763
  $ref: '#/components/responses/BadRequest'
6560
6764
  '401':
@@ -6563,17 +6767,315 @@ paths:
6563
6767
  $ref: '#/components/responses/Forbidden'
6564
6768
  '404':
6565
6769
  $ref: '#/components/responses/NotFound'
6566
- '409':
6567
- $ref: '#/components/responses/Conflict'
6568
6770
  '422':
6569
6771
  $ref: '#/components/responses/UnprocessableEntity'
6570
6772
  x-error-codes:
6571
6773
  - BAD_REQUEST
6572
6774
  - UNAUTHORIZED
6573
6775
  - FORBIDDEN
6776
+ - NOT_FOUND
6777
+ - VALIDATION
6574
6778
  x-cli:
6575
- name: list
6576
- post:
6779
+ name: create
6780
+ positional:
6781
+ - handle
6782
+ /api/admin/agent-profiles:
6783
+ get:
6784
+ operationId: listOpsAgents
6785
+ summary: List connected AI agents
6786
+ description: |-
6787
+ The project's connected AI agents — one per authenticated trigger
6788
+ connector (Claude / Cursor / Copilot / Jules). These are the agent types
6789
+ `ops ack` accepts and the agents a queue item can be assigned to.
6790
+
6791
+ **Use case:** Discover which `agent` values an AI ack can use, e.g. after
6792
+ an `AGENT_NOT_CONNECTED` error.
6793
+ tags:
6794
+ - Agents
6795
+ parameters:
6796
+ - $ref: '#/components/parameters/ProjectId'
6797
+ responses:
6798
+ '200':
6799
+ description: List connected AI agents
6800
+ content:
6801
+ application/json:
6802
+ schema:
6803
+ $ref: '#/components/schemas/ListOpsAgentsResponse'
6804
+ '400':
6805
+ $ref: '#/components/responses/BadRequest'
6806
+ '401':
6807
+ $ref: '#/components/responses/Unauthorized'
6808
+ '403':
6809
+ $ref: '#/components/responses/Forbidden'
6810
+ x-error-codes:
6811
+ - BAD_REQUEST
6812
+ - UNAUTHORIZED
6813
+ - FORBIDDEN
6814
+ x-cli:
6815
+ name: list
6816
+ /api/admin/ops/{handle}/comments:
6817
+ get:
6818
+ operationId: listOpsComments
6819
+ summary: List an item's comments
6820
+ description: |-
6821
+ List the comment thread on a queue item, oldest first. Each comment
6822
+ carries its author (a teammate email, or `system` for a comment authored by
6823
+ Jarvis — the AI agent), its markdown body, and `parentId` for the single
6824
+ level of threaded replies. Removed comments are omitted.
6825
+
6826
+ **Use case:** Read the discussion on an item before replying or acting on it.
6827
+ tags:
6828
+ - Comments
6829
+ parameters:
6830
+ - $ref: '#/components/parameters/ProjectId'
6831
+ - name: handle
6832
+ in: path
6833
+ required: true
6834
+ description: Per-project item number (e.g. `7`) or the full ops item id.
6835
+ schema:
6836
+ $ref: '#/components/schemas/ResourceId'
6837
+ responses:
6838
+ '200':
6839
+ description: List an item's comments
6840
+ content:
6841
+ application/json:
6842
+ schema:
6843
+ $ref: '#/components/schemas/ListOpsCommentsResponse'
6844
+ '400':
6845
+ $ref: '#/components/responses/BadRequest'
6846
+ '401':
6847
+ $ref: '#/components/responses/Unauthorized'
6848
+ '403':
6849
+ $ref: '#/components/responses/Forbidden'
6850
+ '404':
6851
+ $ref: '#/components/responses/NotFound'
6852
+ x-error-codes:
6853
+ - BAD_REQUEST
6854
+ - UNAUTHORIZED
6855
+ - FORBIDDEN
6856
+ - NOT_FOUND
6857
+ x-cli:
6858
+ name: list
6859
+ positional:
6860
+ - handle
6861
+ post:
6862
+ operationId: createOpsComment
6863
+ summary: Comment on an item
6864
+ description: |-
6865
+ Append a comment to a queue item's thread. The body is markdown (mentions
6866
+ like `@teammate` notify that person; `@shipeasy` asks Jarvis, the AI agent,
6867
+ to reply). Pass `parentId` to reply under an existing top-level comment
6868
+ (one level of threading — a reply to a reply attaches to the same parent).
6869
+
6870
+ Create-only and append-only, so it is safe for restricted ops keys — the
6871
+ same channel the ops loop and Jarvis use to leave a note on an item.
6872
+ tags:
6873
+ - Comments
6874
+ parameters:
6875
+ - $ref: '#/components/parameters/ProjectId'
6876
+ - name: handle
6877
+ in: path
6878
+ required: true
6879
+ description: Per-project item number (e.g. `7`) or the full ops item id.
6880
+ schema:
6881
+ $ref: '#/components/schemas/ResourceId'
6882
+ requestBody:
6883
+ required: true
6884
+ content:
6885
+ application/json:
6886
+ schema:
6887
+ $ref: '#/components/schemas/CreateOpsCommentRequest'
6888
+ examples:
6889
+ comment:
6890
+ summary: Leave a comment
6891
+ value:
6892
+ body: Repro'd on iOS Safari 17 — the button overflows its container. cc @alice
6893
+ reply:
6894
+ summary: Reply under an existing comment
6895
+ value:
6896
+ body: Agreed, let's ship the fix behind @shipeasy's suggested flag.
6897
+ parentId: cm_01j7w8a1b2c3d4e5f6g7h8i9j0
6898
+ responses:
6899
+ '201':
6900
+ description: Comment on an item
6901
+ content:
6902
+ application/json:
6903
+ schema:
6904
+ $ref: '#/components/schemas/CreateOpsCommentResponse'
6905
+ example:
6906
+ id: cm_01j7w8a1b2c3d4e5f6g7h8i9j0
6907
+ '400':
6908
+ $ref: '#/components/responses/BadRequest'
6909
+ '401':
6910
+ $ref: '#/components/responses/Unauthorized'
6911
+ '403':
6912
+ $ref: '#/components/responses/Forbidden'
6913
+ '404':
6914
+ $ref: '#/components/responses/NotFound'
6915
+ '422':
6916
+ $ref: '#/components/responses/UnprocessableEntity'
6917
+ x-error-codes:
6918
+ - BAD_REQUEST
6919
+ - UNAUTHORIZED
6920
+ - FORBIDDEN
6921
+ - NOT_FOUND
6922
+ - VALIDATION
6923
+ x-cli:
6924
+ name: create
6925
+ positional:
6926
+ - handle
6927
+ /api/admin/notifications:
6928
+ post:
6929
+ operationId: notifyOps
6930
+ summary: Raise an attention notification
6931
+ description: |-
6932
+ Raise a 'needs your attention' bell notification. Create-only and idempotent on `dedupeKey` — re-raising with the same key updates the one card instead of stacking duplicates. It never reads, marks read, or deletes the feed, so it is safe for restricted ops keys.
6933
+
6934
+ **Use case:** Escalate work that can't land in code (a product decision, a credential only a human has, a resource only a human may edit), deduped so repeats don't spam the bell.
6935
+ tags:
6936
+ - Ops
6937
+ parameters:
6938
+ - $ref: '#/components/parameters/ProjectId'
6939
+ requestBody:
6940
+ required: true
6941
+ content:
6942
+ application/json:
6943
+ schema:
6944
+ $ref: '#/components/schemas/NotifyOpsRequest'
6945
+ example:
6946
+ title: 'Alert rule @alert:checkout-error-rate needs a human edit for #42'
6947
+ summary: '#42 is a false positive — the rule fires on the expected nightly batch spike, and rule definitions can only be edited by a human.'
6948
+ steps:
6949
+ - Open @alert:checkout-error-rate in the dashboard alerts page.
6950
+ - Raise the threshold from `2%` to `5%` and widen the window to `1h`.
6951
+ - 'Re-run the shipeasy-ops-work loop — #42 will be acknowledged.'
6952
+ dedupeKey: feedback:42
6953
+ href: /dashboard/prj_0abc/bugs/42
6954
+ responses:
6955
+ '201':
6956
+ description: Raise an attention notification
6957
+ content:
6958
+ application/json:
6959
+ schema:
6960
+ $ref: '#/components/schemas/NotifyOpsResponse'
6961
+ example:
6962
+ dedupeKey: error:checkout:5xx
6963
+ dispatched: true
6964
+ '400':
6965
+ $ref: '#/components/responses/BadRequest'
6966
+ '401':
6967
+ $ref: '#/components/responses/Unauthorized'
6968
+ '403':
6969
+ $ref: '#/components/responses/Forbidden'
6970
+ '404':
6971
+ $ref: '#/components/responses/NotFound'
6972
+ '409':
6973
+ $ref: '#/components/responses/Conflict'
6974
+ '422':
6975
+ $ref: '#/components/responses/UnprocessableEntity'
6976
+ x-error-codes:
6977
+ - BAD_REQUEST
6978
+ - UNAUTHORIZED
6979
+ - FORBIDDEN
6980
+ - VALIDATION
6981
+ x-cli:
6982
+ name: notify
6983
+ /api/admin/slack/channels:
6984
+ get:
6985
+ operationId: listSlackChannels
6986
+ summary: List Slack channels
6987
+ description: |-
6988
+ List the project's connected Slack channels — used to resolve an alert rule's notification target.
6989
+
6990
+ **Use case:** Populate a channel picker, or validate an alert rule's `--slack-channel` before saving.
6991
+ tags:
6992
+ - Alerts
6993
+ parameters:
6994
+ - $ref: '#/components/parameters/ProjectId'
6995
+ responses:
6996
+ '200':
6997
+ description: List Slack channels
6998
+ content:
6999
+ application/json:
7000
+ schema:
7001
+ $ref: '#/components/schemas/ListSlackChannelsResponse'
7002
+ example:
7003
+ connected: true
7004
+ channels:
7005
+ - id: C0123
7006
+ name: alerts
7007
+ - id: C0456
7008
+ name: general
7009
+ '400':
7010
+ $ref: '#/components/responses/BadRequest'
7011
+ '401':
7012
+ $ref: '#/components/responses/Unauthorized'
7013
+ '403':
7014
+ $ref: '#/components/responses/Forbidden'
7015
+ '404':
7016
+ $ref: '#/components/responses/NotFound'
7017
+ '409':
7018
+ $ref: '#/components/responses/Conflict'
7019
+ '422':
7020
+ $ref: '#/components/responses/UnprocessableEntity'
7021
+ x-error-codes:
7022
+ - BAD_REQUEST
7023
+ - UNAUTHORIZED
7024
+ - FORBIDDEN
7025
+ x-cli:
7026
+ name: channels
7027
+ /api/admin/alert-rules:
7028
+ get:
7029
+ operationId: listAlertRules
7030
+ summary: List alert rules
7031
+ description: |-
7032
+ Returns every alert rule in the project (not paginated). Each rule carries its bound `metricId`, the denormalised `metricName` (or `null` if the metric was removed), the comparator/threshold/window, severity, enabled flag, and delivery target.
7033
+
7034
+ **Use case:** Audit which metrics have alerting configured — for example to confirm an on-call threshold is set before a launch.
7035
+ tags:
7036
+ - Alerts
7037
+ parameters:
7038
+ - $ref: '#/components/parameters/ProjectId'
7039
+ - $ref: '#/components/parameters/Q'
7040
+ responses:
7041
+ '200':
7042
+ description: List alert rules
7043
+ content:
7044
+ application/json:
7045
+ schema:
7046
+ $ref: '#/components/schemas/ListAlertRulesResponse'
7047
+ example:
7048
+ - id: ar_01j7w8a1b2c3d4e5f6g7h8i9j0
7049
+ name: Checkout error rate
7050
+ metricId: met_01j6abc2d3e4f5g6h7i8j9k0l1
7051
+ metricName: checkout_errors
7052
+ comparator: gt
7053
+ threshold: 50
7054
+ windowHours: 24
7055
+ severity: warn
7056
+ enabled: true
7057
+ notify: null
7058
+ createdAt: '2026-04-12T10:14:08.000Z'
7059
+ updatedAt: '2026-04-12T10:14:08.000Z'
7060
+ '400':
7061
+ $ref: '#/components/responses/BadRequest'
7062
+ '401':
7063
+ $ref: '#/components/responses/Unauthorized'
7064
+ '403':
7065
+ $ref: '#/components/responses/Forbidden'
7066
+ '404':
7067
+ $ref: '#/components/responses/NotFound'
7068
+ '409':
7069
+ $ref: '#/components/responses/Conflict'
7070
+ '422':
7071
+ $ref: '#/components/responses/UnprocessableEntity'
7072
+ x-error-codes:
7073
+ - BAD_REQUEST
7074
+ - UNAUTHORIZED
7075
+ - FORBIDDEN
7076
+ x-cli:
7077
+ name: list
7078
+ post:
6577
7079
  operationId: createAlertRule
6578
7080
  summary: Create an alert rule
6579
7081
  description: |-
@@ -6790,7 +7292,9 @@ paths:
6790
7292
  description: |-
6791
7293
  Returns the project's FIRED alerts as a **bare JSON array** (no pagination envelope), ordered by `createdAt desc`. Defaults to the currently-firing ones (`status=active`); pass a `status` to widen to resolved/dismissed history or `all`.
6792
7294
 
6793
- Fired alerts are raised only by the platform — the UI's killswitch handlers plus the worker's analysis consumer and alerts cron — never filed by hand, so this surface is list + triage (via PATCH), with no create. The rules that *define* metric-threshold alerts live at `/api/admin/alert-rules`.
7295
+ A fired alert is an **instance** of an alert rule, and an instance IS its ops queue item — the same row `/api/admin/ops` returns with `type: "alert"`, viewed through the alert lens. One rule produces many instances over its life and at most one is open at a time: when the condition clears the instance closes, and the next firing is a NEW instance with its own id and history. That is why `severity` reads off the rule (the queue item carries it as `priority`) and why resolving the queue item and clearing the alert are one act, not two rows to keep in step.
7296
+
7297
+ Instances are opened only by the platform — the UI's killswitch handlers plus the worker's analysis consumer and alerts cron — never filed by hand, so this surface is list + triage (via PATCH), with no create. The rules that *define* metric-threshold alerts live at `/api/admin/alert-rules`.
6794
7298
 
6795
7299
  **Use case:** Snapshot what is currently firing for an on-call view or the home Alerts block, or pull `status=all` to audit how a noisy rule has behaved over time.
6796
7300
  tags:
@@ -6874,7 +7378,7 @@ paths:
6874
7378
  description: |-
6875
7379
  Triage writes on one fired alert — the only mutations this surface allows. All body fields are optional (at least one required); only the fields present are changed.
6876
7380
 
6877
- - **`status`** — flip between `active` / `resolved` / `dismissed`. `resolved` and `dismissed` stamp `resolvedAt` / `dismissedAt`; `active` re-opens and clears both. A resolved alert re-fires (as the same row, re-activated) if its condition is raised again.
7381
+ - **`status`** — flip between `active` / `resolved` / `dismissed`. `resolved` and `dismissed` stamp `resolvedAt` / `dismissedAt`; `active` re-opens and clears both. These write the underlying queue item's own status (`resolved` / `wont_fix` / `open`), so clearing an alert here and resolving it in the ops queue are the same act. A cleared instance is never revived: if the condition fires again it opens a NEW instance with its own id.
6878
7382
  - **`assigneeId`** — the PERSON owner (a `users.id`), or `null` to unassign.
6879
7383
  - **`agent`** — the AGENT owner: a connected trigger connector's id, or the built-in `"jarvis"` (**Enterprise plan only** — `403` otherwise), or `null` to clear. Person and agent halves are independent.
6880
7384
 
@@ -6891,7 +7395,7 @@ paths:
6891
7395
  - name: id
6892
7396
  in: path
6893
7397
  required: true
6894
- description: Stable opaque alert id (`al_…`).
7398
+ description: Stable opaque instance id — the same id as the `alert` ops item this instance is.
6895
7399
  schema:
6896
7400
  $ref: '#/components/schemas/ResourceId'
6897
7401
  requestBody:
@@ -7365,11 +7869,12 @@ paths:
7365
7869
  - name: limit
7366
7870
  in: query
7367
7871
  required: false
7368
- description: Max keys to return (1–500).
7872
+ description: Max keys to return (1–500). Defaults to 200.
7369
7873
  schema:
7370
7874
  type: integer
7371
7875
  minimum: 1
7372
7876
  maximum: 500
7877
+ default: 200
7373
7878
  - name: offset
7374
7879
  in: query
7375
7880
  required: false
@@ -9406,6 +9911,7 @@ paths:
9406
9911
  expires_at: null
9407
9912
  created_by_email: ana@example.com
9408
9913
  name: production back-end
9914
+ description: Held by the checkout service; rotate with the quarterly credential sweep
9409
9915
  scopes:
9410
9916
  - gates:evaluate
9411
9917
  - events:write
@@ -9474,18 +9980,313 @@ paths:
9474
9980
  value:
9475
9981
  type: ops
9476
9982
  responses:
9477
- '201':
9478
- description: Create an API key
9983
+ '201':
9984
+ description: Create an API key
9985
+ content:
9986
+ application/json:
9987
+ schema:
9988
+ $ref: '#/components/schemas/CreateKeyResponse'
9989
+ example:
9990
+ id: 3f2a9b1c-7d4e-4a8f-9c2b-1e5d6f7a8b90
9991
+ type: server
9992
+ env: prod
9993
+ key: sdk_server_9c1f4f1f2c0c4a5fa1c2b6d3e7c8e3a1
9994
+ expires_at: null
9995
+ '400':
9996
+ $ref: '#/components/responses/BadRequest'
9997
+ '401':
9998
+ $ref: '#/components/responses/Unauthorized'
9999
+ '403':
10000
+ $ref: '#/components/responses/Forbidden'
10001
+ '404':
10002
+ $ref: '#/components/responses/NotFound'
10003
+ '409':
10004
+ $ref: '#/components/responses/Conflict'
10005
+ '422':
10006
+ $ref: '#/components/responses/UnprocessableEntity'
10007
+ x-error-codes:
10008
+ - UNAUTHORIZED
10009
+ - FORBIDDEN
10010
+ - VALIDATION
10011
+ - PLAN_LIMIT
10012
+ x-cli:
10013
+ name: create
10014
+ /api/admin/keys/{id}:
10015
+ patch:
10016
+ operationId: updateKey
10017
+ summary: Update an API key's label
10018
+ description: |-
10019
+ Renames a key or re-notes what it is for. **Only `name` and `description` are editable** — everything that gives the key its authority (`type`, `env`, `scopes`, `expires_at`) is fixed at mint, because the worker derives the read env and permissions from the key itself. To change any of those, rotate: mint a replacement and revoke this one.
10020
+
10021
+ Omitted fields are left untouched; send `null` to clear one. Returns the updated key row. Revoked keys can still be relabelled (the audit trail outlives the credential). Returns `404` if no such key exists in the project.
10022
+
10023
+ **Use case:** A key minted by `shipeasy setup` carries an auto-composed provenance label — rename it to the service that actually holds it and note the rotation owner.
10024
+ tags:
10025
+ - API Keys
10026
+ parameters:
10027
+ - $ref: '#/components/parameters/ProjectId'
10028
+ - name: id
10029
+ in: path
10030
+ required: true
10031
+ description: Stable opaque key id (UUID) returned by `create` / `list`.
10032
+ schema:
10033
+ $ref: '#/components/schemas/ResourceId'
10034
+ requestBody:
10035
+ required: true
10036
+ content:
10037
+ application/json:
10038
+ schema:
10039
+ $ref: '#/components/schemas/UpdateKeyRequest'
10040
+ examples:
10041
+ rename:
10042
+ summary: Rename and note the key
10043
+ value:
10044
+ name: checkout-service · production
10045
+ description: Held by the checkout service; rotation owner is the payments team
10046
+ clearDescription:
10047
+ summary: Clear the note
10048
+ value:
10049
+ description: null
10050
+ responses:
10051
+ '200':
10052
+ description: Update an API key's label
10053
+ content:
10054
+ application/json:
10055
+ schema:
10056
+ $ref: '#/components/schemas/KeyRecord'
10057
+ example:
10058
+ id: 3f2a9b1c-7d4e-4a8f-9c2b-1e5d6f7a8b90
10059
+ type: server
10060
+ env: prod
10061
+ created_at: '2026-05-09T16:01:22.000Z'
10062
+ revoked_at: null
10063
+ expires_at: null
10064
+ created_by_email: ana@example.com
10065
+ name: checkout-service · production
10066
+ description: Held by the checkout service; rotation owner is the payments team
10067
+ scopes:
10068
+ - gates:evaluate
10069
+ - events:write
10070
+ last4: a1b2
10071
+ '400':
10072
+ $ref: '#/components/responses/BadRequest'
10073
+ '401':
10074
+ $ref: '#/components/responses/Unauthorized'
10075
+ '403':
10076
+ $ref: '#/components/responses/Forbidden'
10077
+ '404':
10078
+ $ref: '#/components/responses/NotFound'
10079
+ '409':
10080
+ $ref: '#/components/responses/Conflict'
10081
+ '422':
10082
+ $ref: '#/components/responses/UnprocessableEntity'
10083
+ x-error-codes:
10084
+ - UNAUTHORIZED
10085
+ - FORBIDDEN
10086
+ - NOT_FOUND
10087
+ - VALIDATION
10088
+ x-cli:
10089
+ name: update
10090
+ positional:
10091
+ - id
10092
+ /api/admin/keys/{id}/revoke:
10093
+ post:
10094
+ operationId: revokeKey
10095
+ summary: Revoke an API key
10096
+ description: |-
10097
+ Revokes a key by id — stamps its `revoked_at` and deletes the hot-path KV entry so the token stops authenticating immediately. Takes no body.
10098
+
10099
+ Idempotent: revoking an already-revoked key is a no-op and returns the same `{ id, revoked: true }`. Returns `404` if no such key exists in the project.
10100
+
10101
+ **Use case:** Rotate a leaked or stale credential — mint the replacement, then revoke the old key.
10102
+ tags:
10103
+ - API Keys
10104
+ parameters:
10105
+ - $ref: '#/components/parameters/ProjectId'
10106
+ - name: id
10107
+ in: path
10108
+ required: true
10109
+ description: Stable opaque key id (UUID) returned by `create` / `list`.
10110
+ schema:
10111
+ $ref: '#/components/schemas/ResourceId'
10112
+ responses:
10113
+ '200':
10114
+ description: Revoke an API key
10115
+ content:
10116
+ application/json:
10117
+ schema:
10118
+ $ref: '#/components/schemas/RevokeKeyResponse'
10119
+ example:
10120
+ id: 3f2a9b1c-7d4e-4a8f-9c2b-1e5d6f7a8b90
10121
+ revoked: true
10122
+ '400':
10123
+ $ref: '#/components/responses/BadRequest'
10124
+ '401':
10125
+ $ref: '#/components/responses/Unauthorized'
10126
+ '403':
10127
+ $ref: '#/components/responses/Forbidden'
10128
+ '404':
10129
+ $ref: '#/components/responses/NotFound'
10130
+ '409':
10131
+ $ref: '#/components/responses/Conflict'
10132
+ '422':
10133
+ $ref: '#/components/responses/UnprocessableEntity'
10134
+ x-error-codes:
10135
+ - UNAUTHORIZED
10136
+ - FORBIDDEN
10137
+ - NOT_FOUND
10138
+ x-cli:
10139
+ name: revoke
10140
+ positional:
10141
+ - id
10142
+ /api/admin/search:
10143
+ get:
10144
+ operationId: searchResources
10145
+ summary: Search project resources
10146
+ description: |-
10147
+ Universal substring search across the project's primary resources — feature gates, experiments, dynamic configs, killswitches, metrics, and team members. Matches the trimmed `q` against each resource's immutable `name` and its human title/display name (members match on display name or email), newest-first, capped per resource family so one noisy type can't crowd out the rest.
10148
+
10149
+ Returns a flat, ranked-by-type list of hits (members first); an empty or whitespace-only `q` returns no hits. Each hit carries a project-relative `href` that deep-links to the row.
10150
+
10151
+ **Use case:** Back a command-palette / quick-switcher in the dashboard, or look up the id of a resource by a fragment of its name before driving another API call.
10152
+ tags:
10153
+ - Projects
10154
+ parameters:
10155
+ - $ref: '#/components/parameters/ProjectId'
10156
+ - name: q
10157
+ in: query
10158
+ required: false
10159
+ description: Search query. Matched as a case-insensitive substring against each resource's `name` and title/display name. Trimmed; empty returns no hits.
10160
+ schema:
10161
+ type: string
10162
+ responses:
10163
+ '200':
10164
+ description: Search project resources
10165
+ content:
10166
+ application/json:
10167
+ schema:
10168
+ $ref: '#/components/schemas/SearchResponse'
10169
+ example:
10170
+ hits:
10171
+ - type: member
10172
+ id: ana@example.com
10173
+ name: Ana Ng
10174
+ title: ana@example.com
10175
+ href: /team
10176
+ - type: gate
10177
+ id: gat_01j7w7m9q4hxbf6npe6s9zr3vc
10178
+ name: checkout_v2
10179
+ title: Checkout v2
10180
+ href: /gates?open=gat_01j7w7m9q4hxbf6npe6s9zr3vc
10181
+ - type: experiment
10182
+ id: exp_01j7w7m9q4hxbf6npe6s9zr3vd
10183
+ name: checkout_button_color
10184
+ title: Checkout button colour test
10185
+ href: /experiments?open=exp_01j7w7m9q4hxbf6npe6s9zr3vd
10186
+ '400':
10187
+ $ref: '#/components/responses/BadRequest'
10188
+ '401':
10189
+ $ref: '#/components/responses/Unauthorized'
10190
+ '403':
10191
+ $ref: '#/components/responses/Forbidden'
10192
+ '404':
10193
+ $ref: '#/components/responses/NotFound'
10194
+ '409':
10195
+ $ref: '#/components/responses/Conflict'
10196
+ '422':
10197
+ $ref: '#/components/responses/UnprocessableEntity'
10198
+ x-error-codes:
10199
+ - BAD_REQUEST
10200
+ - UNAUTHORIZED
10201
+ - FORBIDDEN
10202
+ x-cli:
10203
+ name: search
10204
+ /api/admin/tags:
10205
+ get:
10206
+ operationId: listTags
10207
+ summary: List the project's tags
10208
+ description: |-
10209
+ Returns every tag in the project — the shared roster any item (queue items, flags, configs, kill switches, metrics, tracked errors, alert rules, experiments) can carry. Sorted by name.
10210
+
10211
+ **Use case:** See which tags already exist before tagging an item, so you reuse `ios` instead of minting `iOS-app`.
10212
+ tags:
10213
+ - Tags
10214
+ parameters:
10215
+ - $ref: '#/components/parameters/ProjectId'
10216
+ - name: q
10217
+ in: query
10218
+ required: false
10219
+ description: Case-insensitive substring filter on the tag name. Omit to return every tag.
10220
+ schema:
10221
+ type: string
10222
+ maxLength: 64
10223
+ responses:
10224
+ '200':
10225
+ description: List the project's tags
10226
+ content:
10227
+ application/json:
10228
+ schema:
10229
+ $ref: '#/components/schemas/ListTagsResponse'
10230
+ example:
10231
+ - id: tag_01j7w8a1b2c3d4e5f6g7h8i9j0
10232
+ name: checkout
10233
+ - id: tag_01j7w8a1b2c3d4e5f6g7h8i9j1
10234
+ name: ios
10235
+ '400':
10236
+ $ref: '#/components/responses/BadRequest'
10237
+ '401':
10238
+ $ref: '#/components/responses/Unauthorized'
10239
+ '403':
10240
+ $ref: '#/components/responses/Forbidden'
10241
+ '404':
10242
+ $ref: '#/components/responses/NotFound'
10243
+ '409':
10244
+ $ref: '#/components/responses/Conflict'
10245
+ '422':
10246
+ $ref: '#/components/responses/UnprocessableEntity'
10247
+ x-error-codes:
10248
+ - BAD_REQUEST
10249
+ - UNAUTHORIZED
10250
+ - FORBIDDEN
10251
+ x-cli:
10252
+ name: list
10253
+ /api/admin/tags/{entityType}/{entityId}:
10254
+ get:
10255
+ operationId: getEntityTags
10256
+ summary: Read an item's tags
10257
+ description: |-
10258
+ Returns the tags attached to one item. The item is addressed by kind (`entityType`) plus its handle, so the same call reads tags off a queue item, a flag, a config, a kill switch, a metric, a tracked error, an alert rule, or an experiment.
10259
+
10260
+ **Use case:** Check an item's current tags before editing them.
10261
+ tags:
10262
+ - Tags
10263
+ parameters:
10264
+ - $ref: '#/components/parameters/ProjectId'
10265
+ - name: entityType
10266
+ in: path
10267
+ required: true
10268
+ description: The kind of item — `ops`, `flag`, `config`, `killswitch`, `metric`, `error`, `alert_rule`, or `experiment`.
10269
+ schema:
10270
+ $ref: '#/components/schemas/TaggableEntityType'
10271
+ - name: entityId
10272
+ in: path
10273
+ required: true
10274
+ description: The item's handle — an ops item's number (e.g. `7`) or id; a flag/config/killswitch/metric/alert rule/experiment's name or id; a tracked error's id.
10275
+ schema:
10276
+ $ref: '#/components/schemas/ResourceId'
10277
+ responses:
10278
+ '200':
10279
+ description: Read an item's tags
9479
10280
  content:
9480
10281
  application/json:
9481
10282
  schema:
9482
- $ref: '#/components/schemas/CreateKeyResponse'
10283
+ $ref: '#/components/schemas/EntityTagsResponse'
9483
10284
  example:
9484
- id: 3f2a9b1c-7d4e-4a8f-9c2b-1e5d6f7a8b90
9485
- type: server
9486
- env: prod
9487
- key: sdk_server_9c1f4f1f2c0c4a5fa1c2b6d3e7c8e3a1
9488
- expires_at: null
10285
+ entityType: ops
10286
+ entityId: fb_01j7w8a1b2c3d4e5f6g7h8i9j0
10287
+ tags:
10288
+ - checkout
10289
+ - ios
9489
10290
  '400':
9490
10291
  $ref: '#/components/responses/BadRequest'
9491
10292
  '401':
@@ -9499,42 +10300,73 @@ paths:
9499
10300
  '422':
9500
10301
  $ref: '#/components/responses/UnprocessableEntity'
9501
10302
  x-error-codes:
10303
+ - BAD_REQUEST
9502
10304
  - UNAUTHORIZED
9503
10305
  - FORBIDDEN
9504
- - VALIDATION
9505
- - PLAN_LIMIT
10306
+ - NOT_FOUND
9506
10307
  x-cli:
9507
- name: create
9508
- /api/admin/keys/{id}/revoke:
9509
- post:
9510
- operationId: revokeKey
9511
- summary: Revoke an API key
10308
+ name: get
10309
+ positional:
10310
+ - entityType
10311
+ - entityId
10312
+ put:
10313
+ operationId: setEntityTags
10314
+ summary: Replace an item's tags
9512
10315
  description: |-
9513
- Revokes a key by id — stamps its `revoked_at` and deletes the hot-path KV entry so the token stops authenticating immediately. Takes no body.
10316
+ Replaces the item's whole tag set with `tags`: names not yet in the project are created, attached names not in the list are detached, and `[]` clears every tag. Names are deduped case-insensitively, reusing an existing tag's casing (sending `iOS` when `ios` exists attaches `ios`).
9514
10317
 
9515
- Idempotent: revoking an already-revoked key is a no-op and returns the same `{ id, revoked: true }`. Returns `404` if no such key exists in the project.
10318
+ **Use cases**
9516
10319
 
9517
- **Use case:** Rotate a leaked or stale credential — mint the replacement, then revoke the old key.
10320
+ - **Tag an item** — `{ "tags": ["checkout", "ios"] }`.
10321
+ - **Clear tags** — `{ "tags": [] }`.
10322
+
10323
+ To add or remove a few tags without restating the rest, use the incremental `PATCH` instead.
9518
10324
  tags:
9519
- - API Keys
10325
+ - Tags
9520
10326
  parameters:
9521
10327
  - $ref: '#/components/parameters/ProjectId'
9522
- - name: id
10328
+ - name: entityType
9523
10329
  in: path
9524
10330
  required: true
9525
- description: Stable opaque key id (UUID) returned by `create` / `list`.
10331
+ description: The kind of item — `ops`, `flag`, `config`, `killswitch`, `metric`, `error`, `alert_rule`, or `experiment`.
10332
+ schema:
10333
+ $ref: '#/components/schemas/TaggableEntityType'
10334
+ - name: entityId
10335
+ in: path
10336
+ required: true
10337
+ description: The item's handle — an ops item's number (e.g. `7`) or id; a flag/config/killswitch/metric/alert rule/experiment's name or id; a tracked error's id.
9526
10338
  schema:
9527
10339
  $ref: '#/components/schemas/ResourceId'
10340
+ requestBody:
10341
+ required: true
10342
+ content:
10343
+ application/json:
10344
+ schema:
10345
+ $ref: '#/components/schemas/SetEntityTagsRequest'
10346
+ examples:
10347
+ tag:
10348
+ summary: Set the item's tags
10349
+ value:
10350
+ tags:
10351
+ - checkout
10352
+ - ios
10353
+ clear:
10354
+ summary: Remove every tag
10355
+ value:
10356
+ tags: []
9528
10357
  responses:
9529
10358
  '200':
9530
- description: Revoke an API key
10359
+ description: Replace an item's tags
9531
10360
  content:
9532
10361
  application/json:
9533
10362
  schema:
9534
- $ref: '#/components/schemas/RevokeKeyResponse'
10363
+ $ref: '#/components/schemas/EntityTagsResponse'
9535
10364
  example:
9536
- id: 3f2a9b1c-7d4e-4a8f-9c2b-1e5d6f7a8b90
9537
- revoked: true
10365
+ entityType: ops
10366
+ entityId: fb_01j7w8a1b2c3d4e5f6g7h8i9j0
10367
+ tags:
10368
+ - checkout
10369
+ - ios
9538
10370
  '400':
9539
10371
  $ref: '#/components/responses/BadRequest'
9540
10372
  '401':
@@ -9548,57 +10380,74 @@ paths:
9548
10380
  '422':
9549
10381
  $ref: '#/components/responses/UnprocessableEntity'
9550
10382
  x-error-codes:
10383
+ - BAD_REQUEST
9551
10384
  - UNAUTHORIZED
9552
10385
  - FORBIDDEN
9553
10386
  - NOT_FOUND
10387
+ - VALIDATION
9554
10388
  x-cli:
9555
- name: revoke
10389
+ name: set
9556
10390
  positional:
9557
- - id
9558
- /api/admin/search:
9559
- get:
9560
- operationId: searchResources
9561
- summary: Search project resources
10391
+ - entityType
10392
+ - entityId
10393
+ patch:
10394
+ operationId: updateEntityTags
10395
+ summary: Add or remove tags on an item
9562
10396
  description: |-
9563
- Universal substring search across the project's primary resources — feature gates, experiments, dynamic configs, killswitches, metrics, and team members. Matches the trimmed `q` against each resource's immutable `name` and its human title/display name (members match on display name or email), newest-first, capped per resource family so one noisy type can't crowd out the rest.
10397
+ Edits an item's tags incrementally: attaches every name in `add` (creating project tags as needed) and detaches every name in `remove`, leaving the item's other tags untouched. Matching is case-insensitive; a name in both lists ends up removed.
9564
10398
 
9565
- Returns a flat, ranked-by-type list of hits (members first); an empty or whitespace-only `q` returns no hits. Each hit carries a project-relative `href` that deep-links to the row.
10399
+ **Use cases**
9566
10400
 
9567
- **Use case:** Back a command-palette / quick-switcher in the dashboard, or look up the id of a resource by a fragment of its name before driving another API call.
10401
+ - **Add a tag** — `{ "add": ["regression"] }`.
10402
+ - **Swap a tag** — `{ "add": ["p1"], "remove": ["p2"] }`.
9568
10403
  tags:
9569
- - Projects
10404
+ - Tags
9570
10405
  parameters:
9571
10406
  - $ref: '#/components/parameters/ProjectId'
9572
- - name: q
9573
- in: query
9574
- required: false
9575
- description: Search query. Matched as a case-insensitive substring against each resource's `name` and title/display name. Trimmed; empty returns no hits.
10407
+ - name: entityType
10408
+ in: path
10409
+ required: true
10410
+ description: The kind of item — `ops`, `flag`, `config`, `killswitch`, `metric`, `error`, `alert_rule`, or `experiment`.
9576
10411
  schema:
9577
- type: string
10412
+ $ref: '#/components/schemas/TaggableEntityType'
10413
+ - name: entityId
10414
+ in: path
10415
+ required: true
10416
+ description: The item's handle — an ops item's number (e.g. `7`) or id; a flag/config/killswitch/metric/alert rule/experiment's name or id; a tracked error's id.
10417
+ schema:
10418
+ $ref: '#/components/schemas/ResourceId'
10419
+ requestBody:
10420
+ required: true
10421
+ content:
10422
+ application/json:
10423
+ schema:
10424
+ $ref: '#/components/schemas/UpdateEntityTagsRequest'
10425
+ examples:
10426
+ add:
10427
+ summary: Attach one tag
10428
+ value:
10429
+ add:
10430
+ - regression
10431
+ swap:
10432
+ summary: Replace one tag with another
10433
+ value:
10434
+ add:
10435
+ - p1
10436
+ remove:
10437
+ - p2
9578
10438
  responses:
9579
10439
  '200':
9580
- description: Search project resources
10440
+ description: Add or remove tags on an item
9581
10441
  content:
9582
10442
  application/json:
9583
10443
  schema:
9584
- $ref: '#/components/schemas/SearchResponse'
10444
+ $ref: '#/components/schemas/EntityTagsResponse'
9585
10445
  example:
9586
- hits:
9587
- - type: member
9588
- id: ana@example.com
9589
- name: Ana Ng
9590
- title: ana@example.com
9591
- href: /team
9592
- - type: gate
9593
- id: gat_01j7w7m9q4hxbf6npe6s9zr3vc
9594
- name: checkout_v2
9595
- title: Checkout v2
9596
- href: /gates?open=gat_01j7w7m9q4hxbf6npe6s9zr3vc
9597
- - type: experiment
9598
- id: exp_01j7w7m9q4hxbf6npe6s9zr3vd
9599
- name: checkout_button_color
9600
- title: Checkout button colour test
9601
- href: /experiments?open=exp_01j7w7m9q4hxbf6npe6s9zr3vd
10446
+ entityType: ops
10447
+ entityId: fb_01j7w8a1b2c3d4e5f6g7h8i9j0
10448
+ tags:
10449
+ - checkout
10450
+ - ios
9602
10451
  '400':
9603
10452
  $ref: '#/components/responses/BadRequest'
9604
10453
  '401':
@@ -9615,8 +10464,13 @@ paths:
9615
10464
  - BAD_REQUEST
9616
10465
  - UNAUTHORIZED
9617
10466
  - FORBIDDEN
10467
+ - NOT_FOUND
10468
+ - VALIDATION
9618
10469
  x-cli:
9619
- name: search
10470
+ name: update
10471
+ positional:
10472
+ - entityType
10473
+ - entityId
9620
10474
  components:
9621
10475
  securitySchemes:
9622
10476
  bearerSdkKey:
@@ -11134,13 +11988,10 @@ components:
11134
11988
  aggregation:
11135
11989
  type: string
11136
11990
  enum:
11137
- - count_users
11138
11991
  - count_events
11139
- - retention_7d
11140
- - retention_30d
11141
11992
  - sum
11142
11993
  - avg
11143
- description: Reducer for the `event` form. Defaults to `count_users`.
11994
+ description: 'Reducer for the `event` form. Defaults to `count_events`. `count_users` was removed: Analytics Engine samples rows and weights the survivors, and a distinct count cannot be reweighted, so it under-reports by the sample interval. `retention_7d` / `retention_30d` were removed on 2026-08-10: the day count reached no query and no reducer, so both produced the same number as a distinct user count and reported it as a returning-user rate.'
11144
11995
  value:
11145
11996
  type: string
11146
11997
  minLength: 1
@@ -13538,272 +14389,79 @@ components:
13538
14389
  description: Replacement allowed values (for `enum`), or `null` to clear.
13539
14390
  required:
13540
14391
  type: boolean
13541
- description: Whether the attribute must be present on the evaluation context.
13542
- description:
13543
- type: string
13544
- description: Optional human note shown in the dashboard.
13545
- sdk_path:
13546
- type: string
13547
- description: Optional dotted path the SDK reads the value from.
13548
- additionalProperties: false
13549
- description: Body for `PATCH /api/admin/attributes/{id}`. Every field is optional; `name` is immutable.
13550
- UpdateAttributeResponse:
13551
- type: object
13552
- properties:
13553
- id:
13554
- type: string
13555
- description: Id of the attribute that was updated.
13556
- required:
13557
- - id
13558
- additionalProperties: false
13559
- description: Response for `PATCH /api/admin/attributes/{id}`.
13560
- QueryIr:
13561
- description: Typed query IR — the structured alternative to the `query` DSL string. Exactly one of `query` / `query_ir` is supplied per metric body.
13562
- type: object
13563
- properties:
13564
- agg:
13565
- oneOf:
13566
- - type: object
13567
- properties:
13568
- kind:
13569
- type: string
13570
- const: count_users
13571
- required:
13572
- - kind
13573
- additionalProperties: false
13574
- - type: object
13575
- properties:
13576
- kind:
13577
- type: string
13578
- const: count_events
13579
- required:
13580
- - kind
13581
- additionalProperties: false
13582
- - type: object
13583
- properties:
13584
- kind:
13585
- type: string
13586
- const: sum
13587
- required:
13588
- - kind
13589
- additionalProperties: false
13590
- - type: object
13591
- properties:
13592
- kind:
13593
- type: string
13594
- const: avg
13595
- required:
13596
- - kind
13597
- additionalProperties: false
13598
- - type: object
13599
- properties:
13600
- kind:
13601
- type: string
13602
- const: min
13603
- required:
13604
- - kind
13605
- additionalProperties: false
13606
- - type: object
13607
- properties:
13608
- kind:
13609
- type: string
13610
- const: max
13611
- required:
13612
- - kind
13613
- additionalProperties: false
13614
- - type: object
13615
- properties:
13616
- kind:
13617
- type: string
13618
- const: unique
13619
- required:
13620
- - kind
13621
- additionalProperties: false
13622
- - type: object
13623
- properties:
13624
- kind:
13625
- type: string
13626
- const: quantile
13627
- p:
13628
- anyOf:
13629
- - type: number
13630
- const: 0.5
13631
- - type: number
13632
- const: 0.75
13633
- - type: number
13634
- const: 0.9
13635
- - type: number
13636
- const: 0.95
13637
- - type: number
13638
- const: 0.99
13639
- - type: number
13640
- const: 0.999
13641
- description: Quantile fraction (0.5 = p50 … 0.999 = p999).
13642
- required:
13643
- - kind
13644
- - p
13645
- additionalProperties: false
13646
- - type: object
13647
- properties:
13648
- kind:
13649
- type: string
13650
- const: retention_Nd
13651
- 'n':
13652
- type: integer
13653
- minimum: 1
13654
- maximum: 90
13655
- description: Retention window in days (1–90).
13656
- required:
13657
- - kind
13658
- - 'n'
13659
- additionalProperties: false
13660
- - type: object
13661
- properties:
13662
- kind:
13663
- type: string
13664
- const: ratio
13665
- numerator:
13666
- type: object
13667
- properties:
13668
- agg:
13669
- type: string
13670
- enum:
13671
- - count_users
13672
- - count_events
13673
- description: Counting mode for this ratio arm.
13674
- metric:
13675
- type: string
13676
- minLength: 1
13677
- maxLength: 128
13678
- description: Source event name for this ratio arm.
13679
- filters:
13680
- description: Optional label filters.
13681
- maxItems: 16
13682
- type: array
13683
- items:
13684
- type: object
13685
- properties:
13686
- label:
13687
- type: string
13688
- pattern: ^[a-z_][a-z0-9_]{0,63}$
13689
- description: Event property / label identifier.
13690
- op:
13691
- type: string
13692
- enum:
13693
- - '='
13694
- - '!='
13695
- - '=~'
13696
- - '!~'
13697
- description: Match operator (`=~`/`!~` are regex).
13698
- value:
13699
- type: string
13700
- maxLength: 512
13701
- description: Quoted filter value (coerced for numeric labels).
13702
- required:
13703
- - label
13704
- - op
13705
- - value
13706
- additionalProperties: false
13707
- description: One label filter on the source event.
13708
- required:
13709
- - agg
13710
- - metric
13711
- additionalProperties: false
13712
- description: Numerator or denominator arm of a `ratio` aggregation.
13713
- denominator:
13714
- type: object
13715
- properties:
13716
- agg:
13717
- type: string
13718
- enum:
13719
- - count_users
13720
- - count_events
13721
- description: Counting mode for this ratio arm.
13722
- metric:
13723
- type: string
13724
- minLength: 1
13725
- maxLength: 128
13726
- description: Source event name for this ratio arm.
13727
- filters:
13728
- description: Optional label filters.
13729
- maxItems: 16
13730
- type: array
13731
- items:
13732
- type: object
13733
- properties:
13734
- label:
13735
- type: string
13736
- pattern: ^[a-z_][a-z0-9_]{0,63}$
13737
- description: Event property / label identifier.
13738
- op:
13739
- type: string
13740
- enum:
13741
- - '='
13742
- - '!='
13743
- - '=~'
13744
- - '!~'
13745
- description: Match operator (`=~`/`!~` are regex).
13746
- value:
13747
- type: string
13748
- maxLength: 512
13749
- description: Quoted filter value (coerced for numeric labels).
13750
- required:
13751
- - label
13752
- - op
13753
- - value
13754
- additionalProperties: false
13755
- description: One label filter on the source event.
13756
- required:
13757
- - agg
13758
- - metric
13759
- additionalProperties: false
13760
- description: Numerator or denominator arm of a `ratio` aggregation.
13761
- required:
13762
- - kind
13763
- - numerator
13764
- - denominator
13765
- additionalProperties: false
13766
- description: Aggregation function applied to the source event.
13767
- metric:
13768
- type: string
13769
- minLength: 1
13770
- maxLength: 128
13771
- description: Source event name (must equal `event_name`).
13772
- valueLabel:
13773
- description: Numeric property summed/averaged for `sum`/`avg`/quantile aggregations.
13774
- type: string
13775
- minLength: 1
13776
- maxLength: 128
13777
- filters:
13778
- default: []
13779
- description: Label filters on the event.
13780
- maxItems: 16
13781
- type: array
13782
- items:
13783
- type: object
13784
- properties:
13785
- label:
13786
- type: string
13787
- pattern: ^[a-z_][a-z0-9_]{0,63}$
13788
- description: Event property / label identifier.
13789
- op:
13790
- type: string
13791
- enum:
13792
- - '='
13793
- - '!='
13794
- - '=~'
13795
- - '!~'
13796
- description: Match operator (`=~`/`!~` are regex).
13797
- value:
13798
- type: string
13799
- maxLength: 512
13800
- description: Quoted filter value (coerced for numeric labels).
13801
- required:
13802
- - label
13803
- - op
13804
- - value
13805
- additionalProperties: false
13806
- description: One label filter on the source event.
14392
+ description: Whether the attribute must be present on the evaluation context.
14393
+ description:
14394
+ type: string
14395
+ description: Optional human note shown in the dashboard.
14396
+ sdk_path:
14397
+ type: string
14398
+ description: Optional dotted path the SDK reads the value from.
14399
+ additionalProperties: false
14400
+ description: Body for `PATCH /api/admin/attributes/{id}`. Every field is optional; `name` is immutable.
14401
+ UpdateAttributeResponse:
14402
+ type: object
14403
+ properties:
14404
+ id:
14405
+ type: string
14406
+ description: Id of the attribute that was updated.
14407
+ required:
14408
+ - id
14409
+ additionalProperties: false
14410
+ description: Response for `PATCH /api/admin/attributes/{id}`.
14411
+ MetricDisplayConfig:
14412
+ description: How the metric's series is DRAWN, as opposed to what it measures. Both parts used to be DSL functions (`expected(q, seasonal)`, `forecast(q, …)`), which meant turning a band on minted a different metric; they are properties now, so every chart of the metric picks them up and nothing that JUDGES the metric — an alert rule, the experiment analyzer — reads them at all.
14413
+ type: object
14414
+ properties:
14415
+ band:
14416
+ description: Shade the seasonal baseline around the series. The fit is the median of this hour of the week over the four weeks before the window, and it is the SAME fit an `anomaly` alert rule fires on — so the shaded region is exactly where such a rule would stay quiet, whether or not one exists.
14417
+ type: object
14418
+ properties:
14419
+ method:
14420
+ type: string
14421
+ enum:
14422
+ - seasonal
14423
+ sigma:
14424
+ type: number
14425
+ exclusiveMinimum: 0
14426
+ description: Half-width of the band in sigma-equivalents. Omit for 3, the sigma an alert rule also defaults to.
14427
+ required:
14428
+ - method
14429
+ additionalProperties: false
14430
+ forecast:
14431
+ description: Project the series past the end of the window. Projected buckets are marked, so a chart can draw an estimate differently from a measurement.
14432
+ type: object
14433
+ properties:
14434
+ method:
14435
+ type: string
14436
+ enum:
14437
+ - linear
14438
+ - seasonal
14439
+ description: '`linear` continues a least-squares slope over the window it is already reading. `seasonal` de-seasonalises against the four weeks before the window, fits the trend on the remainder and adds the hour-of-week term back — ask for it when the metric has a shape.'
14440
+ horizon:
14441
+ type: integer
14442
+ minimum: 1
14443
+ maximum: 500
14444
+ description: Buckets to project. Omit for a quarter of the window, which is the same claim at every bucket width.
14445
+ required:
14446
+ - method
14447
+ additionalProperties: false
14448
+ additionalProperties: false
14449
+ QueryIr:
14450
+ description: |-
14451
+ A metric definition as a typed expression tree — the structured alternative to the `query` DSL string, and what the API stores. Exactly one of `query` / `query_ir` is supplied per metric body; `query` is the same definition written as text, and is the spelling to prefer.
14452
+ Until 2026-08-10 this documented a single aggregation (`{agg, metric, filters}`, with `ratio` as a special case). That shape no longer exists — stored metrics were migrated and the code that read it was deleted — so a body sending it is rejected. Send the `query` string instead, or the tree below.
14453
+ type: object
14454
+ properties:
14455
+ v:
14456
+ type: integer
14457
+ const: 2
14458
+ description: Storage version of the definition. `2` is the only one.
14459
+ expr:
14460
+ type: object
14461
+ additionalProperties: true
14462
+ description: |-
14463
+ The expression tree: aggregate leaves (`{node: "agg", event, agg, filters, valueLabel?}`), arithmetic over them (`{node: "binary", op, left, right}`), scalars, and function calls (`{node: "call", fn, arg, params?}`). A ratio is ordinary division of two leaves.
14464
+ Deliberately opaque here rather than mirrored node by node: the grammar belongs to the query language, a second copy of it in this spec would drift from the one the server enforces, and the server validates every tree by planning it before the metric saves. `GET /api/admin/metrics` returns the grammar itself; the `query` string is the same tree in the form humans and the CLI write.
13807
14465
  groupBy:
13808
14466
  type: object
13809
14467
  properties:
@@ -13812,23 +14470,25 @@ components:
13812
14470
  enum:
13813
14471
  - by
13814
14472
  - without
13815
- description: '`by` keeps the listed labels; `without` drops them.'
14473
+ description: '`by` names the output dimensions; `without` names the ones to drop.'
13816
14474
  labels:
13817
- maxItems: 5
13818
14475
  type: array
14476
+ maxItems: 8
13819
14477
  items:
13820
14478
  type: string
13821
14479
  pattern: ^[a-z_][a-z0-9_]{0,63}$
13822
14480
  description: Event property / label identifier.
13823
- description: Labels to group by (max 5).
14481
+ description: Labels the grouping names.
13824
14482
  required:
13825
14483
  - op
13826
14484
  - labels
13827
14485
  additionalProperties: false
13828
- description: Optional group-by clause (ignored for experiment analysis).
14486
+ description: How the series is split. On the envelope rather than in `expr` because every leaf of one expression shares it.
14487
+ display:
14488
+ $ref: '#/components/schemas/MetricDisplayConfig'
13829
14489
  required:
13830
- - agg
13831
- - metric
14490
+ - v
14491
+ - expr
13832
14492
  additionalProperties: false
13833
14493
  ListMetricsResponse:
13834
14494
  type: array
@@ -13930,6 +14590,12 @@ components:
13930
14590
  additionalProperties: {}
13931
14591
  description: A metric definition.
13932
14592
  description: Every metric in the project (the list endpoint is not paginated).
14593
+ MetricDisplayName:
14594
+ description: 'What a human calls the metric — "Checkout revenue" beside the `checkout.revenue` that identifies it. Unlike `name` it is free text, it is editable, and nothing addresses the metric by it: the dashboard leads with it and falls back to `name` when it is absent, so a metric nobody named simply reads as its key. Send `null` to clear it.'
14595
+ maxLength: 200
14596
+ anyOf:
14597
+ - type: string
14598
+ - type: 'null'
13933
14599
  MetricEventName:
13934
14600
  type: string
13935
14601
  minLength: 1
@@ -13969,6 +14635,8 @@ components:
13969
14635
  properties:
13970
14636
  name:
13971
14637
  $ref: '#/components/schemas/MetricName'
14638
+ display_name:
14639
+ $ref: '#/components/schemas/MetricDisplayName'
13972
14640
  folder:
13973
14641
  $ref: '#/components/schemas/Folder'
13974
14642
  event_name:
@@ -13983,6 +14651,8 @@ components:
13983
14651
  $ref: '#/components/schemas/MetricDirection'
13984
14652
  unit:
13985
14653
  $ref: '#/components/schemas/MetricDisplayUnit'
14654
+ display:
14655
+ $ref: '#/components/schemas/MetricDisplayConfig'
13986
14656
  required:
13987
14657
  - name
13988
14658
  - event_name
@@ -13994,6 +14664,8 @@ components:
13994
14664
  properties:
13995
14665
  name:
13996
14666
  $ref: '#/components/schemas/MetricName'
14667
+ display_name:
14668
+ $ref: '#/components/schemas/MetricDisplayName'
13997
14669
  folder:
13998
14670
  $ref: '#/components/schemas/Folder'
13999
14671
  event_name:
@@ -14008,6 +14680,8 @@ components:
14008
14680
  $ref: '#/components/schemas/MetricDirection'
14009
14681
  unit:
14010
14682
  $ref: '#/components/schemas/MetricDisplayUnit'
14683
+ display:
14684
+ $ref: '#/components/schemas/MetricDisplayConfig'
14011
14685
  required:
14012
14686
  - name
14013
14687
  - event_name
@@ -14133,6 +14807,8 @@ components:
14133
14807
  UpdateMetricWithQuery:
14134
14808
  type: object
14135
14809
  properties:
14810
+ display_name:
14811
+ $ref: '#/components/schemas/MetricDisplayName'
14136
14812
  folder:
14137
14813
  $ref: '#/components/schemas/Folder'
14138
14814
  event_name:
@@ -14147,6 +14823,8 @@ components:
14147
14823
  $ref: '#/components/schemas/MetricDirection'
14148
14824
  unit:
14149
14825
  $ref: '#/components/schemas/MetricDisplayUnit'
14826
+ display:
14827
+ $ref: '#/components/schemas/MetricDisplayConfig'
14150
14828
  required:
14151
14829
  - query
14152
14830
  additionalProperties: false
@@ -14154,6 +14832,8 @@ components:
14154
14832
  UpdateMetricWithQueryIr:
14155
14833
  type: object
14156
14834
  properties:
14835
+ display_name:
14836
+ $ref: '#/components/schemas/MetricDisplayName'
14157
14837
  folder:
14158
14838
  $ref: '#/components/schemas/Folder'
14159
14839
  event_name:
@@ -14168,6 +14848,8 @@ components:
14168
14848
  $ref: '#/components/schemas/MetricDirection'
14169
14849
  unit:
14170
14850
  $ref: '#/components/schemas/MetricDisplayUnit'
14851
+ display:
14852
+ $ref: '#/components/schemas/MetricDisplayConfig'
14171
14853
  required:
14172
14854
  - query_ir
14173
14855
  additionalProperties: false
@@ -14175,6 +14857,8 @@ components:
14175
14857
  UpdateMetricFields:
14176
14858
  type: object
14177
14859
  properties:
14860
+ display_name:
14861
+ $ref: '#/components/schemas/MetricDisplayName'
14178
14862
  folder:
14179
14863
  $ref: '#/components/schemas/Folder'
14180
14864
  event_name:
@@ -14187,8 +14871,10 @@ components:
14187
14871
  $ref: '#/components/schemas/MetricDirection'
14188
14872
  unit:
14189
14873
  $ref: '#/components/schemas/MetricDisplayUnit'
14874
+ display:
14875
+ $ref: '#/components/schemas/MetricDisplayConfig'
14190
14876
  additionalProperties: false
14191
- description: Update-metric variant that leaves the query untouched (metadata-only edit — folder, event, winsorisation, default minimum effect of interest, direction, display unit).
14877
+ description: Update-metric variant that leaves the query untouched (metadata-only edit — folder, event, winsorisation, default minimum effect of interest, direction, display unit, band and projection).
14192
14878
  UpdateMetricRequest:
14193
14879
  oneOf:
14194
14880
  - $ref: '#/components/schemas/UpdateMetricWithQuery'
@@ -14287,6 +14973,14 @@ components:
14287
14973
  v:
14288
14974
  type: number
14289
14975
  description: Aggregated metric value in the bucket (sample-interval weighted).
14976
+ lo:
14977
+ type: number
14978
+ description: |
14979
+ Lower edge of the seasonal band — what this hour of the week normally holds, `sigma` below the median of the four weeks before the window. Present only for a metric whose `display.band` is on, and always alongside `hi`.
14980
+ hi:
14981
+ type: number
14982
+ description: |
14983
+ Upper edge of the seasonal band. Present only for a metric whose `display.band` is on, and always alongside `lo`. A point outside `[lo, hi]` is exactly a bucket an `anomaly` alert rule at the same sigma would breach on — the band a chart draws and the condition a rule fires on come from one fit.
14290
14984
  required:
14291
14985
  - t
14292
14986
  - v
@@ -14330,7 +15024,8 @@ components:
14330
15024
  type: string
14331
15025
  minLength: 1
14332
15026
  maxLength: 64
14333
- description: Property key, e.g. `amount` or `plan`.
15027
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
15028
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
14334
15029
  type:
14335
15030
  type: string
14336
15031
  enum:
@@ -14346,11 +15041,14 @@ components:
14346
15041
  default: ''
14347
15042
  description: Human-readable description of the property.
14348
15043
  type: string
15044
+ pending:
15045
+ description: '`true` when this property was DISCOVERED in live traffic rather than declared. Nothing is recorded for it yet — a property is written to Analytics Engine only once it owns a column, and a column is allocated when an admin declares the property (include it in a `PATCH .../events/{id}` body). Approval is a gate rather than a formality: columns are finite and permanently reserved, so auto-allocating them from customer payload keys would exhaust an event''s schema irreversibly.'
15046
+ type: boolean
14349
15047
  required:
14350
15048
  - name
14351
15049
  - type
14352
15050
  additionalProperties: false
14353
- description: One typed property declared on a catalogued event.
15051
+ description: One typed property on a catalogued event — declared by an admin, or discovered in traffic and awaiting declaration.
14354
15052
  description: Typed properties declared on the event.
14355
15053
  pending:
14356
15054
  type: integer
@@ -14375,8 +15073,8 @@ components:
14375
15073
  properties:
14376
15074
  name:
14377
15075
  type: string
14378
- pattern: ^[a-zA-Z0-9_][a-zA-Z0-9_\-.]{0,127}$
14379
- description: Event name. Starts with a letter, digit, or `_`; letters, digits, `_`, `-`, `.`; max 128 chars. Immutable after create — this is the handle metric queries reference.
15076
+ pattern: ^[a-zA-Z0-9_][a-zA-Z0-9_\-.]{0,49}$
15077
+ description: Event name. Starts with a letter, digit, or `_`; letters, digits, `_`, `-`, `.`; max 50 characters. The cap is the Analytics Engine index budget — 96 bytes total, less a 36-byte project UUID and a separator — and the API has always enforced 50 while this spec advertised 128. The charset is ASCII, so characters and bytes are the same count. Immutable after create — this is the handle metric queries reference.
14380
15078
  folder:
14381
15079
  $ref: '#/components/schemas/Folder'
14382
15080
  description:
@@ -14393,7 +15091,8 @@ components:
14393
15091
  type: string
14394
15092
  minLength: 1
14395
15093
  maxLength: 64
14396
- description: Property key, e.g. `amount` or `plan`.
15094
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
15095
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
14397
15096
  type:
14398
15097
  type: string
14399
15098
  enum:
@@ -14465,7 +15164,8 @@ components:
14465
15164
  type: string
14466
15165
  minLength: 1
14467
15166
  maxLength: 64
14468
- description: Property key, e.g. `amount` or `plan`.
15167
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
15168
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
14469
15169
  type:
14470
15170
  type: string
14471
15171
  enum:
@@ -14481,11 +15181,14 @@ components:
14481
15181
  default: ''
14482
15182
  description: Human-readable description of the property.
14483
15183
  type: string
15184
+ pending:
15185
+ description: '`true` when this property was DISCOVERED in live traffic rather than declared. Nothing is recorded for it yet — a property is written to Analytics Engine only once it owns a column, and a column is allocated when an admin declares the property (include it in a `PATCH .../events/{id}` body). Approval is a gate rather than a formality: columns are finite and permanently reserved, so auto-allocating them from customer payload keys would exhaust an event''s schema irreversibly.'
15186
+ type: boolean
14484
15187
  required:
14485
15188
  - name
14486
15189
  - type
14487
15190
  additionalProperties: false
14488
- description: One typed property declared on a catalogued event.
15191
+ description: One typed property on a catalogued event — declared by an admin, or discovered in traffic and awaiting declaration.
14489
15192
  description: Typed properties declared on the event.
14490
15193
  pending:
14491
15194
  type: integer
@@ -14532,7 +15235,8 @@ components:
14532
15235
  type: string
14533
15236
  minLength: 1
14534
15237
  maxLength: 64
14535
- description: Property key, e.g. `amount` or `plan`.
15238
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
15239
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
14536
15240
  type:
14537
15241
  type: string
14538
15242
  enum:
@@ -14583,7 +15287,8 @@ components:
14583
15287
  type: string
14584
15288
  minLength: 1
14585
15289
  maxLength: 64
14586
- description: Property key, e.g. `amount` or `plan`.
15290
+ pattern: ^[a-zA-Z_][a-zA-Z_0-9]*$
15291
+ description: Property key, e.g. `amount` or `plan`. Must be a bare identifier — it is emitted as a SQL alias when the property is used as a `by()` dimension, and there is no way to quote one in the Analytics Engine dialect.
14587
15292
  type:
14588
15293
  type: string
14589
15294
  enum:
@@ -14641,6 +15346,7 @@ components:
14641
15346
  enum:
14642
15347
  - pr_ready
14643
15348
  - pr_merged
15349
+ - pr_closed
14644
15350
  - investigated
14645
15351
  - detected
14646
15352
  - question
@@ -14648,7 +15354,7 @@ components:
14648
15354
  - pending_approval
14649
15355
  - working
14650
15356
  - backlog
14651
- description: 'Where automation has taken an item in the investigation lifecycle — the signal the ops cockpit renders as "what AI did" plus the one action expected of the user. It is **written as a side-effect of the actions that already happen**, never authored on its own: linking a PR sets `pr_ready`, moving to `in_progress` sets `working`, `ready_for_qa` sets `ready_for_qa`, a system/assistant reply sets `question`, resolving clears it, and creation seeds `backlog` or `investigated`/`detected` for auto-filed error/alert tickets. `backlog` is the single "nothing has happened yet" state — how urgent that is reads off `priority`, not off a separate state, and its one action is to hand the item to an agent. `pr_merged` is derived client-side by scanning the linked pull request on GitHub, so it is accepted on write but not currently emitted by the API. Absent on rows filed before the field existed — consumers should treat a missing value as "derive from the other fields".'
15357
+ description: 'Where automation has taken an item in the investigation lifecycle — the signal the ops cockpit renders as "what AI did" plus the one action expected of the user. It is **written as a side-effect of the actions that already happen**, never authored on its own: linking a PR sets `pr_ready`, moving to `in_progress` sets `working`, `ready_for_qa` sets `ready_for_qa`, a system/assistant reply sets `question`, resolving clears it, and creation seeds `backlog` for every untouched item (bugs, features, and auto-filed error/alert tickets alike — type is not a signal). `backlog` is the single "nothing has happened yet" state — how urgent that is reads off `priority`, not off a separate state, and its one action is to hand the item to an agent. `investigated`/`detected` are set when a completed agent run leaves findings to review, not at filing time. `pr_merged` and `pr_closed` are written by the GitHub `pull_request` webhook when the linked PR reaches a terminal outcome — merged (the fix shipped, resolve the item) or closed without merging (the fix was rejected, decide what happens next). Absent on rows filed before the field existed — consumers should treat a missing value as "derive from the other fields".'
14652
15358
  OpsBrowserContext:
14653
15359
  type: object
14654
15360
  properties:
@@ -14808,20 +15514,31 @@ components:
14808
15514
  type: string
14809
15515
  enum:
14810
15516
  - metric_rule
15517
+ - killswitch_armed
14811
15518
  - experiment_srm
14812
15519
  - experiment_peek
14813
15520
  - guardrail
14814
- description: What kind of alert transitioned to active.
15521
+ description: What kind of condition opened this alert instance.
14815
15522
  dedupeKey:
14816
15523
  type: string
14817
15524
  description: Stable per-condition key; with `source` forms the ticket's `sourceRef`.
15525
+ detail:
15526
+ anyOf:
15527
+ - type: string
15528
+ - type: 'null'
15529
+ description: The instance's one-line reading — the observed condition in words.
15530
+ ruleId:
15531
+ anyOf:
15532
+ - type: string
15533
+ - type: 'null'
15534
+ description: Id of the alert rule that opened this instance, or `null` for a built-in condition.
14818
15535
  severity:
14819
15536
  type: string
14820
15537
  enum:
14821
15538
  - danger
14822
15539
  - warn
14823
15540
  - info
14824
- description: Alert severity.
15541
+ description: How loud the alert is. Severity belongs to the RULE and is carried by this ticket as `priority`; it is DERIVED from that on the hydrated single-item read, and absent from the raw stored context list rows return.
14825
15542
  observedValue:
14826
15543
  anyOf:
14827
15544
  - type: number
@@ -14840,7 +15557,7 @@ components:
14840
15557
  - resolved
14841
15558
  - dismissed
14842
15559
  - type: 'null'
14843
- description: Current status of the underlying alert (`active` while the condition holds), or `null` if the alert row is gone.
15560
+ description: Firing state of this instance, projected from the ticket's own status (`active` while it is open, i.e. while the condition holds). Absent from the raw stored context list rows return.
14844
15561
  activeSince:
14845
15562
  anyOf:
14846
15563
  - type: string
@@ -14864,9 +15581,8 @@ components:
14864
15581
  required:
14865
15582
  - source
14866
15583
  - dedupeKey
14867
- - severity
14868
15584
  additionalProperties: {}
14869
- description: Hydrated detail for an auto-filed `alert` ticket, resolving the rule → metric → event chain at request time.
15585
+ description: Detail for an `alert` ticket — the fired-alert INSTANCE this ticket is. List rows carry the stored capture subset (`source`, `dedupeKey`, `ruleId`, `detail`, `href`, `observedValue`); the single-item read hydrates the rest, resolving `severity`/`status` off the ticket itself and the rule → metric → event chain at request time.
14870
15586
  MeasurePlanResource:
14871
15587
  type: object
14872
15588
  properties:
@@ -14970,7 +15686,7 @@ components:
14970
15686
  properties:
14971
15687
  t:
14972
15688
  type: number
14973
- description: Bucket start — unix epoch **milliseconds** (hourly buckets).
15689
+ description: Bucket start — unix epoch **milliseconds**. Bucket width scales with the window (hourly up to ~5 days, coarser beyond).
14974
15690
  v:
14975
15691
  type: number
14976
15692
  description: Occurrence count in the bucket (sample-rate weighted).
@@ -14978,11 +15694,11 @@ components:
14978
15694
  - t
14979
15695
  - v
14980
15696
  additionalProperties: {}
14981
- description: One hourly occurrence bucket.
14982
- description: Hourly occurrence buckets, oldest first. Hours with no occurrences are omitted.
15697
+ description: One occurrence bucket.
15698
+ description: Occurrence buckets, oldest first. Buckets with no occurrences are omitted.
14983
15699
  domainFrom:
14984
15700
  type: number
14985
- description: Chart domain start — unix epoch milliseconds (24h before the read).
15701
+ description: Chart domain start — unix epoch milliseconds (the window's start).
14986
15702
  domainTo:
14987
15703
  type: number
14988
15704
  description: Chart domain end — unix epoch milliseconds (the read time).
@@ -14991,7 +15707,7 @@ components:
14991
15707
  - domainFrom
14992
15708
  - domainTo
14993
15709
  additionalProperties: {}
14994
- description: Occurrence timeline for an `error` ticket — hourly buckets over the last 24h from the sampled occurrences, hydrated at read time by the single-item read only. ABSENT (not `null`) on non-error tickets, on list rows (raw context), and when no occurrence landed in the window.
15710
+ description: 'Occurrence timeline for an `error` ticket — buckets over the project''s error auto-close window (Settings → Errors: `errorAutocloseDays`, 7 days when auto-close is off, capped at 30) from the sampled occurrences, hydrated at read time by the single-item read only. Plotting exactly the quiet window that auto-resolves the error is what makes an empty right-hand edge meaningful. ABSENT (not `null`) on non-error tickets, on list rows (raw context), and when no occurrence landed in the window.'
14995
15711
  additionalProperties: {}
14996
15712
  description: Per-type capture context on a queue item. Exactly one of `browser` (bug/feature), `error`, `alert`, or `measurePlan` is populated depending on `type`; other keys (e.g. the AI dedupe block on bugs) may also be present.
14997
15713
  GithubPrLink:
@@ -15015,6 +15731,25 @@ components:
15015
15731
  - closes
15016
15732
  - comment
15017
15733
  description: How the PR was connected to the issue.
15734
+ owner:
15735
+ type: string
15736
+ description: Repository owner (org or user) the pull request lives in. Absent on links recorded before the field existed — `url` still carries it.
15737
+ repo:
15738
+ type: string
15739
+ description: Repository name the pull request lives in. Absent on links recorded before the field existed — `url` still carries it.
15740
+ state:
15741
+ type: string
15742
+ enum:
15743
+ - open
15744
+ - merged
15745
+ - closed
15746
+ description: Outcome of the pull request on GitHub, kept current by the `pull_request` webhook. `merged` and `closed` (closed WITHOUT merging, i.e. rejected) are terminal. Absent means the outcome has never been observed — treat it as `open`.
15747
+ mergedAt:
15748
+ type: string
15749
+ description: ISO-8601 timestamp the pull request merged, when `state` is `merged`.
15750
+ closedAt:
15751
+ type: string
15752
+ description: ISO-8601 timestamp the pull request closed unmerged, when `state` is `closed`.
15018
15753
  required:
15019
15754
  - number
15020
15755
  - url
@@ -15387,6 +16122,11 @@ components:
15387
16122
  additionalProperties: {}
15388
16123
  description: One queue item — any type — plus its resolved `owner` and run state.
15389
16124
  description: A page of queue items, newest first.
16125
+ TagName:
16126
+ type: string
16127
+ minLength: 1
16128
+ maxLength: 64
16129
+ description: A tag name. Trimmed; matched case-insensitively against the project's existing tags.
15390
16130
  CreateBugRequest:
15391
16131
  type: object
15392
16132
  properties:
@@ -15439,10 +16179,11 @@ components:
15439
16179
  description: Emails of teammates to subscribe to this item's Slack pings at creation.
15440
16180
  tags:
15441
16181
  type: array
16182
+ maxItems: 50
15442
16183
  items:
15443
- type: string
16184
+ $ref: '#/components/schemas/TagName'
15444
16185
  default: []
15445
- description: Tag names to attach at creation (get-or-created by name, deduped case-insensitively).
16186
+ description: Tag names to label the item with, e.g. `["ios", "checkout"]`. Each name is matched case-insensitively against the project's existing tags and reuses that tag (keeping its stored casing) when one matches; a name with no match creates a new project tag.
15446
16187
  reporterEmail:
15447
16188
  anyOf:
15448
16189
  - type: string
@@ -15530,10 +16271,11 @@ components:
15530
16271
  description: Emails of teammates to subscribe to this item's Slack pings at creation.
15531
16272
  tags:
15532
16273
  type: array
16274
+ maxItems: 50
15533
16275
  items:
15534
- type: string
16276
+ $ref: '#/components/schemas/TagName'
15535
16277
  default: []
15536
- description: Tag names to attach at creation (get-or-created by name, deduped case-insensitively).
16278
+ description: Tag names to label the item with, e.g. `["ios", "checkout"]`. Each name is matched case-insensitively against the project's existing tags and reuses that tag (keeping its stored casing) when one matches; a name with no match creates a new project tag.
15537
16279
  reporterEmail:
15538
16280
  anyOf:
15539
16281
  - type: string
@@ -15640,10 +16382,11 @@ components:
15640
16382
  description: Emails of teammates to subscribe to this item's Slack pings at creation.
15641
16383
  tags:
15642
16384
  type: array
16385
+ maxItems: 50
15643
16386
  items:
15644
- type: string
16387
+ $ref: '#/components/schemas/TagName'
15645
16388
  default: []
15646
- description: Tag names to attach at creation (get-or-created by name, deduped case-insensitively).
16389
+ description: 'Tag names to label the item with, e.g. `["ios", "checkout"]`. Each name is matched case-insensitively against the project''s existing tags and reuses that tag (keeping its stored casing) when one matches; a name with no match creates a new project tag. Here the tags only label the ticket: the project''s tag rules (owner/status/agent routing) are not applied to a public report while it awaits approval.'
15647
16390
  reporterEmail:
15648
16391
  anyOf:
15649
16392
  - type: string
@@ -15674,6 +16417,10 @@ components:
15674
16417
  additionalProperties: {}
15675
16418
  - type: 'null'
15676
16419
  description: Arbitrary capture context, or `null`.
16420
+ dedupKey:
16421
+ type: string
16422
+ maxLength: 200
16423
+ description: 'Caller-chosen dedupe identity for this report, stored on the ticket. A repeat submission carrying the same key does NOT file a second ticket: the open ticket already holding that key is refreshed from this payload (title and the report fields overwritten, a "re-triggered" comment appended) and returned with `deduped: true` and `updated: true`. Triage state a human owns — status, priority, assignee, tags — is left untouched, and a `resolved`/`wont_fix` ticket no longer holds the key, so a failure that comes back after being closed files a fresh ticket. Omit it to fall back to the derived (title + `context.step`) dedupe, whose repeats return the existing ticket unchanged.'
15677
16424
  notify:
15678
16425
  anyOf:
15679
16426
  - $ref: '#/components/schemas/NotificationTarget'
@@ -15682,7 +16429,7 @@ components:
15682
16429
  required:
15683
16430
  - title
15684
16431
  additionalProperties: false
15685
- description: Body for `POST /ops/bug`. The same bug fields as `CreateBugRequest`, minus the `type` discriminator — the path already says what is being filed.
16432
+ description: Body for `POST /ops/bug`. The same bug fields as `CreateBugRequest`, minus the `type` discriminator — the path already says what is being filed, plus the public intake's own `dedupKey`.
15686
16433
  CreatePublicTicketResponse:
15687
16434
  type: object
15688
16435
  properties:
@@ -15695,10 +16442,13 @@ components:
15695
16442
  deduped:
15696
16443
  type: boolean
15697
16444
  description: '`true` when an open ticket already tracked this report and nothing was filed (HTTP 200). Absent on a fresh file (HTTP 201).'
16445
+ updated:
16446
+ type: boolean
16447
+ description: '`true` when the deduped ticket was REFRESHED from this payload and got a "re-triggered" comment — only ever set alongside `deduped` on a submission that carried a `dedupKey`. Absent when the existing ticket was returned untouched.'
15698
16448
  required:
15699
16449
  - number
15700
16450
  additionalProperties: false
15701
- description: 'Response for the public ticket intake. A fresh file returns `201` with `id` + `number`; a repeat of a report already tracked by an open ticket returns `200` with that ticket''s `number` and `deduped: true`.'
16451
+ description: 'Response for the public ticket intake. A fresh file returns `201` with `id` + `number`; a repeat of a report already tracked by an open ticket returns `200` with that ticket''s `number` and `deduped: true` — plus `updated: true` when a `dedupKey` re-triggered it and its fields were refreshed.'
15702
16452
  CreatePublicFeatureRequestRequest:
15703
16453
  type: object
15704
16454
  properties:
@@ -15741,10 +16491,11 @@ components:
15741
16491
  description: Emails of teammates to subscribe to this item's Slack pings at creation.
15742
16492
  tags:
15743
16493
  type: array
16494
+ maxItems: 50
15744
16495
  items:
15745
- type: string
16496
+ $ref: '#/components/schemas/TagName'
15746
16497
  default: []
15747
- description: Tag names to attach at creation (get-or-created by name, deduped case-insensitively).
16498
+ description: 'Tag names to label the item with, e.g. `["ios", "checkout"]`. Each name is matched case-insensitively against the project''s existing tags and reuses that tag (keeping its stored casing) when one matches; a name with no match creates a new project tag. Here the tags only label the ticket: the project''s tag rules (owner/status/agent routing) are not applied to a public report while it awaits approval.'
15748
16499
  reporterEmail:
15749
16500
  anyOf:
15750
16501
  - type: string
@@ -15769,6 +16520,10 @@ components:
15769
16520
  additionalProperties: {}
15770
16521
  - type: 'null'
15771
16522
  description: Arbitrary capture context, or `null`.
16523
+ dedupKey:
16524
+ type: string
16525
+ maxLength: 200
16526
+ description: 'Caller-chosen dedupe identity for this request, stored on the ticket. Behaves exactly as on `POST /ops/bug`: a repeat carrying the same key refreshes the open ticket already holding it (fields overwritten, a "re-triggered" comment appended) and returns `deduped: true` with `updated: true`, instead of filing a second one.'
15772
16527
  notify:
15773
16528
  anyOf:
15774
16529
  - $ref: '#/components/schemas/NotificationTarget'
@@ -15777,7 +16532,7 @@ components:
15777
16532
  required:
15778
16533
  - title
15779
16534
  additionalProperties: false
15780
- description: Body for `POST /ops/feature-request`. The same feature-request fields as `CreateFeatureRequestRequest`, minus the `type` discriminator — the path already says what is being filed.
16535
+ description: Body for `POST /ops/feature-request`. The same feature-request fields as `CreateFeatureRequestRequest`, minus the `type` discriminator — the path already says what is being filed, plus the public intake's own `dedupKey`.
15781
16536
  OpsItemAttachment:
15782
16537
  type: object
15783
16538
  properties:
@@ -16121,6 +16876,10 @@ components:
16121
16876
  type: string
16122
16877
  maxLength: 300
16123
16878
  description: The agent-run session id (e.g. Claude's `session_01…`), so the dashboard can deep-link to the exact run page. Omit when the harness has no session id.
16879
+ launch:
16880
+ type: boolean
16881
+ default: true
16882
+ description: 'Whether an AI ack may FIRE the agent''s connected trigger. Default `true`: an ack naming a connected agent, with no `sessionId`, starts a new provider run of that agent. Pass `false` to only record the ack — claim the item and open the run record — without starting anything, e.g. when you ARE that agent working the item yourself and have no session id to pass. Ignored on a human ack.'
16124
16883
  additionalProperties: false
16125
16884
  description: Body for `POST /api/admin/ops/{handle}/ack`. With `agent` set this is an AI ack — the named agent type must have a connected trigger connector in the project, or the call fails with `AGENT_NOT_CONNECTED` and instructions to query the available agents (`ops agents list`). `gemini` is accepted as an alias for `jules`. Without `agent` it is a HUMAN ack by the authenticated caller.
16126
16885
  AckOpsItemResponse:
@@ -16448,100 +17207,441 @@ components:
16448
17207
  description: ISO-8601 timestamp the work finished.
16449
17208
  sessionId:
16450
17209
  type: string
16451
- maxLength: 300
16452
- description: The agent-run session id, so the dashboard can deep-link to the run.
17210
+ maxLength: 300
17211
+ description: The agent-run session id, so the dashboard can deep-link to the run.
17212
+ required:
17213
+ - kind
17214
+ additionalProperties: false
17215
+ description: Body for `POST /api/admin/ops/{handle}/investigation`. `kind` is required; include at least one content field (`summary`/`findings`/`question`/`qaNotes`) — the panel renders whatever it gets.
17216
+ UpdateOpsInvestigationRequest:
17217
+ type: object
17218
+ properties:
17219
+ kind:
17220
+ type: string
17221
+ enum:
17222
+ - investigated
17223
+ - detected
17224
+ - question
17225
+ - ready_for_qa
17226
+ - working
17227
+ - note
17228
+ description: Reclassify the record's lifecycle stage (e.g. flip a `working` run into `investigated` once findings land).
17229
+ summary:
17230
+ type: string
17231
+ maxLength: 2000
17232
+ description: One-line summary of the record.
17233
+ findings:
17234
+ type: string
17235
+ maxLength: 50000
17236
+ description: The full findings write-up (markdown).
17237
+ question:
17238
+ type: string
17239
+ maxLength: 10000
17240
+ description: A blocking question for the team (markdown).
17241
+ qaNotes:
17242
+ type: string
17243
+ maxLength: 50000
17244
+ description: How to verify the fix — QA notes (markdown).
17245
+ model:
17246
+ type: string
17247
+ maxLength: 200
17248
+ description: The model the agent ran on.
17249
+ prNumber:
17250
+ type: integer
17251
+ description: A PR the record references.
17252
+ prUrl:
17253
+ type: string
17254
+ format: uri
17255
+ maxLength: 2000
17256
+ description: HTML URL of that PR.
17257
+ sources:
17258
+ type: array
17259
+ maxItems: 200
17260
+ items:
17261
+ type: object
17262
+ properties:
17263
+ path:
17264
+ type: string
17265
+ description: Repo-relative file path inspected.
17266
+ url:
17267
+ type: string
17268
+ description: External link consulted.
17269
+ label:
17270
+ type: string
17271
+ description: Human-readable label for the source.
17272
+ additionalProperties: false
17273
+ description: One file/link inspected while producing the record.
17274
+ description: The files/links inspected.
17275
+ confidence:
17276
+ type: string
17277
+ enum:
17278
+ - low
17279
+ - medium
17280
+ - high
17281
+ description: Self-reported confidence in the record.
17282
+ tokensUsed:
17283
+ type: integer
17284
+ description: Tokens the run consumed.
17285
+ durationMs:
17286
+ type: integer
17287
+ description: Run duration in milliseconds.
17288
+ visibility:
17289
+ type: string
17290
+ enum:
17291
+ - draft
17292
+ - published
17293
+ description: Record visibility. `draft` keeps it out of the panel; `published` surfaces it.
17294
+ completedAt:
17295
+ type: string
17296
+ description: ISO-8601 timestamp the work finished. Set it (or flip `kind` off `working`) to mark a run record done.
17297
+ sessionId:
17298
+ type: string
17299
+ maxLength: 300
17300
+ description: The agent-run session id, so the dashboard can deep-link to the run.
17301
+ additionalProperties: false
17302
+ minProperties: 1
17303
+ description: Body for `PATCH /api/admin/ops/{handle}/investigation/{investigationId}` — a partial update of one existing investigation record (typically the `working` run record you were handed). Send only the fields you want to change; at least one is required.
17304
+ OpsQuestionStatus:
17305
+ type: string
17306
+ enum:
17307
+ - open
17308
+ - completed
17309
+ - cancelled
17310
+ description: Where a question stands — `open` (waiting on a person), `completed` (answered), or `cancelled` (withdrawn by the asker).
17311
+ OpsQuestionAnswer:
17312
+ type: object
17313
+ properties:
17314
+ selected:
17315
+ type: array
17316
+ items:
17317
+ type: string
17318
+ description: The option labels the person picked (empty when they only answered "Other").
17319
+ other:
17320
+ anyOf:
17321
+ - type: string
17322
+ - type: 'null'
17323
+ description: The free-text "Other" answer, or `null`.
17324
+ required:
17325
+ - selected
17326
+ - other
17327
+ additionalProperties: false
17328
+ description: A person's answer to a question.
17329
+ OpsQuestionDelivery:
17330
+ type: string
17331
+ enum:
17332
+ - resume
17333
+ - poll
17334
+ description: How the answer gets back to the asker — `resume` re-fires the asking run's trigger connector on the item once every question that run asked is answered; `poll` fires nothing and the asker reads `GET .../questions`.
17335
+ OpsQuestion:
17336
+ type: object
17337
+ properties:
17338
+ id:
17339
+ type: string
17340
+ description: Question id.
17341
+ feedbackId:
17342
+ type: string
17343
+ description: The queue item the question hangs off.
17344
+ header:
17345
+ type: string
17346
+ description: Short chip label for the question.
17347
+ question:
17348
+ type: string
17349
+ description: The full question (markdown).
17350
+ options:
17351
+ type: array
17352
+ items:
17353
+ type: object
17354
+ properties:
17355
+ label:
17356
+ type: string
17357
+ description: The option's display text.
17358
+ description:
17359
+ anyOf:
17360
+ - type: string
17361
+ - type: 'null'
17362
+ description: What picking the option means, or `null`.
17363
+ required:
17364
+ - label
17365
+ - description
17366
+ additionalProperties: false
17367
+ description: One offered choice.
17368
+ description: The offered choices, in order.
17369
+ multiSelect:
17370
+ type: boolean
17371
+ description: Whether several options may be picked.
17372
+ status:
17373
+ $ref: '#/components/schemas/OpsQuestionStatus'
17374
+ answer:
17375
+ anyOf:
17376
+ - $ref: '#/components/schemas/OpsQuestionAnswer'
17377
+ - type: 'null'
17378
+ description: The person's answer, or `null` while the question is open (or was withdrawn).
17379
+ answeredBy:
17380
+ anyOf:
17381
+ - type: string
17382
+ - type: 'null'
17383
+ description: Email of the person who answered, or `null`.
17384
+ answeredAt:
17385
+ anyOf:
17386
+ - type: string
17387
+ - type: 'null'
17388
+ description: ISO-8601 timestamp of the answer, or `null`.
17389
+ agent:
17390
+ anyOf:
17391
+ - type: string
17392
+ enum:
17393
+ - jarvis
17394
+ - claude
17395
+ - cursor
17396
+ - copilot
17397
+ - jules
17398
+ - type: 'null'
17399
+ description: The agent that asked, or `null` (asked by a person or an unattributed key).
17400
+ askedBy:
17401
+ anyOf:
17402
+ - type: string
17403
+ - type: 'null'
17404
+ description: Email of the person who asked, when a human asked, or `null`.
17405
+ runId:
17406
+ anyOf:
17407
+ - type: string
17408
+ - type: 'null'
17409
+ description: The run (investigation record id) the question was asked under, or `null`.
17410
+ delivery:
17411
+ $ref: '#/components/schemas/OpsQuestionDelivery'
17412
+ deliveredAt:
17413
+ anyOf:
17414
+ - type: string
17415
+ - type: 'null'
17416
+ description: ISO-8601 timestamp the answer was re-delivered to the asking run (`resume` only), or `null`.
17417
+ deliveryError:
17418
+ anyOf:
17419
+ - type: string
17420
+ - type: 'null'
17421
+ description: Why a `resume` re-delivery failed, or `null`.
17422
+ createdAt:
17423
+ type: string
17424
+ description: ISO-8601 creation timestamp.
17425
+ updatedAt:
17426
+ type: string
17427
+ description: ISO-8601 last-update timestamp.
16453
17428
  required:
16454
- - kind
17429
+ - id
17430
+ - feedbackId
17431
+ - header
17432
+ - question
17433
+ - options
17434
+ - multiSelect
17435
+ - status
17436
+ - answer
17437
+ - answeredBy
17438
+ - answeredAt
17439
+ - agent
17440
+ - askedBy
17441
+ - runId
17442
+ - delivery
17443
+ - deliveredAt
17444
+ - deliveryError
17445
+ - createdAt
17446
+ - updatedAt
16455
17447
  additionalProperties: false
16456
- description: Body for `POST /api/admin/ops/{handle}/investigation`. `kind` is required; include at least one content field (`summary`/`findings`/`question`/`qaNotes`) — the panel renders whatever it gets.
16457
- UpdateOpsInvestigationRequest:
17448
+ description: One structured question on a queue item and, once answered, its answer.
17449
+ ListOpsQuestionsResponse:
17450
+ type: array
17451
+ items:
17452
+ $ref: '#/components/schemas/OpsQuestion'
17453
+ description: Response for `GET /api/admin/ops/{handle}/questions` — the item's questions, newest first (max 100).
17454
+ OpsQuestionOption:
16458
17455
  type: object
16459
17456
  properties:
16460
- kind:
17457
+ label:
16461
17458
  type: string
16462
- enum:
16463
- - investigated
16464
- - detected
16465
- - question
16466
- - ready_for_qa
16467
- - working
16468
- - note
16469
- description: Reclassify the record's lifecycle stage (e.g. flip a `working` run into `investigated` once findings land).
16470
- summary:
17459
+ minLength: 1
17460
+ maxLength: 120
17461
+ description: The option's display text — short (1–5 words). Answers name options by this label.
17462
+ description:
16471
17463
  type: string
16472
- maxLength: 2000
16473
- description: One-line summary of the record.
16474
- findings:
17464
+ maxLength: 500
17465
+ description: What picking this option means — its trade-off or consequence.
17466
+ required:
17467
+ - label
17468
+ additionalProperties: false
17469
+ description: One choice offered by a question — rendered as a radio, or a checkbox when the question is `multiSelect`.
17470
+ AskOpsQuestionInput:
17471
+ type: object
17472
+ properties:
17473
+ header:
16475
17474
  type: string
16476
- maxLength: 50000
16477
- description: The full findings write-up (markdown).
17475
+ minLength: 1
17476
+ maxLength: 24
17477
+ description: Very short chip label for the question (max 24 chars), e.g. `Auth method`.
16478
17478
  question:
16479
17479
  type: string
16480
- maxLength: 10000
16481
- description: A blocking question for the team (markdown).
16482
- qaNotes:
16483
- type: string
16484
- maxLength: 50000
16485
- description: How to verify the fix — QA notes (markdown).
16486
- model:
17480
+ minLength: 1
17481
+ maxLength: 2000
17482
+ description: The complete question, ending with a question mark (markdown).
17483
+ options:
17484
+ type: array
17485
+ minItems: 2
17486
+ maxItems: 4
17487
+ items:
17488
+ $ref: '#/components/schemas/OpsQuestionOption'
17489
+ description: 2–4 distinct choices. Do not add an "Other" option — a free-text answer is always offered.
17490
+ multiSelect:
17491
+ type: boolean
17492
+ default: false
17493
+ description: '`true` lets the person pick several options (checkboxes); `false` (the default) exactly one (radios).'
17494
+ required:
17495
+ - header
17496
+ - question
17497
+ - options
17498
+ additionalProperties: false
17499
+ description: One question to ask — the `AskUserQuestion` shape.
17500
+ AskOpsQuestionsRequest:
17501
+ type: object
17502
+ properties:
17503
+ questions:
17504
+ type: array
17505
+ minItems: 1
17506
+ maxItems: 4
17507
+ items:
17508
+ $ref: '#/components/schemas/AskOpsQuestionInput'
17509
+ description: 1–4 questions to ask at once.
17510
+ runId:
16487
17511
  type: string
16488
17512
  maxLength: 200
16489
- description: The model the agent ran on.
16490
- prNumber:
16491
- type: integer
16492
- description: A PR the record references.
16493
- prUrl:
17513
+ description: The run (investigation record id) you are working the item under — the `runId` an ack or a launch handed you. Ties the questions to that run so a `resume` delivery continues it.
17514
+ agent:
16494
17515
  type: string
16495
- format: uri
16496
- maxLength: 2000
16497
- description: HTML URL of that PR.
16498
- sources:
17516
+ enum:
17517
+ - jarvis
17518
+ - claude
17519
+ - cursor
17520
+ - copilot
17521
+ - jules
17522
+ description: The agent type asking — pass your own type when you are a coding agent. Defaults to the run's agent.
17523
+ delivery:
17524
+ $ref: '#/components/schemas/OpsQuestionDelivery'
17525
+ required:
17526
+ - questions
17527
+ additionalProperties: false
17528
+ description: Body for `POST /api/admin/ops/{handle}/questions`.
17529
+ AskOpsQuestionsResponse:
17530
+ type: array
17531
+ items:
17532
+ $ref: '#/components/schemas/OpsQuestion'
17533
+ description: Response for `POST /api/admin/ops/{handle}/questions` — the created questions, in the order asked.
17534
+ AnswerOpsQuestionRequest:
17535
+ type: object
17536
+ properties:
17537
+ selected:
16499
17538
  type: array
16500
- maxItems: 200
17539
+ maxItems: 4
16501
17540
  items:
16502
- type: object
16503
- properties:
16504
- path:
16505
- type: string
16506
- description: Repo-relative file path inspected.
16507
- url:
16508
- type: string
16509
- description: External link consulted.
16510
- label:
16511
- type: string
16512
- description: Human-readable label for the source.
16513
- additionalProperties: false
16514
- description: One file/link inspected while producing the record.
16515
- description: The files/links inspected.
16516
- confidence:
17541
+ type: string
17542
+ maxLength: 120
17543
+ description: The option labels picked — exactly one for a single-select question (unless answering only with `other`), one or more for `multiSelect`.
17544
+ other:
16517
17545
  type: string
16518
- enum:
16519
- - low
16520
- - medium
16521
- - high
16522
- description: Self-reported confidence in the record.
16523
- tokensUsed:
16524
- type: integer
16525
- description: Tokens the run consumed.
16526
- durationMs:
16527
- type: integer
16528
- description: Run duration in milliseconds.
16529
- visibility:
17546
+ maxLength: 10000
17547
+ description: A free-text "Other" answer.
17548
+ additionalProperties: false
17549
+ minProperties: 1
17550
+ description: Body for `POST /api/admin/ops/{handle}/questions/{questionId}/answer` — pick option labels, write a free-text answer, or both (multi-select only).
17551
+ OpsQaNoteKind:
17552
+ type: string
17553
+ enum:
17554
+ - narrowing
17555
+ - clarification
17556
+ - verification
17557
+ - correction
17558
+ description: What a QA note is — `narrowing` (only part of the bug still fails), `clarification` (changes what "fixed" means), `verification` (a pass/fail verdict on a fix) or `correction` (withdraws an earlier framing).
17559
+ OpsQaNoteVerdict:
17560
+ type: string
17561
+ enum:
17562
+ - pass
17563
+ - fail
17564
+ description: The outcome a `verification` note records.
17565
+ OpsQaNote:
17566
+ type: object
17567
+ properties:
17568
+ id:
16530
17569
  type: string
16531
- enum:
16532
- - draft
16533
- - published
16534
- description: Record visibility. `draft` keeps it out of the panel; `published` surfaces it.
16535
- completedAt:
17570
+ description: Note id.
17571
+ feedbackId:
16536
17572
  type: string
16537
- description: ISO-8601 timestamp the work finished. Set it (or flip `kind` off `working`) to mark a run record done.
16538
- sessionId:
17573
+ description: The queue item the note hangs off.
17574
+ kind:
17575
+ $ref: '#/components/schemas/OpsQaNoteKind'
17576
+ verdict:
17577
+ anyOf:
17578
+ - $ref: '#/components/schemas/OpsQaNoteVerdict'
17579
+ - type: 'null'
17580
+ description: The pass/fail verdict of a `verification` note; `null` for every other kind.
17581
+ body:
16539
17582
  type: string
16540
- maxLength: 300
16541
- description: The agent-run session id, so the dashboard can deep-link to the run.
17583
+ description: The note (markdown) — for a `verification`, what was checked.
17584
+ authorEmail:
17585
+ anyOf:
17586
+ - type: string
17587
+ - type: 'null'
17588
+ description: Email of the person (or the key's actor) who wrote the note, or `null` when an agent wrote it.
17589
+ authorAgent:
17590
+ anyOf:
17591
+ - type: string
17592
+ enum:
17593
+ - jarvis
17594
+ - claude
17595
+ - cursor
17596
+ - copilot
17597
+ - jules
17598
+ - type: 'null'
17599
+ description: The agent type that wrote the note (QA automation), or `null` for a person.
17600
+ createdAt:
17601
+ type: string
17602
+ description: ISO-8601 creation timestamp.
17603
+ required:
17604
+ - id
17605
+ - feedbackId
17606
+ - kind
17607
+ - verdict
17608
+ - body
17609
+ - authorEmail
17610
+ - authorAgent
17611
+ - createdAt
16542
17612
  additionalProperties: false
16543
- minProperties: 1
16544
- description: Body for `PATCH /api/admin/ops/{handle}/investigation/{investigationId}` — a partial update of one existing investigation record (typically the `working` run record you were handed). Send only the fields you want to change; at least one is required.
17613
+ description: One typed, append-only QA note on a queue item.
17614
+ ListOpsQaNotesResponse:
17615
+ type: array
17616
+ items:
17617
+ $ref: '#/components/schemas/OpsQaNote'
17618
+ description: Response for `GET /api/admin/ops/{handle}/qa-notes` — the item's QA notes, newest first (max 200).
17619
+ CreateOpsQaNoteRequest:
17620
+ type: object
17621
+ properties:
17622
+ kind:
17623
+ $ref: '#/components/schemas/OpsQaNoteKind'
17624
+ body:
17625
+ type: string
17626
+ minLength: 1
17627
+ maxLength: 10000
17628
+ description: The note (markdown).
17629
+ verdict:
17630
+ $ref: '#/components/schemas/OpsQaNoteVerdict'
17631
+ agent:
17632
+ type: string
17633
+ enum:
17634
+ - jarvis
17635
+ - claude
17636
+ - cursor
17637
+ - copilot
17638
+ - jules
17639
+ description: The agent type writing the note — pass your own type when you are QA automation. Omitted, the note is attributed to the caller's identity.
17640
+ required:
17641
+ - kind
17642
+ - body
17643
+ additionalProperties: false
17644
+ description: Body for `POST /api/admin/ops/{handle}/qa-notes`. A `verification` note requires `verdict`; every other kind rejects it.
16545
17645
  OpsAgentProfile:
16546
17646
  type: object
16547
17647
  properties:
@@ -16735,6 +17835,59 @@ components:
16735
17835
  - channels
16736
17836
  additionalProperties: false
16737
17837
  description: Response for `GET /api/admin/slack/channels`.
17838
+ AlertCompositeSpec:
17839
+ type: object
17840
+ description: 'What a `composite` rule is a boolean over. `rules` are sibling alert-rule ids in the same project and `op` is how their states combine. A child''s state is what the current pass concluded about it, falling back to whether it has an open instance — so a composite still means something on a tick where a child could not be evaluated, which is exactly the tick a "two of these are broken at once" rule is for. One level deep: a child may not itself be composite.'
17841
+ properties:
17842
+ op:
17843
+ type: string
17844
+ enum:
17845
+ - and
17846
+ - or
17847
+ description: How the children's states combine.
17848
+ rules:
17849
+ type: array
17850
+ minItems: 2
17851
+ items:
17852
+ type: string
17853
+ minLength: 1
17854
+ description: Ids of the alert rules to combine. Stored sorted and de-duplicated.
17855
+ required:
17856
+ - op
17857
+ - rules
17858
+ additionalProperties: false
17859
+ AlertRuleNotify:
17860
+ anyOf:
17861
+ - type: object
17862
+ properties:
17863
+ slackChannel:
17864
+ anyOf:
17865
+ - type: object
17866
+ properties:
17867
+ id:
17868
+ type: string
17869
+ minLength: 1
17870
+ name:
17871
+ type: string
17872
+ minLength: 1
17873
+ required:
17874
+ - id
17875
+ - name
17876
+ additionalProperties: false
17877
+ - type: 'null'
17878
+ email:
17879
+ anyOf:
17880
+ - type: string
17881
+ format: email
17882
+ - type: 'null'
17883
+ inApp:
17884
+ anyOf:
17885
+ - type: boolean
17886
+ - type: 'null'
17887
+ description: Whether the in-app notification (the dashboard bell and Home) is recorded when the rule fires. Absent or `null` means on; `false` skips it for this rule.
17888
+ additionalProperties: false
17889
+ - type: 'null'
17890
+ description: Delivery target for a rule's alert; `null` = use the project default. The `NotificationTarget` shape plus the in-app switch.
16738
17891
  ListAlertRulesResponse:
16739
17892
  type: array
16740
17893
  items:
@@ -16753,23 +17906,107 @@ components:
16753
17906
  anyOf:
16754
17907
  - type: string
16755
17908
  - type: 'null'
16756
- description: Display name of the bound metric, or `null` if the metric no longer exists.
16757
- comparator:
16758
- type: string
16759
- enum:
16760
- - gt
16761
- - gte
16762
- - lt
16763
- - lte
16764
- description: How the metric value is compared to the threshold (gt/gte/lt/lte).
16765
- threshold:
16766
- type: number
16767
- description: Threshold the metric value is compared against.
17909
+ description: Display name of the bound metric, or `null` if the metric no longer exists.
17910
+ kind:
17911
+ type: string
17912
+ enum:
17913
+ - normal
17914
+ - anomaly
17915
+ - outliers
17916
+ - no_data
17917
+ - composite
17918
+ description: What the rule watches for. `normal` compares the metric's own value — against `threshold` via `comparator`, or against the [`rangeMin`, `rangeMax`] corridor it must stay inside. `anomaly` compares it against ITS OWN seasonal baseline in sigmas, which is the same fit a chart draws as the band. `outliers` compares each `by()` group against its peers in the same bucket, so it needs a grouped metric. `no_data` fires when the metric records nothing at all for `noDataMinutes` — a count of zero is not silence, and the two are distinguished by the reader's presence columns. `composite` is a boolean over OTHER rules' current state and watches no metric of its own.
17919
+ comparator:
17920
+ type: string
17921
+ enum:
17922
+ - gt
17923
+ - gte
17924
+ - lt
17925
+ - lte
17926
+ description: How the metric value is compared to the threshold (gt/gte/lt/lte). Read only by a `normal` rule with no range set.
17927
+ threshold:
17928
+ type: number
17929
+ description: Threshold the metric value is compared against. Read only by a `normal` rule with no range set.
17930
+ rangeMin:
17931
+ anyOf:
17932
+ - type: number
17933
+ - type: 'null'
17934
+ description: Lower edge of the corridor a `normal` rule must stay inside; `null` if the rule is judged by comparator and threshold.
17935
+ rangeMax:
17936
+ anyOf:
17937
+ - type: number
17938
+ - type: 'null'
17939
+ description: Upper edge of the corridor a `normal` rule must stay inside; `null` if the rule is judged by comparator and threshold.
17940
+ sigma:
17941
+ anyOf:
17942
+ - type: number
17943
+ - type: 'null'
17944
+ description: How far from normal is too far, for `anomaly` and `outliers`, in sigma-equivalents. `null` takes the default of 3, which is also the width of the band a chart draws.
17945
+ direction:
17946
+ anyOf:
17947
+ - type: string
17948
+ enum:
17949
+ - above
17950
+ - below
17951
+ - either
17952
+ - type: 'null'
17953
+ description: Which side of the baseline an `anomaly` or `outliers` rule watches. `null` means either.
17954
+ sustained:
17955
+ type: boolean
17956
+ description: Whether the rule is judged by ACCUMULATED departure rather than bucket by bucket. `anomaly` and `outliers` only.
17957
+ warnThreshold:
17958
+ anyOf:
17959
+ - type: number
17960
+ - type: 'null'
17961
+ description: The milder bound, in the same unit the kind judges in. `null` means the rule has one level.
17962
+ recoveryThreshold:
17963
+ anyOf:
17964
+ - type: number
17965
+ - type: 'null'
17966
+ description: What the metric must get back to before a live alert closes. `null` means no hysteresis — the firing bound clears it.
17967
+ groupAlerts:
17968
+ type: boolean
17969
+ description: Whether the rule fires one alert per `by()` group rather than one for the whole metric.
17970
+ maxGroups:
17971
+ anyOf:
17972
+ - type: integer
17973
+ - type: 'null'
17974
+ description: How many groups this rule may alert on at once. `null` takes the shipped cap of 10.
17975
+ noDataMinutes:
17976
+ anyOf:
17977
+ - type: integer
17978
+ - type: 'null'
17979
+ description: How long the silence must last for a `no_data` rule. `null` on every other kind.
17980
+ delayMinutes:
17981
+ anyOf:
17982
+ - type: integer
17983
+ - type: 'null'
17984
+ description: How far behind live the evaluated window is held, on top of the reader's settle grace. `null` means no extra delay.
17985
+ autoResolveMinutes:
17986
+ anyOf:
17987
+ - type: integer
17988
+ - type: 'null'
17989
+ description: Close a live instance that has gone this long without a fresh verdict. `null` never auto-resolves.
17990
+ composite:
17991
+ anyOf:
17992
+ - $ref: '#/components/schemas/AlertCompositeSpec'
17993
+ - type: 'null'
17994
+ description: For a `composite` rule, the child rules and how their states combine. `null` on every other kind.
16768
17995
  windowHours:
16769
17996
  type: integer
16770
17997
  minimum: -9007199254740991
16771
17998
  maximum: 9007199254740991
16772
17999
  description: Lookback window (hours) the metric is aggregated over.
18000
+ bucketMinutes:
18001
+ anyOf:
18002
+ - type: integer
18003
+ - type: 'null'
18004
+ description: Width of the buckets the window is split into, in minutes. `null` divides the window into 12 equal buckets.
18005
+ requiredBuckets:
18006
+ anyOf:
18007
+ - type: integer
18008
+ - type: 'null'
18009
+ description: How many buckets must breach before the rule fires. `null` means every bucket that had data.
16773
18010
  severity:
16774
18011
  type: string
16775
18012
  enum:
@@ -16781,7 +18018,7 @@ components:
16781
18018
  type: boolean
16782
18019
  description: Whether the rule is evaluated by the cron.
16783
18020
  notify:
16784
- $ref: '#/components/schemas/NotificationTarget'
18021
+ $ref: '#/components/schemas/AlertRuleNotify'
16785
18022
  createdAt:
16786
18023
  type: string
16787
18024
  description: ISO-8601 timestamp of creation.
@@ -16793,9 +18030,25 @@ components:
16793
18030
  - name
16794
18031
  - metricId
16795
18032
  - metricName
18033
+ - kind
16796
18034
  - comparator
16797
18035
  - threshold
18036
+ - rangeMin
18037
+ - rangeMax
18038
+ - sigma
18039
+ - direction
18040
+ - sustained
18041
+ - warnThreshold
18042
+ - recoveryThreshold
18043
+ - groupAlerts
18044
+ - maxGroups
18045
+ - noDataMinutes
18046
+ - delayMinutes
18047
+ - autoResolveMinutes
18048
+ - composite
16798
18049
  - windowHours
18050
+ - bucketMinutes
18051
+ - requiredBuckets
16799
18052
  - severity
16800
18053
  - enabled
16801
18054
  - notify
@@ -16815,26 +18068,122 @@ components:
16815
18068
  type: string
16816
18069
  minLength: 1
16817
18070
  description: Id of the metric to evaluate.
18071
+ kind:
18072
+ default: normal
18073
+ description: 'What the rule watches for. `normal` compares the metric''s own value — against `threshold` via `comparator`, or against the [`rangeMin`, `rangeMax`] corridor it must stay inside. `anomaly` compares it against its own seasonal baseline in sigmas: the same fit a chart draws as the band, so a point outside the drawn band is exactly a breaching bucket, and one metric can back rules at several sigmas. `outliers` compares each `by()` group against its peers in the same bucket and is refused on a metric with no grouping.'
18074
+ type: string
18075
+ enum:
18076
+ - normal
18077
+ - anomaly
18078
+ - outliers
18079
+ - no_data
18080
+ - composite
16818
18081
  comparator:
18082
+ default: gt
18083
+ description: How the metric value is compared to the threshold. Read only by a `normal` rule with no range set.
16819
18084
  type: string
16820
18085
  enum:
16821
18086
  - gt
16822
18087
  - gte
16823
18088
  - lt
16824
18089
  - lte
16825
- description: How the metric value is compared to the threshold (gt/gte/lt/lte).
16826
18090
  threshold:
16827
18091
  type: number
16828
- description: Threshold the metric value is compared against.
18092
+ description: Threshold the metric value is compared against. Required for a `normal` rule unless a range is given; ignored by every other kind.
18093
+ rangeMin:
18094
+ anyOf:
18095
+ - type: number
18096
+ - type: 'null'
18097
+ description: Lower edge of the corridor the metric must stay inside. Setting either bound switches a `normal` rule off `comparator`/`threshold` and onto the range, which breaches on LEAVING it in either direction. One bound alone is a one-sided range.
18098
+ rangeMax:
18099
+ anyOf:
18100
+ - type: number
18101
+ - type: 'null'
18102
+ description: Upper edge of the corridor the metric must stay inside.
18103
+ sigma:
18104
+ anyOf:
18105
+ - type: number
18106
+ exclusiveMinimum: 0
18107
+ - type: 'null'
18108
+ description: How far from normal is too far, for `anomaly` and `outliers`, in sigma-equivalents. Omit (or `null`) for 3, which is also the half-width of the band a chart draws — so an unconfigured rule fires exactly where the picture says it would.
18109
+ direction:
18110
+ anyOf:
18111
+ - type: string
18112
+ enum:
18113
+ - above
18114
+ - below
18115
+ - either
18116
+ - type: 'null'
18117
+ description: Which side of the baseline an `anomaly` or `outliers` rule watches. Omit (or `null`) for either, which is what "is this unusual" means; name a side for a metric that is only bad in one direction.
18118
+ sustained:
18119
+ default: false
18120
+ type: boolean
18121
+ description: 'Judge the window by ACCUMULATED departure instead of bucket by bucket. The ordinary check asks every bucket to be past `sigma`, which a slow regression never manages: a metric running 1.5σ worse than normal since Tuesday puts no single bucket past 3σ and pages nobody. A sustained rule adds up each bucket''s excess over half a sigma and fires once the total passes twice `sigma` — four buckets at 2σ do it, a lone 3σ spike does not. `anomaly` and `outliers` only, and refused alongside `requiredBuckets`: the accumulated bar IS the evidence bar, and a second one counted in buckets would silently override it.'
16829
18122
  windowHours:
16830
18123
  default: 24
16831
18124
  description: Lookback window (hours) the metric is aggregated over. 1–720.
16832
18125
  type: integer
16833
18126
  minimum: 1
16834
18127
  maximum: 720
18128
+ bucketMinutes:
18129
+ anyOf:
18130
+ - type: integer
18131
+ minimum: 1
18132
+ maximum: 43200
18133
+ - type: 'null'
18134
+ description: Width of the buckets the window is split into, in minutes. Omit (or `null`) to divide the window into 12 equal buckets. The rule is judged per bucket, so this is the resolution at which "sustained" is measured — a short bucket asks the condition to hold through finer detail.
18135
+ requiredBuckets:
18136
+ anyOf:
18137
+ - type: integer
18138
+ minimum: 1
18139
+ - type: 'null'
18140
+ description: How many buckets must breach before the rule fires. Omit (or `null`) to require every bucket that had data. Buckets with nothing in them are excluded before this is counted, so on a sparse metric `null` can mean a single bucket — set this to 2 or more where one lone sample must never page.
18141
+ warnThreshold:
18142
+ anyOf:
18143
+ - type: number
18144
+ - type: 'null'
18145
+ description: 'The milder bound — a second level, in the same unit the kind judges in, that raises a quieter alert before the firing level is reached. Must be strictly milder than the level the rule fires at, or it could never fire on its own. Refused on a range rule and on a `sustained` one: neither condition is a single number, so there is nothing to substitute.'
18146
+ recoveryThreshold:
18147
+ anyOf:
18148
+ - type: number
18149
+ - type: 'null'
18150
+ description: What the metric must get back to before a live alert closes. Without it a metric sitting on its threshold pages and clears once per tick. Must be on the safe side of the firing level, or equal to it. Omit (or `null`) for no hysteresis.
18151
+ groupAlerts:
18152
+ default: false
18153
+ type: boolean
18154
+ description: Fire one alert per `by()` group instead of one for the whole metric. The firing key becomes (rule, group), so each group raises, dedupes and recovers on its own. Refused on a metric with no `by()`, and on `no_data` — a group that went silent has no rows left to be missing from.
18155
+ maxGroups:
18156
+ anyOf:
18157
+ - type: integer
18158
+ minimum: 1
18159
+ maximum: 50
18160
+ - type: 'null'
18161
+ description: How many groups this rule may alert on at once. Omit (or `null`) for 10. Past the cap the worst groups fire and the count of the rest is stated on each ticket — a group set is customer data, and an uncapped rule on `by(user_id)` would file a ticket per user.
18162
+ noDataMinutes:
18163
+ anyOf:
18164
+ - type: integer
18165
+ minimum: 5
18166
+ maximum: 43200
18167
+ - type: 'null'
18168
+ description: 'For a `no_data` rule: how long the silence must last. Omit (or `null`) for 15 minutes. Five is the floor — below it, ordinary ingest lag empties the trailing bucket and reads as an outage.'
18169
+ delayMinutes:
18170
+ anyOf:
18171
+ - type: integer
18172
+ minimum: 1
18173
+ maximum: 1440
18174
+ - type: 'null'
18175
+ description: Hold the evaluated window back this far behind live, on top of the reader's own settle grace. For a metric assembled from a source that lands in batches, whose trailing buckets are legitimately incomplete for longer than the grace covers.
18176
+ autoResolveMinutes:
18177
+ anyOf:
18178
+ - type: integer
18179
+ minimum: 1
18180
+ - type: 'null'
18181
+ description: Close a live instance that has gone this long without a fresh verdict. A rule that cannot reach a verdict deliberately leaves its alert alone, which is right for a tick and wrong for a week. Never closes an instance that is currently breaching. Omit (or `null`) to never auto-resolve.
18182
+ composite:
18183
+ $ref: '#/components/schemas/AlertCompositeSpec'
16835
18184
  severity:
16836
18185
  default: warn
16837
- description: Severity of the raised alert.
18186
+ description: Severity of the raised alert. A `warnThreshold` breach opens one step quieter than this.
16838
18187
  type: string
16839
18188
  enum:
16840
18189
  - danger
@@ -16845,12 +18194,10 @@ components:
16845
18194
  description: Whether the rule is evaluated by the cron.
16846
18195
  type: boolean
16847
18196
  notify:
16848
- $ref: '#/components/schemas/NotificationTarget'
18197
+ $ref: '#/components/schemas/AlertRuleNotify'
16849
18198
  required:
16850
18199
  - name
16851
18200
  - metricId
16852
- - comparator
16853
- - threshold
16854
18201
  additionalProperties: false
16855
18202
  CreateAlertRuleResponse:
16856
18203
  type: object
@@ -16877,6 +18224,14 @@ components:
16877
18224
  type: string
16878
18225
  minLength: 1
16879
18226
  maxLength: 120
18227
+ kind:
18228
+ type: string
18229
+ enum:
18230
+ - normal
18231
+ - anomaly
18232
+ - outliers
18233
+ - no_data
18234
+ - composite
16880
18235
  comparator:
16881
18236
  type: string
16882
18237
  enum:
@@ -16886,10 +18241,93 @@ components:
16886
18241
  - lte
16887
18242
  threshold:
16888
18243
  type: number
18244
+ rangeMin:
18245
+ anyOf:
18246
+ - type: number
18247
+ - type: 'null'
18248
+ description: Lower edge of the corridor; `null` drops it, returning the rule to comparator and threshold when both bounds are gone.
18249
+ rangeMax:
18250
+ anyOf:
18251
+ - type: number
18252
+ - type: 'null'
18253
+ description: Upper edge of the corridor; `null` drops it.
18254
+ sigma:
18255
+ anyOf:
18256
+ - type: number
18257
+ exclusiveMinimum: 0
18258
+ - type: 'null'
18259
+ description: Sigmas an `anomaly` or `outliers` rule fires at; `null` restores the default of 3.
18260
+ direction:
18261
+ anyOf:
18262
+ - type: string
18263
+ enum:
18264
+ - above
18265
+ - below
18266
+ - either
18267
+ - type: 'null'
18268
+ description: Which side an `anomaly` or `outliers` rule watches; `null` restores either.
18269
+ sustained:
18270
+ type: boolean
18271
+ description: Judge the window by accumulated departure rather than bucket by bucket. `anomaly` and `outliers` only; refused alongside `requiredBuckets`.
18272
+ warnThreshold:
18273
+ anyOf:
18274
+ - type: number
18275
+ - type: 'null'
18276
+ description: The milder bound; `null` drops the warning level, leaving the rule with one.
18277
+ recoveryThreshold:
18278
+ anyOf:
18279
+ - type: number
18280
+ - type: 'null'
18281
+ description: What the metric must get back to before a live alert closes; `null` drops the hysteresis.
18282
+ groupAlerts:
18283
+ type: boolean
18284
+ description: Fire one alert per `by()` group. Refused on a metric with no grouping.
18285
+ maxGroups:
18286
+ anyOf:
18287
+ - type: integer
18288
+ minimum: 1
18289
+ maximum: 50
18290
+ - type: 'null'
18291
+ description: How many groups may alert at once; `null` restores the default of 10.
18292
+ noDataMinutes:
18293
+ anyOf:
18294
+ - type: integer
18295
+ minimum: 5
18296
+ maximum: 43200
18297
+ - type: 'null'
18298
+ description: How long a `no_data` rule's silence must last; `null` restores 15 minutes.
18299
+ delayMinutes:
18300
+ anyOf:
18301
+ - type: integer
18302
+ minimum: 1
18303
+ maximum: 1440
18304
+ - type: 'null'
18305
+ description: How far behind live the window is held; `null` drops the delay.
18306
+ autoResolveMinutes:
18307
+ anyOf:
18308
+ - type: integer
18309
+ minimum: 1
18310
+ - type: 'null'
18311
+ description: Close a stale live instance after this long; `null` never auto-resolves.
18312
+ composite:
18313
+ $ref: '#/components/schemas/AlertCompositeSpec'
16889
18314
  windowHours:
16890
18315
  type: integer
16891
18316
  minimum: 1
16892
18317
  maximum: 720
18318
+ bucketMinutes:
18319
+ anyOf:
18320
+ - type: integer
18321
+ minimum: 1
18322
+ maximum: 43200
18323
+ - type: 'null'
18324
+ description: Bucket width in minutes; `null` restores the default 12 buckets per window.
18325
+ requiredBuckets:
18326
+ anyOf:
18327
+ - type: integer
18328
+ minimum: 1
18329
+ - type: 'null'
18330
+ description: Buckets that must breach to fire; `null` restores "every bucket that had data".
16893
18331
  severity:
16894
18332
  type: string
16895
18333
  enum:
@@ -16899,7 +18337,7 @@ components:
16899
18337
  enabled:
16900
18338
  type: boolean
16901
18339
  notify:
16902
- $ref: '#/components/schemas/NotificationTarget'
18340
+ $ref: '#/components/schemas/AlertRuleNotify'
16903
18341
  additionalProperties: false
16904
18342
  UpdateAlertRuleResponse:
16905
18343
  type: object
@@ -16913,13 +18351,15 @@ components:
16913
18351
  AlertApiRow:
16914
18352
  type: object
16915
18353
  description: |-
16916
- One FIRED alert — a condition the platform observed and raised, not the rule that defines it (alert *rules* are the `/api/admin/alert-rules` resource). Alerts are never filed by hand: the UI (killswitch flips) and the worker (analysis consumer + alerts cron) raise them, keyed by `(source, dedupeKey)` so re-raising refreshes the active row and a clearing condition auto-resolves it.
18354
+ One FIRED alert — an INSTANCE of a condition the platform observed, not the rule that defines it (alert *rules* are the `/api/admin/alert-rules` resource). Instances are never filed by hand: the UI (killswitch flips) and the worker (analysis consumer + alerts cron) open them, keyed by `(source, dedupeKey)`, and at most one instance per condition is open at a time — re-raising refreshes the open one, and the condition clearing closes it.
18355
+
18356
+ An instance IS an ops queue item (`/api/admin/ops` with `type: "alert"`) seen through the alert lens: same `id`, `severity` is that item's `priority`, and `status` is its work status projected onto three states. Resolving the queue item and clearing the alert are therefore the same act.
16917
18357
 
16918
18358
  Every field is always present on the wire; nullable fields are `null`, never absent.
16919
18359
  properties:
16920
18360
  id:
16921
18361
  type: string
16922
- description: Stable opaque alert id.
18362
+ description: Stable opaque instance id — also the id of the ops queue item this instance is.
16923
18363
  source:
16924
18364
  type: string
16925
18365
  enum:
@@ -16940,10 +18380,10 @@ components:
16940
18380
  - danger
16941
18381
  - warn
16942
18382
  - info
16943
- description: How loud the alert is — `danger` pages, `warn` is the default, `info` is FYI-only.
18383
+ description: How loud the alert is — `danger` pages, `warn` is the default, `info` is FYI-only. Severity belongs to the RULE and seeds the instance's queue `priority` (`danger`→`critical`, `warn`→`high`, `info`→`medium`); it is read back from there, so re-prioritising the queue item changes what this reports.
16944
18384
  dedupeKey:
16945
18385
  type: string
16946
- description: Idempotency + reconcile key within `(project, source)` — re-raising the same condition refreshes the existing active row instead of stacking duplicates, and the condition clearing flips that row to `resolved`.
18386
+ description: Idempotency + reconcile key within `(project, source)` — re-raising the same condition refreshes the open instance instead of stacking duplicates, and the condition clearing closes it. A later firing of the same condition opens a NEW instance with its own `id`.
16947
18387
  title:
16948
18388
  type: string
16949
18389
  description: Short human-readable headline (what fired).
@@ -16966,10 +18406,10 @@ components:
16966
18406
  - active
16967
18407
  - resolved
16968
18408
  - dismissed
16969
- description: Lifecycle state. `active` means the condition currently holds (shown as "Firing" in the UI); `resolved` means it cleared (auto, when the raiser reconciles, or by hand); `dismissed` means a human waved it off.
18409
+ description: Lifecycle state, projected from the queue item's work status. `active` means the item is still open, i.e. the condition holds (shown as "Firing" in the UI); `resolved` means it cleared (auto, when the raiser reconciles, or by hand); `dismissed` means a human waved it off (the item is closed `wont_fix`).
16970
18410
  createdAt:
16971
18411
  type: string
16972
- description: ISO-8601 timestamp the alert was first raised.
18412
+ description: ISO-8601 timestamp this instance opened — when the condition started firing.
16973
18413
  resolvedAt:
16974
18414
  description: ISO-8601 timestamp the alert flipped to `resolved`, or `null` while it hasn't.
16975
18415
  anyOf:
@@ -17031,7 +18471,7 @@ components:
17031
18471
  - active
17032
18472
  - resolved
17033
18473
  - dismissed
17034
- description: New lifecycle state. `resolved` / `dismissed` stamp their timestamp; `active` re-opens and clears both.
18474
+ description: New lifecycle state, written through to the queue item (`resolved` / `wont_fix` / `open`). `resolved` / `dismissed` stamp their timestamp; `active` re-opens and clears both.
17035
18475
  assigneeId:
17036
18476
  description: PERSON owner — a `users.id`, or `null` to clear the assignment.
17037
18477
  anyOf:
@@ -17217,6 +18657,12 @@ components:
17217
18657
  - type: boolean
17218
18658
  - type: number
17219
18659
  description: Whether key-authenticated public ticket creation (`POST /cli/report`) is allowed into this project. Off by default.
18660
+ errorAutocloseDays:
18661
+ type: integer
18662
+ description: Days an `open` tracked error may go unseen before the nightly sweep auto-resolves it. `0` disables auto-close for this project. Ingest reopens a resolved error the moment it recurs, so this never hides a live issue.
18663
+ errorTicketMinOccurrences:
18664
+ type: integer
18665
+ description: Occurrence count a tracked error must cross before an `error` ticket is auto-filed into the ops queue. Customising it requires a paid plan; unentitled projects always run at the platform default of 10.
17220
18666
  stripeSubscriptionId:
17221
18667
  anyOf:
17222
18668
  - type: string
@@ -17296,6 +18742,8 @@ components:
17296
18742
  - moduleUser
17297
18743
  - moduleEvents
17298
18744
  - allowPublicTickets
18745
+ - errorAutocloseDays
18746
+ - errorTicketMinOccurrences
17299
18747
  - slug
17300
18748
  - logo
17301
18749
  - defaultEnv
@@ -17514,6 +18962,16 @@ components:
17514
18962
  minimum: 0.0001
17515
18963
  maximum: 0.05
17516
18964
  description: SRM chi-square p-value below which the run is called invalid.
18965
+ errorAutocloseDays:
18966
+ type: integer
18967
+ minimum: 0
18968
+ maximum: 365
18969
+ description: Days an `open` tracked error may go unseen before the nightly sweep auto-resolves it. `0` disables auto-close for this project.
18970
+ errorTicketMinOccurrences:
18971
+ type: integer
18972
+ minimum: 1
18973
+ maximum: 10000
18974
+ description: Occurrence count a tracked error must cross before an `error` ticket is auto-filed into the ops queue. Requires a paid plan — the request is rejected with 403 on a plan without the `ops_auto_error_issues` entitlement.
17517
18975
  additionalProperties: false
17518
18976
  description: Body for `PATCH /api/admin/projects/{id}`. Partial — every field is optional; only supplied fields change. The project id in the path must match the caller's own project.
17519
18977
  ListI18nProfilesResponse:
@@ -18582,21 +20040,50 @@ components:
18582
20040
  - config
18583
20041
  additionalProperties: false
18584
20042
  description: Create a Claude Code routine trigger in one shot. Idempotent by `config.routineId` — re-creating with the same routine id updates the existing row.
20043
+ CursorTriggerRepo:
20044
+ type: object
20045
+ properties:
20046
+ url:
20047
+ type: string
20048
+ format: uri
20049
+ description: Repo the cloud agent runs against, e.g. `https://github.com/owner/repo`.
20050
+ startingRef:
20051
+ type: string
20052
+ minLength: 1
20053
+ description: Optional branch this repo's runs start from. Omit for the repo's default branch.
20054
+ required:
20055
+ - url
20056
+ additionalProperties: false
20057
+ description: One repo a trigger runs against, with the branch its runs start from.
18585
20058
  CursorTriggerConfig:
18586
20059
  type: object
18587
20060
  properties:
18588
20061
  repoUrl:
18589
20062
  type: string
18590
20063
  format: uri
18591
- description: Repo the cloud agent runs against, e.g. `https://github.com/owner/repo`. Idempotency key for the connector.
20064
+ description: '**First** repo the cloud agent runs against, e.g. `https://github.com/owner/repo`. Idempotency key for the connector, and always a mirror of `repos[0].url` when `repos` is supplied.'
18592
20065
  startingRef:
18593
20066
  type: string
18594
20067
  minLength: 1
18595
- description: Optional git ref the run starts from.
20068
+ description: Optional git ref the run starts from. Mirrors `repos[0].startingRef`. Pinned onto the Cursor agent at creation, so changing it provisions a replacement agent.
20069
+ repos:
20070
+ type: array
20071
+ minItems: 1
20072
+ items:
20073
+ $ref: '#/components/schemas/CursorTriggerRepo'
20074
+ description: Every repo this trigger runs against, each with its own starting branch — Cursor launches ONE agent across the whole set. Omit for a single-repo trigger, which is then described by `repoUrl`/`startingRef` alone; when supplied, the first entry must match them. Changing the set re-provisions the agent (its repos are pinned at creation).
18596
20075
  projectId:
18597
20076
  type: string
18598
20077
  minLength: 1
18599
20078
  description: Shipeasy project id the run targets.
20079
+ agentId:
20080
+ type: string
20081
+ minLength: 1
20082
+ description: Server-managed. The reusable Cursor cloud agent (`bc-…`) Shipeasy provisions once at connect and then starts every run on. A supplied value is ignored on create; if Cursor stops recognising the id (404), the next fire provisions a replacement and stores it here.
20083
+ fireText:
20084
+ type: string
20085
+ minLength: 1
20086
+ description: Optional standing instruction prepended to every run's prompt — the trigger's base prompt. Lets two triggers on different repos steer their runs differently without changing the `/shipeasy-ops-work` task block.
18600
20087
  required:
18601
20088
  - repoUrl
18602
20089
  - projectId
@@ -18631,7 +20118,7 @@ components:
18631
20118
  opsKey:
18632
20119
  type: string
18633
20120
  minLength: 1
18634
- description: Restricted Shipeasy ops key, injected into the run as `SHIPEASY_CLI_TOKEN` via the launch envVars (secret). Encrypted; never returned.
20121
+ description: Restricted Shipeasy ops key (secret). Sent as the Bearer for the Shipeasy MCP server handed to each run inline — never in the prompt text or the run env. Encrypted; never returned.
18635
20122
  enabled:
18636
20123
  type: boolean
18637
20124
  default: true
@@ -18643,13 +20130,33 @@ components:
18643
20130
  - opsKey
18644
20131
  additionalProperties: false
18645
20132
  description: Create a Cursor cloud-agent trigger. Both keys are required up front (never tokenless). Idempotent by `config.repoUrl`.
20133
+ CopilotTriggerRepo:
20134
+ type: object
20135
+ properties:
20136
+ owner:
20137
+ type: string
20138
+ minLength: 1
20139
+ description: GitHub repo owner.
20140
+ repo:
20141
+ type: string
20142
+ minLength: 1
20143
+ description: GitHub repo name.
20144
+ baseRef:
20145
+ type: string
20146
+ minLength: 1
20147
+ description: Optional base ref this repo's agent task branches from. Omit for the repo's default branch.
20148
+ required:
20149
+ - owner
20150
+ - repo
20151
+ additionalProperties: false
20152
+ description: One repo a Copilot trigger runs against, with the base branch its tasks branch from.
18646
20153
  CopilotTriggerConfig:
18647
20154
  type: object
18648
20155
  properties:
18649
20156
  owner:
18650
20157
  type: string
18651
20158
  minLength: 1
18652
- description: GitHub repo owner. Together with `repo` forms the connector's idempotency key (`owner/repo`).
20159
+ description: '**First** GitHub repo owner. Together with `repo` forms the connector''s idempotency key (`owner/repo`), and both mirror `repos[0]` when `repos` is supplied.'
18653
20160
  repo:
18654
20161
  type: string
18655
20162
  minLength: 1
@@ -18657,11 +20164,21 @@ components:
18657
20164
  baseRef:
18658
20165
  type: string
18659
20166
  minLength: 1
18660
- description: Optional base ref the agent branches from.
20167
+ description: Optional base ref the agent branches from. Mirrors `repos[0].baseRef`.
20168
+ repos:
20169
+ type: array
20170
+ minItems: 1
20171
+ items:
20172
+ $ref: '#/components/schemas/CopilotTriggerRepo'
20173
+ description: Every repo this trigger runs against, each with its own base branch. GitHub's agent-tasks API is per-repo, so ONE fire starts one task per entry. Omit for a single-repo trigger, which is then described by `owner`/`repo`/`baseRef` alone; when supplied, the first entry must match them.
18661
20174
  projectId:
18662
20175
  type: string
18663
20176
  minLength: 1
18664
20177
  description: Shipeasy project id the run targets.
20178
+ fireText:
20179
+ type: string
20180
+ minLength: 1
20181
+ description: Optional standing instruction prepended to every run's prompt — the trigger's base prompt. Lets two triggers on different repos steer their runs differently without changing the `/shipeasy-ops-work` task block.
18665
20182
  required:
18666
20183
  - owner
18667
20184
  - repo
@@ -18719,6 +20236,10 @@ components:
18719
20236
  type: string
18720
20237
  minLength: 1
18721
20238
  description: Shipeasy project id the run targets.
20239
+ fireText:
20240
+ type: string
20241
+ minLength: 1
20242
+ description: Optional standing instruction prepended to every run's prompt — the trigger's base prompt. Lets two triggers on different repos steer their runs differently without changing the `/shipeasy-ops-work` task block.
18722
20243
  required:
18723
20244
  - source
18724
20245
  - projectId
@@ -18918,7 +20439,7 @@ components:
18918
20439
  - provider
18919
20440
  - config
18920
20441
  additionalProperties: false
18921
- description: Per-provider PATCH body for editing an existing `cursor_trigger` connector. `config` replaces the stored config wholesale; `name` and either credential are optional (a supplied credential rotates just that key).
20442
+ description: 'Per-provider PATCH body for editing an existing `cursor_trigger` connector. `config` replaces the stored config wholesale, except for the server-managed keys: the provisioned `agentId` is carried over (and dropped, so a replacement agent is provisioned, when `repoUrl`/`startingRef` change), as is the last-run pointer. `name` and either credential are optional (a supplied credential rotates just that key).'
18922
20443
  UpdateCopilotTriggerRequest:
18923
20444
  type: object
18924
20445
  properties:
@@ -19044,6 +20565,11 @@ components:
19044
20565
  - type: string
19045
20566
  - type: 'null'
19046
20567
  description: Human label for the key. `null` for unnamed dashboard mints; programmatic mints auto-name themselves.
20568
+ description:
20569
+ anyOf:
20570
+ - type: string
20571
+ - type: 'null'
20572
+ description: Free-text note recording what the key is for (the service that holds it, the rotation owner, the ticket it was minted under). Display only — `null` unless someone set it via `PATCH /api/admin/keys/{id}`.
19047
20573
  scopes:
19048
20574
  anyOf:
19049
20575
  - type: array
@@ -19056,6 +20582,14 @@ components:
19056
20582
  - type: string
19057
20583
  - type: 'null'
19058
20584
  description: Last 4 characters of the raw token, so the dashboard mask is matchable against the held key. `null` for keys minted before this column.
20585
+ origin:
20586
+ type: string
20587
+ enum:
20588
+ - cli
20589
+ - mobile
20590
+ - dashboard
20591
+ - api
20592
+ description: Where the key was minted. `cli` (a signed-in CLI session from device auth), `mobile` (a mobile app login), `dashboard` (the "New API key" dialog) or `api` (minted with another key — CLI, MCP, agents). Set at mint time; keys that predate the column were backfilled from their label, defaulting to `dashboard`.
19059
20593
  required:
19060
20594
  - id
19061
20595
  - type
@@ -19065,8 +20599,10 @@ components:
19065
20599
  - expires_at
19066
20600
  - created_by_email
19067
20601
  - name
20602
+ - description
19068
20603
  - scopes
19069
20604
  - last4
20605
+ - origin
19070
20606
  additionalProperties: false
19071
20607
  ListKeysResponse:
19072
20608
  type: object
@@ -19172,6 +20708,30 @@ components:
19172
20708
  - key
19173
20709
  - expires_at
19174
20710
  additionalProperties: false
20711
+ UpdateKeyRequest:
20712
+ type: object
20713
+ description: |-
20714
+ Body for `PATCH /api/admin/keys/{id}`. Only the key's **label** is editable — `name` and `description`.
20715
+
20716
+ Everything that gives a key its authority (`type`, `env`, `scopes`, `expires_at`) is fixed at mint: those are the isolation boundary the worker derives the read env and permissions from, so changing them would silently re-point a credential that is already deployed. Rotate instead — mint a replacement and revoke the old key.
20717
+
20718
+ Omitted fields are left untouched; send `null` to clear one.
20719
+ properties:
20720
+ name:
20721
+ anyOf:
20722
+ - type: string
20723
+ minLength: 1
20724
+ maxLength: 160
20725
+ - type: 'null'
20726
+ description: New human label, or `null` to clear it. Same 160-char budget as create — provenance labels are composed, not typed.
20727
+ description:
20728
+ anyOf:
20729
+ - type: string
20730
+ minLength: 1
20731
+ maxLength: 500
20732
+ - type: 'null'
20733
+ description: New free-text note, or `null` to clear it.
20734
+ additionalProperties: false
19175
20735
  RevokeKeyResponse:
19176
20736
  type: object
19177
20737
  description: Confirms the key is revoked. Idempotent — revoking an already-revoked key returns the same shape.
@@ -19199,6 +20759,7 @@ components:
19199
20759
  - config
19200
20760
  - killswitch
19201
20761
  - metric
20762
+ - ops
19202
20763
  description: Resource family — drives the result group + icon in the palette. (`member` hits are surfaced first.)
19203
20764
  id:
19204
20765
  type: string
@@ -19232,6 +20793,85 @@ components:
19232
20793
  required:
19233
20794
  - hits
19234
20795
  additionalProperties: false
20796
+ Tag:
20797
+ type: object
20798
+ properties:
20799
+ id:
20800
+ type: string
20801
+ description: Stable opaque tag id.
20802
+ name:
20803
+ type: string
20804
+ description: Display name — also the tag's identity, unique case-insensitively within the project.
20805
+ required:
20806
+ - id
20807
+ - name
20808
+ additionalProperties: false
20809
+ ListTagsResponse:
20810
+ type: array
20811
+ items:
20812
+ $ref: '#/components/schemas/Tag'
20813
+ description: The project's tags, sorted by name.
20814
+ TaggableEntityType:
20815
+ type: string
20816
+ enum:
20817
+ - ops
20818
+ - flag
20819
+ - config
20820
+ - killswitch
20821
+ - metric
20822
+ - error
20823
+ - alert_rule
20824
+ - experiment
20825
+ description: 'The kind of item a tag set hangs off: `ops` (any queue item — bug, feature request, error/alert/measure-plan ticket), `flag` (feature gate), `config` (dynamic config), `killswitch`, `metric`, `error` (tracked error group), `alert_rule`, or `experiment`.'
20826
+ EntityTagsResponse:
20827
+ type: object
20828
+ properties:
20829
+ entityType:
20830
+ $ref: '#/components/schemas/TaggableEntityType'
20831
+ entityId:
20832
+ type: string
20833
+ description: The item's canonical id (the handle you passed, resolved).
20834
+ tags:
20835
+ type: array
20836
+ items:
20837
+ type: string
20838
+ description: The tag names now attached to the item, sorted by name.
20839
+ required:
20840
+ - entityType
20841
+ - entityId
20842
+ - tags
20843
+ additionalProperties: false
20844
+ SetEntityTagsRequest:
20845
+ type: object
20846
+ properties:
20847
+ tags:
20848
+ type: array
20849
+ maxItems: 50
20850
+ items:
20851
+ $ref: '#/components/schemas/TagName'
20852
+ description: The complete tag set the item should carry — tags not listed are detached. Names are get-or-created (a new name mints a project tag), deduped case-insensitively; `[]` clears every tag.
20853
+ required:
20854
+ - tags
20855
+ additionalProperties: false
20856
+ UpdateEntityTagsRequest:
20857
+ type: object
20858
+ properties:
20859
+ add:
20860
+ type: array
20861
+ maxItems: 50
20862
+ items:
20863
+ $ref: '#/components/schemas/TagName'
20864
+ default: []
20865
+ description: Tag names to attach (get-or-created by name, case-insensitive). Already-attached names are a no-op.
20866
+ remove:
20867
+ type: array
20868
+ maxItems: 50
20869
+ items:
20870
+ $ref: '#/components/schemas/TagName'
20871
+ default: []
20872
+ description: Tag names to detach (case-insensitive). Names the item doesn't carry are a no-op. A name in both lists is removed.
20873
+ additionalProperties: false
20874
+ description: Incremental tag edit — pass `add`, `remove`, or both. Every other tag on the item is left as is.
19235
20875
  responses:
19236
20876
  BadRequest:
19237
20877
  description: The request was malformed (bad JSON or missing project scope).