{
  "components": {
    "headers": {
      "ContentDisposition": {
        "description": "`attachment` with the file name.",
        "example": "attachment; filename=\"report-214.html\"",
        "schema": {
          "type": "string"
        }
      },
      "Deprecation": {
        "description": "Set on deprecated endpoints: `@<unix seconds>` of the deprecation (RFC 9745).",
        "example": "@1790000000",
        "schema": {
          "type": "string"
        }
      },
      "ETag": {
        "description": "A weak validator of the body; send it back in `If-None-Match` (or `If-Match` on writes).",
        "example": "W/\"5c2f9a1b3d4c6e7f\"",
        "schema": {
          "type": "string"
        }
      },
      "IdempotentReplayed": {
        "description": "`true` when this is the stored response of an earlier request with the same `Idempotency-Key`.",
        "example": "true",
        "schema": {
          "enum": [
            "true"
          ],
          "type": "string"
        }
      },
      "Link": {
        "description": "Set on deprecated endpoints: the migration notes, `rel=\"deprecation\"`.",
        "example": "<https://docs.mycve.io-v1#changelog>; rel=\"deprecation\"",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Requests allowed in the current 60-second window of this bucket.",
        "example": 120,
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitPolicy": {
        "description": "The bucket's quota and window: `<limit>;w=<seconds>`.",
        "example": "120;w=60",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the window.",
        "example": 117,
        "schema": {
          "minimum": 0,
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the window resets.",
        "example": 41,
        "schema": {
          "minimum": 0,
          "type": "integer"
        }
      },
      "RequestId": {
        "description": "The request's id (`req_` + 16 hex); also `request_id` in error bodies. Quote it to support.",
        "example": "req_00010203aabbccff",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "example": 41,
        "schema": {
          "minimum": 1,
          "type": "integer"
        }
      },
      "Sunset": {
        "description": "Set on deprecated endpoints: when it stops working (RFC 8594).",
        "example": "Fri, 19 Mar 2027 00:00:00 GMT",
        "schema": {
          "type": "string"
        }
      }
    },
    "parameters": {
      "Cursor": {
        "description": "`next_cursor` of the previous page, unchanged. Opaque, and only valid on the route that made it (400 otherwise).",
        "example": "eyJ2IjoxLCJrIjpbIm8iLDUwXX0",
        "in": "query",
        "name": "cursor",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "description": "A unique key per logical request (a UUID). Retries with the same key and body within 24 hours replay the stored response instead of creating another resource.",
        "example": "4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f",
        "in": "header",
        "name": "Idempotency-Key",
        "required": false,
        "schema": {
          "maxLength": 255,
          "minLength": 1,
          "pattern": "^[\\x21-\\x7e]+$",
          "type": "string"
        }
      },
      "IfMatch": {
        "description": "The `ETag` of the `GET` of this path: the change happens only if it still matches (412 `precondition_failed` otherwise).",
        "example": "W/\"5c2f9a1b3d4c6e7f\"",
        "in": "header",
        "name": "If-Match",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "IfNoneMatch": {
        "description": "An `ETag` from an earlier response: 304 Not Modified (no body) when it still matches.",
        "example": "W/\"5c2f9a1b3d4c6e7f\"",
        "in": "header",
        "name": "If-None-Match",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "description": "Items per page, 1 to 200.",
        "example": 50,
        "in": "query",
        "name": "limit",
        "required": false,
        "schema": {
          "default": 50,
          "maximum": 200,
          "minimum": 1,
          "type": "integer"
        }
      },
      "WebhookDelivery": {
        "description": "The delivery id (`whd_` + 24 hex, same as `id` in the body); retries keep it. Dedupe on it.",
        "example": "whd_5e0c2f9a1b3d4c6e7f809a1b",
        "in": "header",
        "name": "Mycve-Delivery",
        "required": true,
        "schema": {
          "pattern": "^whd_[0-9a-f]{24}$",
          "type": "string"
        }
      },
      "WebhookEvent": {
        "description": "The event name (same as `event` in the body).",
        "example": "scan.completed",
        "in": "header",
        "name": "Mycve-Event",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/WebhookEvent"
        }
      },
      "WebhookSignature": {
        "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>`; a second `v1=` signed with the previous secret for 24 hours after a rotation.",
        "example": "t=1790000000,v1=6f3c0b1d0e7a4b1f9a8c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6",
        "in": "header",
        "name": "Mycve-Signature",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "Workspace": {
        "description": "The workspace to act in (an id from `listWorkspaces`). Default: the token's own workspace (service tokens and pinned personal tokens), else your personal workspace. Naming another workspace than a service token's is 403; a workspace you are not a member of is 404.",
        "example": 7,
        "in": "header",
        "name": "X-Mycve-Workspace",
        "required": false,
        "schema": {
          "format": "int64",
          "minimum": 1,
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "content": {
          "application/json": {
            "examples": {
              "bad_cursor": {
                "summary": "A cursor of another route",
                "value": {
                  "error": {
                    "code": "bad_request",
                    "details": null,
                    "message": "invalid cursor",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              },
              "malformed_json": {
                "summary": "Malformed JSON",
                "value": {
                  "error": {
                    "code": "bad_request",
                    "details": null,
                    "message": "malformed JSON: expected value at line 1 column 1",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "Bad request: malformed JSON, a bad `X-Mycve-Workspace`, cursor or `Idempotency-Key`.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "Conflict": {
        "content": {
          "application/json": {
            "examples": {
              "in_flight": {
                "summary": "Same Idempotency-Key still running",
                "value": {
                  "error": {
                    "code": "conflict",
                    "details": null,
                    "message": "a request with this Idempotency-Key is still running",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              },
              "state": {
                "summary": "State conflict",
                "value": {
                  "error": {
                    "code": "conflict",
                    "details": null,
                    "message": "this suggestion is already accepted",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "The resource's state does not allow this, or a request with the same `Idempotency-Key` is still running.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "Forbidden": {
        "content": {
          "application/json": {
            "examples": {
              "role": {
                "summary": "Role too low",
                "value": {
                  "error": {
                    "code": "forbidden",
                    "details": null,
                    "message": "this needs the member role in workspace \"acme\"; the request acts as viewer",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              },
              "scope": {
                "summary": "Token scope",
                "value": {
                  "error": {
                    "code": "forbidden",
                    "details": null,
                    "message": "this token's scopes do not allow write access to scans",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "The token's scopes or access level, or your role in the workspace, do not allow this.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "Gone": {
        "content": {
          "application/json": {
            "examples": {
              "gone": {
                "summary": "Sunset",
                "value": {
                  "error": {
                    "code": "gone",
                    "details": null,
                    "message": "this endpoint was removed on 2027-03-19; see the changelog",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "Gone: removed after its sunset date.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "Internal": {
        "content": {
          "application/json": {
            "examples": {
              "internal": {
                "summary": "Internal error",
                "value": {
                  "error": {
                    "code": "internal",
                    "details": null,
                    "message": "internal error",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "Our fault: retry with exponential backoff, and quote `request_id` if it persists.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "NotFound": {
        "content": {
          "application/json": {
            "examples": {
              "not_found": {
                "summary": "Not found",
                "value": {
                  "error": {
                    "code": "not_found",
                    "details": null,
                    "message": "scan not found",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "No such resource in this workspace (or you are not a member of the workspace).",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "NotImplemented": {
        "content": {
          "application/json": {
            "examples": {
              "not_implemented": {
                "summary": "Not built yet",
                "value": {
                  "error": {
                    "code": "not_implemented",
                    "details": null,
                    "message": "scan tasks (distributed scans) is not available on this deployment yet",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "Not available on this deployment yet: retry after an upgrade, never in a loop.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "NotModified": {
        "description": "Not modified: the `If-None-Match` ETag still matches. No body.",
        "headers": {
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "PayloadTooLarge": {
        "content": {
          "application/json": {
            "examples": {
              "payload_too_large": {
                "summary": "Too large",
                "value": {
                  "error": {
                    "code": "payload_too_large",
                    "details": null,
                    "message": "request body over 1 MB",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "The body is over 1 MB.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "PreconditionFailed": {
        "content": {
          "application/json": {
            "examples": {
              "precondition_failed": {
                "summary": "Stale ETag",
                "value": {
                  "error": {
                    "code": "precondition_failed",
                    "details": null,
                    "message": "If-Match does not match the current version",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "`If-Match` does not match the current `ETag`: someone changed the resource since you read it.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "RateLimited": {
        "content": {
          "application/json": {
            "examples": {
              "rate_limited": {
                "summary": "Over the limit",
                "value": {
                  "error": {
                    "code": "rate_limited",
                    "details": {
                      "bucket": "write",
                      "retry_after": 41
                    },
                    "message": "rate limit exceeded; retry after the window resets",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "Too many requests in this window: wait `Retry-After` seconds.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "Unauthenticated": {
        "content": {
          "application/json": {
            "examples": {
              "invalid": {
                "summary": "Unknown, expired or revoked token",
                "value": {
                  "error": {
                    "code": "unauthenticated",
                    "details": null,
                    "message": "invalid, expired or revoked token",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              },
              "missing": {
                "summary": "No token",
                "value": {
                  "error": {
                    "code": "unauthenticated",
                    "details": null,
                    "message": "send Authorization: Bearer mycve_pat_… or mycve_svc_…",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "No token, or an unknown, expired or revoked one.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "Unavailable": {
        "content": {
          "application/json": {
            "examples": {
              "unavailable": {
                "summary": "Database down",
                "value": {
                  "error": {
                    "code": "unavailable",
                    "details": null,
                    "message": "the database is not reachable; retry shortly",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "A dependency (database, queue) is unreachable: retry shortly with backoff.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "UnsupportedMediaType": {
        "content": {
          "application/json": {
            "examples": {
              "unsupported_media_type": {
                "summary": "Not JSON",
                "value": {
                  "error": {
                    "code": "unsupported_media_type",
                    "details": null,
                    "message": "send JSON with Content-Type: application/json",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "The body is not `application/json`.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      },
      "ValidationFailed": {
        "content": {
          "application/json": {
            "examples": {
              "idempotency_key_reused": {
                "summary": "Key reused",
                "value": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "details": null,
                    "message": "this Idempotency-Key was used for a different request",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              },
              "validation_failed": {
                "summary": "Invalid field",
                "value": {
                  "error": {
                    "code": "validation_failed",
                    "details": {
                      "fields": [
                        {
                          "field": "name",
                          "issue": "must not be empty"
                        }
                      ]
                    },
                    "message": "invalid request (name: must not be empty)",
                    "request_id": "req_00010203aabbccff"
                  }
                }
              }
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        },
        "description": "A field or parameter is invalid (`details.fields`), or an `Idempotency-Key` was reused for another request.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        }
      }
    },
    "schemas": {
      "AcceptSuggestion": {
        "description": "`POST /v1/project-suggestions/{suggestion_id}/accept`: optionally rename or leave assets out; creates the project (201 `Project`).",
        "properties": {
          "asset_ids": {
            "description": "Subset of the suggestion's assets; absent = all.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "AcceptSuggestion",
        "type": "object"
      },
      "Access": {
        "description": "A token's access level. Ordered: `read < write < admin`.\n\n- `read`: GET/HEAD; roles viewer and up.\n- `write`: Creates and changes ordinary data (scans, projects, reports, pipelines runs, fixes); role member and up.\n- `admin`: Settings, members, tokens, webhooks, secrets; role admin and up.",
        "enum": [
          "read",
          "write",
          "admin"
        ],
        "title": "Access",
        "type": "string",
        "x-enumDescriptions": {
          "admin": "Settings, members, tokens, webhooks, secrets; role admin and up.",
          "read": "GET/HEAD; roles viewer and up.",
          "write": "Creates and changes ordinary data (scans, projects, reports, pipelines runs, fixes); role member and up."
        }
      },
      "AnalyticsKpis": {
        "description": "`GET /v1/analytics/kpis?from=&to=&project_id=`: the KPI rows of a dashboard. `usage` is for workspace admins and owners only.",
        "properties": {
          "from": {
            "format": "date",
            "type": "string"
          },
          "generated_at": {
            "format": "date-time",
            "type": "string"
          },
          "project_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "rolled_up_at": {
            "description": "When the newest security rollup in range was computed (fresh data is at most a day old; `null` = never rolled up).",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "security": {
            "items": {
              "$ref": "#/components/schemas/Kpi"
            },
            "type": "array"
          },
          "to": {
            "format": "date",
            "type": "string"
          },
          "usage": {
            "items": {
              "$ref": "#/components/schemas/Kpi"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "from",
          "to",
          "generated_at",
          "security"
        ],
        "title": "AnalyticsKpis",
        "type": "object"
      },
      "AnalyticsPreference": {
        "description": "The caller's analytics preference (`GET`/`PUT /v1/me/analytics`).",
        "properties": {
          "opt_out": {
            "description": "true = no usage events are kept for you; essential events are kept without your user id.",
            "type": "boolean"
          }
        },
        "required": [
          "opt_out"
        ],
        "title": "AnalyticsPreference",
        "type": "object"
      },
      "ApiToken": {
        "description": "A token as listed (`GET /v1/tokens`); the secret is never returned after creation.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "kind": {
            "$ref": "#/components/schemas/TokenKind"
          },
          "last_used_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "description": "`mycve_pat_3fa9c0d1` — the prefix plus 8 characters.",
            "type": "string"
          },
          "revoked_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "scopes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "workspace_id": {
            "description": "Service tokens: their workspace. Personal tokens: the workspace they are pinned to, or null (any workspace the user is in).",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "kind",
          "name",
          "prefix",
          "scopes",
          "created_at"
        ],
        "title": "ApiToken",
        "type": "object"
      },
      "AreaSummary": {
        "description": "One feature area's numbers over a range (`GET /v1/analytics/areas`).",
        "properties": {
          "area": {
            "$ref": "#/components/schemas/FeatureArea"
          },
          "daily": {
            "description": "Events per day, oldest first, one per day of the range (sparkline).",
            "items": {
              "format": "int64",
              "type": "integer"
            },
            "type": "array"
          },
          "events": {
            "format": "int64",
            "type": "integer"
          },
          "users": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "area",
          "events",
          "users",
          "daily"
        ],
        "title": "AreaSummary",
        "type": "object"
      },
      "AssetKind": {
        "description": "The kind of an asset of the infrastructure inventory.\n\n- `repository`: A Git repository.\n- `image`: A container image built or deployed by the workspace.\n- `base_image`: A base image other images are built `FROM`.\n- `registry`: A container registry (Docker Hub, GHCR, Artifact Registry, ...).\n- `cloud_project`: A cloud account or project (a GCP project, ...).\n- `path`: A folder uploaded with `cve scan path`.",
        "enum": [
          "repository",
          "image",
          "base_image",
          "registry",
          "cloud_project",
          "path"
        ],
        "title": "AssetKind",
        "type": "string",
        "x-enumDescriptions": {
          "base_image": "A base image other images are built `FROM`.",
          "cloud_project": "A cloud account or project (a GCP project, ...).",
          "image": "A container image built or deployed by the workspace.",
          "path": "A folder uploaded with `cve scan path`.",
          "registry": "A container registry (Docker Hub, GHCR, Artifact Registry, ...).",
          "repository": "A Git repository."
        }
      },
      "BarDatum": {
        "description": "One bar (a category) of a bar or stacked bar chart.",
        "properties": {
          "href": {
            "description": "Where a click goes (an app route), if anywhere.",
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "type": "string"
          },
          "segments": {
            "items": {
              "$ref": "#/components/schemas/Segment"
            },
            "type": "array"
          }
        },
        "required": [
          "label",
          "segments"
        ],
        "title": "BarDatum",
        "type": "object"
      },
      "Caller": {
        "description": "`GET /v1/me`: who the API thinks the caller is, and what it may do.",
        "properties": {
          "access": {
            "$ref": "#/components/schemas/Access"
          },
          "identity": {
            "type": [
              "string",
              "null"
            ]
          },
          "report_email_hour_utc": {
            "description": "The UTC hour (0-23) the person's daily report email goes out; null = the default (08:00 UTC) or a service token. Change it with `PATCH /api/me` or in Settings, Notifications.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "role": {
            "description": "The user's effective role there (null for service tokens).",
            "oneOf": [
              {
                "$ref": "#/components/schemas/Role"
              },
              {
                "type": "null"
              }
            ]
          },
          "scopes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "token": {
            "description": "The token in use; null for a browser session.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/ApiToken"
              },
              {
                "type": "null"
              }
            ]
          },
          "user_id": {
            "description": "Null for a service token.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "workspace_id": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "workspace_id",
          "access",
          "scopes"
        ],
        "title": "Caller",
        "type": "object"
      },
      "ClientEvent": {
        "description": "One usage event as a client sends it (`POST /v1/events`, a batch of at most 50). The server adds `user_id`, `workspace_id`, `at` (clamped to ±10 minutes of its clock) and the surface.",
        "properties": {
          "at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "$ref": "#/components/schemas/EventName"
          },
          "project_id": {
            "description": "Project the screen is about, when it is one.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "props": {
            "$ref": "#/components/schemas/EventProps"
          },
          "route": {
            "description": "`page.viewed`: the app route *pattern* (`/projects/:id`); `cli.command`: the command path (`scan path`). Nothing else.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name"
        ],
        "title": "ClientEvent",
        "type": "object"
      },
      "ComplianceControl": {
        "description": "A control a template helps satisfy.",
        "properties": {
          "control": {
            "description": "The control id in that framework (`2.1.1`, `CC6.1`).",
            "type": "string"
          },
          "framework": {
            "description": "`CIS AWS Foundations 3.0`, `CIS Kubernetes 1.9`, `SOC 2`, ...",
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "framework",
          "control",
          "title"
        ],
        "title": "ComplianceControl",
        "type": "object"
      },
      "CreateFix": {
        "description": "`POST /v1/fixes`: open (or update) a fix PR for a scan's findings.",
        "properties": {
          "ai": {
            "description": "Allow the AI step (default true).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "max_changes": {
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "mode": {
            "description": "`pr` (default), `branch` or `dry_run`.",
            "type": [
              "string",
              "null"
            ]
          },
          "scan_id": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "scan_id"
        ],
        "title": "CreateFix",
        "type": "object"
      },
      "CreateInvite": {
        "description": "`POST /v1/invites`.",
        "properties": {
          "email": {
            "format": "email",
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "email",
          "role"
        ],
        "title": "CreateInvite",
        "type": "object"
      },
      "CreatePipelineRun": {
        "description": "`POST /v1/pipeline-runs`: run a connected repository's pipeline. Give `target` (repository URL) or `connector_id` + `repo`.",
        "properties": {
          "connector_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "file": {
            "description": "Pipeline file (default `cve.yml`).",
            "type": [
              "string",
              "null"
            ]
          },
          "jobs": {
            "description": "Run only these jobs (and what they need).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ref": {
            "description": "Branch or tag (default: the default branch).",
            "type": [
              "string",
              "null"
            ]
          },
          "repo": {
            "type": [
              "string",
              "null"
            ]
          },
          "sha": {
            "type": [
              "string",
              "null"
            ]
          },
          "tag": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "target": {
            "type": [
              "string",
              "null"
            ]
          },
          "variables": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          }
        },
        "title": "CreatePipelineRun",
        "type": "object"
      },
      "CreateReport": {
        "description": "`POST /v1/reports` (202 [`ReportJob`]): build a report in the background. Periods default to the last 30 days.",
        "properties": {
          "formats": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "period_end": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "period_start": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "scope": {
            "$ref": "#/components/schemas/ReportScope"
          }
        },
        "required": [
          "scope"
        ],
        "title": "CreateReport",
        "type": "object"
      },
      "CreateScan": {
        "description": "`POST /v1/scans`: scan a git repository (`git`) or a container image (`image`). Folders are uploaded with `cve scan path`.",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/TargetKind"
          },
          "profile": {
            "description": "quick | standard (default) | deep (docs/SCANS.md \"Profiles\").",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "target"
        ],
        "title": "CreateScan",
        "type": "object"
      },
      "CreateTemplatePullRequest": {
        "description": "`POST /v1/templates/{template_id}/pull-requests`: render into a repository through the fix engine (cloud only).",
        "properties": {
          "mode": {
            "description": "`pr` (default), `branch` or `dry_run`.",
            "type": [
              "string",
              "null"
            ]
          },
          "params": {
            "additionalProperties": {},
            "type": "object"
          },
          "repo": {
            "description": "The repository: its URL as scanned (`https://github.com/o/r`) or its asset id (`repo-...`). It needs a finished scan in the workspace and a fix token (`cve connect github --fixes`).",
            "type": "string"
          }
        },
        "required": [
          "repo"
        ],
        "title": "CreateTemplatePullRequest",
        "type": "object"
      },
      "CreateToken": {
        "description": "`POST /v1/tokens`.",
        "properties": {
          "expires_in_days": {
            "description": "Days until it expires (default 90, at most 366); null = never (service tokens, owner only).",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/TokenKind"
          },
          "name": {
            "type": "string"
          },
          "scopes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "kind",
          "name",
          "scopes"
        ],
        "title": "CreateToken",
        "type": "object"
      },
      "CreateWebhook": {
        "description": "`POST /v1/webhooks`.",
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "events": {
            "description": "Empty = every event.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "url": {
            "description": "`https://` only; private and loopback hosts are refused.",
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "title": "CreateWebhook",
        "type": "object"
      },
      "CreatedToken": {
        "description": "`201` of `POST /v1/tokens`: the only time `token` is shown.",
        "properties": {
          "info": {
            "$ref": "#/components/schemas/ApiToken"
          },
          "token": {
            "type": "string"
          }
        },
        "required": [
          "token",
          "info"
        ],
        "title": "CreatedToken",
        "type": "object"
      },
      "DashboardAsset": {
        "description": "One asset row of a project dashboard.",
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "at_risk": {
            "type": "boolean"
          },
          "kind": {
            "$ref": "#/components/schemas/AssetKind"
          },
          "last_scan_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts"
          },
          "packages": {
            "format": "int64",
            "type": "integer"
          },
          "provider": {
            "type": "string"
          },
          "stale": {
            "type": "boolean"
          }
        },
        "required": [
          "asset_id",
          "kind",
          "name",
          "provider",
          "open",
          "at_risk",
          "stale",
          "packages"
        ],
        "title": "DashboardAsset",
        "type": "object"
      },
      "DashboardCve": {
        "description": "One of the highest-scored open CVEs of a project.",
        "properties": {
          "assets": {
            "description": "Asset ids it is open in.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "cve_id": {
            "type": "string"
          },
          "exploited": {
            "description": "Known exploited (CISA KEV) when the feed says so.",
            "type": "boolean"
          },
          "fix": {
            "description": "Smallest fixing version, when derivable.",
            "type": [
              "string",
              "null"
            ]
          },
          "packages": {
            "description": "`name@version` of the affected packages (at most 5).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "score": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "severity": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Severity"
              },
              {
                "type": "null"
              }
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "cve_id",
          "packages",
          "assets",
          "exploited"
        ],
        "title": "DashboardCve",
        "type": "object"
      },
      "DashboardEvent": {
        "description": "A recent event on the project's timeline.",
        "properties": {
          "at": {
            "format": "date-time",
            "type": "string"
          },
          "href": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "description": "`scan`, `fix`, `pipeline`, `report`, `secret`, `connector`.",
            "type": "string"
          },
          "status": {
            "description": "`ok`, `failed`, `running` (icon via `icons::run_status`).",
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "at",
          "kind",
          "title",
          "status"
        ],
        "title": "DashboardEvent",
        "type": "object"
      },
      "DeliveryMode": {
        "description": "How a render leaves mycve.\n\n- `render`: `POST /v1/templates/{id}/render` (JSON).\n- `zip`: The same render as a zip download.\n- `pr`: A branch and pull/merge request opened by the fix engine in mycve's cloud (needs a fix token for the repository).\n- `cli`: `cve templates render` on the user's machine.",
        "enum": [
          "render",
          "zip",
          "pr",
          "cli"
        ],
        "title": "DeliveryMode",
        "type": "string",
        "x-enumDescriptions": {
          "cli": "`cve templates render` on the user's machine.",
          "pr": "A branch and pull/merge request opened by the fix engine in mycve's cloud (needs a fix token for the repository).",
          "render": "`POST /v1/templates/{id}/render` (JSON).",
          "zip": "The same render as a zip download."
        }
      },
      "DeliveryStatus": {
        "description": "State of a delivery.\n\n- `pending`\n- `delivering`\n- `succeeded`\n- `failed`: Failed, will be retried.\n- `dead`: Gave up (24 hours of retries).",
        "enum": [
          "pending",
          "delivering",
          "succeeded",
          "failed",
          "dead"
        ],
        "title": "DeliveryStatus",
        "type": "string",
        "x-enumDescriptions": {
          "dead": "Gave up (24 hours of retries).",
          "failed": "Failed, will be retried."
        }
      },
      "DriftHint": {
        "description": "One drift hint.",
        "properties": {
          "declared": {
            "description": "The declared tag (or digest).",
            "type": [
              "string",
              "null"
            ]
          },
          "image": {
            "description": "`registry/repository` (Docker Hub as `docker.io/library/x`).",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/DriftKind"
          },
          "message": {
            "type": "string"
          },
          "paths": {
            "description": "Files that declare it.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "registry_tags": {
            "description": "Tags the registry is known to hold (newest first, at most 10).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "severity": {
            "$ref": "#/components/schemas/Severity"
          },
          "target": {
            "description": "The scanned repository or folder that declares it.",
            "type": "string"
          }
        },
        "required": [
          "kind",
          "severity",
          "image",
          "registry_tags",
          "target",
          "paths",
          "message"
        ],
        "title": "DriftHint",
        "type": "object"
      },
      "DriftKind": {
        "description": "What kind of drift a hint describes.\n\n- `tag_not_in_registry`: The declared tag is not in the registry's listing: a deploy would fail, or the listing is stale (`cve connect sync`).\n- `newer_tag_available`: The registry holds a newer version than the manifest deploys.\n- `mutable_tag`: A moving tag (`latest`, `main`, `stable`, no tag): what runs depends on when it was pulled.\n- `inconsistent_tags`: One repository deploys different tags of the same image.",
        "enum": [
          "tag_not_in_registry",
          "newer_tag_available",
          "mutable_tag",
          "inconsistent_tags"
        ],
        "title": "DriftKind",
        "type": "string",
        "x-enumDescriptions": {
          "inconsistent_tags": "One repository deploys different tags of the same image.",
          "mutable_tag": "A moving tag (`latest`, `main`, `stable`, no tag): what runs depends on when it was pulled.",
          "newer_tag_available": "The registry holds a newer version than the manifest deploys.",
          "tag_not_in_registry": "The declared tag is not in the registry's listing: a deploy would fail, or the listing is stale (`cve connect sync`)."
        }
      },
      "DriftReport": {
        "description": "`GET /v1/infrastructure/drift`.",
        "properties": {
          "declared": {
            "description": "Distinct (target, image reference) pairs declared in manifests.",
            "format": "int64",
            "type": "integer"
          },
          "hints": {
            "items": {
              "$ref": "#/components/schemas/DriftHint"
            },
            "type": "array"
          },
          "registry_images": {
            "description": "Distinct image references seen in registries and image scans.",
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "declared",
          "registry_images",
          "hints"
        ],
        "title": "DriftReport",
        "type": "object"
      },
      "Ecosystem": {
        "description": "The package ecosystem of a finding or package.\n\n- `npm`\n- `cargo`\n- `go`\n- `pypi`\n- `rubygems`\n- `composer`\n- `maven`\n- `debian`\n- `alpine`\n- `container-image`\n- `terraform-provider`\n- `github-actions`\n- `unknown`: Not recognised.",
        "enum": [
          "npm",
          "cargo",
          "go",
          "pypi",
          "rubygems",
          "composer",
          "maven",
          "debian",
          "alpine",
          "container-image",
          "terraform-provider",
          "github-actions",
          "unknown"
        ],
        "title": "Ecosystem",
        "type": "string",
        "x-enumDescriptions": {
          "unknown": "Not recognised."
        }
      },
      "ErrorCode": {
        "description": "Machine-readable error codes. Clients switch on `code`, never on `message` (which may change).\n\n- `bad_request`: 400: malformed JSON, a bad `X-Mycve-Workspace`, a bad cursor or `Idempotency-Key`.\n- `validation_failed`: 422: the request is well-formed but a field is invalid (`details.fields`).\n- `unauthenticated`: 401: no token, an unknown, expired or revoked token, or no session.\n- `forbidden`: 403: the token's scopes, its access level or your role do not allow this.\n- `not_found`: 404: no such route or resource in this workspace (or not a member of the workspace).\n- `method_not_allowed`: 405: the path exists with other methods.\n- `conflict`: 409: the resource's state does not allow it, or a request with the same `Idempotency-Key` is still running.\n- `idempotency_key_reused`: 422: this `Idempotency-Key` was used for a different request.\n- `precondition_failed`: 412: `If-Match` does not match the resource's current `ETag`.\n- `payload_too_large`: 413: the body is over 1 MB.\n- `unsupported_media_type`: 415: a body that is not `application/json`.\n- `rate_limited`: 429: the rate-limit bucket is empty; see `Retry-After` and `details.retry_after`.\n- `gone`: 410: the resource existed but is gone for good.\n- `internal`: 500: our fault; retry with backoff and quote `request_id` if it persists.\n- `not_implemented`: 501: the route is in the contract but not served by this deployment yet.\n- `unavailable`: 503: a dependency (database, queue) is unreachable; retry shortly.",
        "enum": [
          "bad_request",
          "validation_failed",
          "unauthenticated",
          "forbidden",
          "not_found",
          "method_not_allowed",
          "conflict",
          "idempotency_key_reused",
          "precondition_failed",
          "payload_too_large",
          "unsupported_media_type",
          "rate_limited",
          "gone",
          "internal",
          "not_implemented",
          "unavailable"
        ],
        "title": "ErrorCode",
        "type": "string",
        "x-enumDescriptions": {
          "bad_request": "400: malformed JSON, a bad `X-Mycve-Workspace`, a bad cursor or `Idempotency-Key`.",
          "conflict": "409: the resource's state does not allow it, or a request with the same `Idempotency-Key` is still running.",
          "forbidden": "403: the token's scopes, its access level or your role do not allow this.",
          "gone": "410: the resource existed but is gone for good.",
          "idempotency_key_reused": "422: this `Idempotency-Key` was used for a different request.",
          "internal": "500: our fault; retry with backoff and quote `request_id` if it persists.",
          "method_not_allowed": "405: the path exists with other methods.",
          "not_found": "404: no such route or resource in this workspace (or not a member of the workspace).",
          "not_implemented": "501: the route is in the contract but not served by this deployment yet.",
          "payload_too_large": "413: the body is over 1 MB.",
          "precondition_failed": "412: `If-Match` does not match the resource's current `ETag`.",
          "rate_limited": "429: the rate-limit bucket is empty; see `Retry-After` and `details.retry_after`.",
          "unauthenticated": "401: no token, an unknown, expired or revoked token, or no session.",
          "unavailable": "503: a dependency (database, queue) is unreachable; retry shortly.",
          "unsupported_media_type": "415: a body that is not `application/json`.",
          "validation_failed": "422: the request is well-formed but a field is invalid (`details.fields`)."
        }
      },
      "ErrorDetail": {
        "description": "The body of every v1 error.",
        "properties": {
          "code": {
            "$ref": "#/components/schemas/ErrorCode"
          },
          "details": {
            "description": "Extra machine-readable data (`fields`, `retry_after`, ...)."
          },
          "message": {
            "description": "For people; sentence case, no secrets, no stack traces.",
            "type": "string"
          },
          "request_id": {
            "description": "The request's id (also the `X-Request-Id` header); quote it in support requests.",
            "type": "string"
          }
        },
        "required": [
          "code",
          "message",
          "request_id"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorEnvelope": {
        "description": "`{\"error\": {...}}`.",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorDetail"
          }
        },
        "required": [
          "error"
        ],
        "title": "ErrorEnvelope",
        "type": "object"
      },
      "EventBatch": {
        "description": "`POST /v1/events`: 202 `{accepted, dropped}`.",
        "properties": {
          "events": {
            "items": {
              "$ref": "#/components/schemas/ClientEvent"
            },
            "type": "array"
          }
        },
        "required": [
          "events"
        ],
        "title": "EventBatch",
        "type": "object"
      },
      "EventBatchResult": {
        "properties": {
          "accepted": {
            "format": "int64",
            "type": "integer"
          },
          "dropped": {
            "description": "Opted out, not client-sendable, or invalid (never an error: the UI must not break over analytics).",
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "accepted",
          "dropped"
        ],
        "title": "EventBatchResult",
        "type": "object"
      },
      "EventName": {
        "description": "The fixed event catalog. Adding one is a reviewed change to this enum, POLICIES.md and docs/API_V1.md together.",
        "enum": [
          "page.viewed",
          "api.call",
          "cli.command",
          "scan.started",
          "scan.completed",
          "scan.failed",
          "finding.viewed",
          "findings.filtered",
          "cve.viewed",
          "cve.searched",
          "asset.viewed",
          "infrastructure.viewed",
          "project.created",
          "project.viewed",
          "project.suggestion_accepted",
          "project.suggestion_dismissed",
          "pipeline.run",
          "fix.started",
          "fix.pr_opened",
          "report.generated",
          "report.downloaded",
          "connector.added",
          "connector.synced",
          "template.used",
          "token.created",
          "webhook.created",
          "dashboard.viewed",
          "chart.interacted"
        ],
        "title": "EventName",
        "type": "string"
      },
      "EventProps": {
        "description": "Enumerated dimensions an event may carry. Every value is checked against a closed list ([`validate`]); there is no free-form field.",
        "properties": {
          "chart": {
            "description": "Chart kind of `chart.interacted` (`ChartKind` wire names).",
            "type": [
              "string",
              "null"
            ]
          },
          "duration": {
            "description": "Duration bucket, never an exact time: `lt1s`, `1-10s`, `10-60s`, `1-10m`, `gt10m`.",
            "type": [
              "string",
              "null"
            ]
          },
          "format": {
            "description": "Report/export format: `html`, `md`, `csv`, `json`, `pdf`.",
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "description": "Scan or target kind: `git`, `image`, `path`, or a new scan type id (`[a-z0-9_]{1,32}`).",
            "type": [
              "string",
              "null"
            ]
          },
          "method": {
            "description": "HTTP method of `api.call`.",
            "type": [
              "string",
              "null"
            ]
          },
          "severity": {
            "description": "Severity filter in use (`critical`..`none`).",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "Outcome: `ok`, `failed`, `canceled`, or an HTTP status class `2xx`..`5xx`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "EventProps",
        "type": "object"
      },
      "FeatureArea": {
        "description": "The mycve feature areas dashboards are cut by.",
        "enum": [
          "overview",
          "findings",
          "inventory",
          "infrastructure",
          "projects",
          "cves",
          "scans",
          "pipelines",
          "fixes",
          "secrets",
          "reports",
          "integrations",
          "templates",
          "workspace",
          "api"
        ],
        "title": "FeatureArea",
        "type": "string"
      },
      "Health": {
        "description": "`GET /v1/health` (public).",
        "properties": {
          "db": {
            "type": "boolean"
          },
          "status": {
            "description": "`ok` or `degraded`.",
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "status",
          "version",
          "db"
        ],
        "title": "Health",
        "type": "object"
      },
      "HeatCell": {
        "description": "A heatmap cell (sequential colour = value).",
        "properties": {
          "value": {
            "format": "double",
            "type": "number"
          },
          "x": {
            "type": "string"
          },
          "y": {
            "type": "string"
          }
        },
        "required": [
          "x",
          "y",
          "value"
        ],
        "title": "HeatCell",
        "type": "object"
      },
      "IacTemplate": {
        "description": "A template with its parameters and files.",
        "properties": {
          "category": {
            "$ref": "#/components/schemas/TemplateCategory"
          },
          "compliance": {
            "items": {
              "$ref": "#/components/schemas/ComplianceControl"
            },
            "type": "array"
          },
          "delivery": {
            "items": {
              "$ref": "#/components/schemas/DeliveryMode"
            },
            "type": "array"
          },
          "description": {
            "description": "Markdown: what it creates and why each default is the secure one.",
            "type": "string"
          },
          "files": {
            "description": "Output paths with the default parameters.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "type": "string"
          },
          "next_steps": {
            "description": "What to do after generating (apply, wire secrets, ...), Markdown.",
            "type": "string"
          },
          "params": {
            "items": {
              "$ref": "#/components/schemas/TemplateParam"
            },
            "type": "array"
          },
          "provider": {
            "$ref": "#/components/schemas/TemplateProvider"
          },
          "remediates": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "summary": {
            "type": "string"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "title": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "version",
          "title",
          "summary",
          "description",
          "category",
          "provider",
          "tags",
          "compliance",
          "remediates",
          "params",
          "files",
          "next_steps",
          "delivery"
        ],
        "title": "IacTemplate",
        "type": "object"
      },
      "IacTemplateSummary": {
        "description": "A template in the library list.",
        "properties": {
          "category": {
            "$ref": "#/components/schemas/TemplateCategory"
          },
          "compliance": {
            "items": {
              "$ref": "#/components/schemas/ComplianceControl"
            },
            "type": "array"
          },
          "id": {
            "description": "kebab-case, stable: `aws-s3-private-bucket`.",
            "type": "string"
          },
          "provider": {
            "$ref": "#/components/schemas/TemplateProvider"
          },
          "remediates": {
            "description": "Posture rule ids (`MYCVE-IAC-005`) a finding of which this template fixes.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "summary": {
            "type": "string"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "title": {
            "type": "string"
          },
          "version": {
            "description": "Semantic version, bumped on every change of the output.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "version",
          "title",
          "summary",
          "category",
          "provider",
          "tags",
          "compliance",
          "remediates"
        ],
        "title": "IacTemplateSummary",
        "type": "object"
      },
      "InsightsReport": {
        "description": "`GET /v1/analytics/insights` (admins): the workspace's insights report, also as `?format=md|html|csv`.",
        "properties": {
          "active_users": {
            "items": {
              "format": "int64",
              "type": "integer"
            },
            "type": "array"
          },
          "areas": {
            "description": "Most used first.",
            "items": {
              "$ref": "#/components/schemas/AreaSummary"
            },
            "type": "array"
          },
          "from": {
            "format": "date",
            "type": "string"
          },
          "generated_at": {
            "format": "date-time",
            "type": "string"
          },
          "members": {
            "description": "Most active first.",
            "items": {
              "$ref": "#/components/schemas/MemberUsage"
            },
            "type": "array"
          },
          "security": {
            "items": {
              "$ref": "#/components/schemas/Kpi"
            },
            "type": "array"
          },
          "surfaces": {
            "additionalProperties": {
              "format": "int64",
              "type": "integer"
            },
            "type": "object"
          },
          "to": {
            "format": "date",
            "type": "string"
          },
          "usage": {
            "items": {
              "$ref": "#/components/schemas/Kpi"
            },
            "type": "array"
          },
          "workspace_id": {
            "format": "int64",
            "type": "integer"
          },
          "workspace_name": {
            "type": "string"
          }
        },
        "required": [
          "workspace_id",
          "workspace_name",
          "from",
          "to",
          "generated_at",
          "security",
          "usage",
          "areas",
          "surfaces",
          "active_users",
          "members"
        ],
        "title": "InsightsReport",
        "type": "object"
      },
      "Kpi": {
        "description": "A headline number (stat tile): value, change over the range, and a sparkline. `tone` is set only when the number is a status.",
        "properties": {
          "delta": {
            "description": "Change vs the start of the range (same unit); null if unknown.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "key": {
            "description": "`open_critical_high`, `coverage`, `mttr_hours`, `new_7d`, `resolved_7d`, `stale_assets`, `secrets_open`, `fix_prs`.",
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "tone": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StatusTone"
              },
              {
                "type": "null"
              }
            ]
          },
          "trend": {
            "items": {
              "format": "double",
              "type": "number"
            },
            "type": "array"
          },
          "unit": {
            "description": "`count`, `percent`, `hours`.",
            "type": "string"
          },
          "up_is_good": {
            "description": "Whether a rising value is good (coverage) or bad (open findings).",
            "type": "boolean"
          },
          "value": {
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "key",
          "label",
          "value",
          "unit",
          "up_is_good",
          "trend"
        ],
        "title": "Kpi",
        "type": "object"
      },
      "MemberUsage": {
        "description": "Events of one member over the range (admins only; never which pages or items).",
        "properties": {
          "active_days": {
            "format": "int64",
            "type": "integer"
          },
          "events": {
            "format": "int64",
            "type": "integer"
          },
          "identity": {
            "description": "The member's sign-in identity, as on the members page.",
            "type": "string"
          },
          "user_id": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "user_id",
          "identity",
          "events",
          "active_days"
        ],
        "title": "MemberUsage",
        "type": "object"
      },
      "MembershipSource": {
        "description": "How an asset got into a project.\n\n- `manual`\n- `suggestion`: Accepted from a suggestion.\n- `auto`: Put there by mycve: the asset was in no project (docs/PROJECTS.md \"Every asset is in a project\").\n- `source`: Listed by one of the project's sources (a GitLab group, a GitHub organization, a Docker Hub namespace, an Artifact Registry location).",
        "enum": [
          "manual",
          "suggestion",
          "auto",
          "source"
        ],
        "title": "MembershipSource",
        "type": "string",
        "x-enumDescriptions": {
          "auto": "Put there by mycve: the asset was in no project (docs/PROJECTS.md \"Every asset is in a project\").",
          "source": "Listed by one of the project's sources (a GitLab group, a GitHub organization, a Docker Hub namespace, an Artifact Registry location).",
          "suggestion": "Accepted from a suggestion."
        }
      },
      "ParamKind": {
        "description": "A parameter's type.\n\n- `string`\n- `int`\n- `bool`\n- `enum`: One of `options`.\n- `list`: A list of strings (each matching `pattern`).",
        "enum": [
          "string",
          "int",
          "bool",
          "enum",
          "list"
        ],
        "title": "ParamKind",
        "type": "string",
        "x-enumDescriptions": {
          "enum": "One of `options`.",
          "list": "A list of strings (each matching `pattern`)."
        }
      },
      "PingResult": {
        "description": "`POST /v1/webhooks/{webhook_id}/ping`: the ping delivery queued.",
        "properties": {
          "delivery_id": {
            "type": "string"
          }
        },
        "required": [
          "delivery_id"
        ],
        "title": "PingResult",
        "type": "object"
      },
      "PortfolioProject": {
        "description": "One project's numbers over the period.",
        "properties": {
          "assets": {
            "format": "int64",
            "type": "integer"
          },
          "color_slot": {
            "maximum": 255,
            "minimum": 0,
            "type": "integer"
          },
          "covered": {
            "description": "Scannable assets with a successful scan in the 7 days before period end.",
            "format": "int64",
            "type": "integer"
          },
          "mttr_hours": {
            "description": "Mean hours from first seen to resolved, critical/high resolved in the period.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "new_findings": {
            "format": "int64",
            "type": "integer"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts",
            "description": "Open findings at period end, and at period start (for deltas)."
          },
          "open_start": {
            "$ref": "#/components/schemas/SeverityCounts"
          },
          "project_id": {
            "format": "int64",
            "type": "integer"
          },
          "rank": {
            "format": "int64",
            "type": "integer"
          },
          "resolved": {
            "format": "int64",
            "type": "integer"
          },
          "risk_score": {
            "description": "Filled by [`build`]: [`risk_score`] and 1-based rank (1 = most at risk).",
            "format": "double",
            "type": "number"
          },
          "scannable": {
            "format": "int64",
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "stale_assets": {
            "format": "int64",
            "type": "integer"
          },
          "top_cves": {
            "description": "Highest-scored open CVE ids (at most 5).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "trend": {
            "description": "Open critical+high per week of the period, oldest first.",
            "items": {
              "format": "int64",
              "type": "integer"
            },
            "type": "array"
          }
        },
        "required": [
          "project_id",
          "name",
          "slug",
          "color_slot",
          "assets",
          "open",
          "open_start",
          "new_findings",
          "resolved",
          "scannable",
          "covered",
          "stale_assets",
          "trend",
          "top_cves",
          "risk_score",
          "rank"
        ],
        "title": "PortfolioProject",
        "type": "object"
      },
      "PortfolioReport": {
        "description": "The whole portfolio report, frozen at generation.",
        "properties": {
          "generated_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "The `reports` row id (0 until stored).",
            "format": "int64",
            "type": "integer"
          },
          "period_end": {
            "format": "date-time",
            "type": "string"
          },
          "period_start": {
            "format": "date-time",
            "type": "string"
          },
          "projects": {
            "description": "Most at risk first.",
            "items": {
              "$ref": "#/components/schemas/PortfolioProject"
            },
            "type": "array"
          },
          "scope": {
            "$ref": "#/components/schemas/ReportScope"
          },
          "shared_cves": {
            "items": {
              "$ref": "#/components/schemas/SharedCve"
            },
            "type": "array"
          },
          "title": {
            "type": "string"
          },
          "totals": {
            "$ref": "#/components/schemas/PortfolioTotals"
          },
          "trigger": {
            "type": "string"
          },
          "weekly": {
            "items": {
              "$ref": "#/components/schemas/PortfolioWeek"
            },
            "type": "array"
          },
          "workspace_id": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "scope",
          "title",
          "trigger",
          "period_start",
          "period_end",
          "generated_at",
          "totals",
          "projects",
          "shared_cves",
          "weekly"
        ],
        "title": "PortfolioReport",
        "type": "object"
      },
      "PortfolioTotals": {
        "description": "Portfolio totals. Assets can be in several projects, so `assets`, `open` and coverage are over **distinct** assets (gathered by the job), never sums of the project rows.",
        "properties": {
          "assets": {
            "format": "int64",
            "type": "integer"
          },
          "at_risk_projects": {
            "format": "int64",
            "type": "integer"
          },
          "coverage": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "mttr_hours": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "new_findings": {
            "format": "int64",
            "type": "integer"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts"
          },
          "projects": {
            "format": "int64",
            "type": "integer"
          },
          "resolved": {
            "format": "int64",
            "type": "integer"
          },
          "scans_done": {
            "format": "int64",
            "type": "integer"
          },
          "scans_failed": {
            "format": "int64",
            "type": "integer"
          },
          "unassigned_assets": {
            "description": "Workspace assets in no project (shown as a call to action).",
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "projects",
          "at_risk_projects",
          "assets",
          "unassigned_assets",
          "open",
          "new_findings",
          "resolved",
          "scans_done",
          "scans_failed"
        ],
        "title": "PortfolioTotals",
        "type": "object"
      },
      "PortfolioWeek": {
        "description": "One week of the portfolio trend.",
        "properties": {
          "new_findings": {
            "format": "int64",
            "type": "integer"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts"
          },
          "resolved": {
            "format": "int64",
            "type": "integer"
          },
          "week_start": {
            "format": "date",
            "type": "string"
          }
        },
        "required": [
          "week_start",
          "open",
          "new_findings",
          "resolved"
        ],
        "title": "PortfolioWeek",
        "type": "object"
      },
      "PostureRuleSummary": {
        "description": "Posture findings of one rule across the workspace.",
        "properties": {
          "area": {
            "description": "`terraform`, `kubernetes`, `container`, `compose`, `ci`, `platform`, or `trivy` for trivy's own checks.",
            "type": "string"
          },
          "controls": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "examples": {
            "description": "Up to 10 `target · path` examples.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "findings": {
            "format": "int64",
            "type": "integer"
          },
          "remediation": {
            "type": "string"
          },
          "rule_id": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/Severity"
          },
          "targets": {
            "description": "Distinct scanned targets (repositories, folders) with a finding.",
            "format": "int64",
            "type": "integer"
          },
          "templates": {
            "description": "Templates that fix it (`/v1/templates/{id}`).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "rule_id",
          "severity",
          "title",
          "area",
          "findings",
          "targets",
          "examples",
          "remediation",
          "templates",
          "controls"
        ],
        "title": "PostureRuleSummary",
        "type": "object"
      },
      "PostureSummary": {
        "description": "`GET /v1/infrastructure/posture`.",
        "properties": {
          "by_area": {
            "additionalProperties": {
              "format": "int64",
              "type": "integer"
            },
            "type": "object"
          },
          "by_severity": {
            "$ref": "#/components/schemas/SeverityCounts"
          },
          "findings": {
            "format": "int64",
            "type": "integer"
          },
          "rules": {
            "description": "Highest severity first, then most findings.",
            "items": {
              "$ref": "#/components/schemas/PostureRuleSummary"
            },
            "type": "array"
          },
          "targets_affected": {
            "format": "int64",
            "type": "integer"
          },
          "targets_scanned": {
            "format": "int64",
            "type": "integer"
          },
          "templates_in_use": {
            "additionalProperties": {
              "format": "int64",
              "type": "integer"
            },
            "description": "Generated files found in scans: template id -> files.",
            "type": "object"
          }
        },
        "required": [
          "findings",
          "targets_scanned",
          "targets_affected",
          "by_severity",
          "by_area",
          "rules",
          "templates_in_use"
        ],
        "title": "PostureSummary",
        "type": "object"
      },
      "Project": {
        "description": "A project (`GET /v1/projects/{project_id}`, `GET /api/projects/{id}`).",
        "properties": {
          "asset_count": {
            "format": "int64",
            "type": "integer"
          },
          "at_risk": {
            "description": "Critical or high open.",
            "type": "boolean"
          },
          "auto": {
            "description": "Made by mycve for an asset that was in no project (named after it); becomes a regular project once someone renames it or adds assets or a source.",
            "type": "boolean"
          },
          "color_slot": {
            "description": "1..=8, the project's categorical chart colour slot.",
            "maximum": 255,
            "minimum": 0,
            "type": "integer"
          },
          "coverage": {
            "description": "Percent of its scannable assets scanned in the last 7 days.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts",
            "description": "Open findings over its assets (latest finished scan of each)."
          },
          "shape": {
            "$ref": "#/components/schemas/ProjectShape",
            "description": "What it is made of, from its assets' kinds."
          },
          "slug": {
            "description": "URL-safe, unique per workspace: `/app/projects/{slug}`.",
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "workspace_id": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "name",
          "slug",
          "color_slot",
          "shape",
          "asset_count",
          "open",
          "at_risk",
          "created_at",
          "updated_at"
        ],
        "title": "Project",
        "type": "object"
      },
      "ProjectAsset": {
        "description": "One asset of a project (`GET /v1/projects/{project_id}/assets`).",
        "properties": {
          "added_at": {
            "format": "date-time",
            "type": "string"
          },
          "added_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "asset_id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/AssetKind"
          },
          "name": {
            "description": "Display name when it was added (the live name is in `/v1/assets`).",
            "type": "string"
          },
          "source": {
            "$ref": "#/components/schemas/MembershipSource"
          },
          "target": {
            "description": "What a scan of it starts with (git URL, image reference), when known.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "asset_id",
          "kind",
          "name",
          "source",
          "added_at"
        ],
        "title": "ProjectAsset",
        "type": "object"
      },
      "ProjectAssetInput": {
        "description": "An asset to add or remove: an existing asset id, or a kind and target that may not be scanned yet (`{\"kind\": \"repository\", \"target\": \"https://github.com/acme/web\"}`; the id is the one its first scan will give it).",
        "properties": {
          "asset_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AssetKind"
              },
              {
                "type": "null"
              }
            ]
          },
          "target": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "ProjectAssetInput",
        "type": "object"
      },
      "ProjectAssetsChange": {
        "description": "`POST /v1/projects/{project_id}/assets` and `.../assets/remove`: asset ids, or assets by id or `{kind, target}` (removal also by `target` alone).",
        "properties": {
          "asset_ids": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "assets": {
            "items": {
              "$ref": "#/components/schemas/ProjectAssetInput"
            },
            "type": "array"
          }
        },
        "title": "ProjectAssetsChange",
        "type": "object"
      },
      "ProjectAssetsResult": {
        "description": "Result of adding or removing assets.",
        "properties": {
          "added": {
            "format": "int64",
            "type": "integer"
          },
          "removed": {
            "format": "int64",
            "type": "integer"
          },
          "unknown": {
            "description": "Ids that are not assets of this workspace (ignored when adding) or not in the project (when removing).",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "added",
          "removed",
          "unknown"
        ],
        "title": "ProjectAssetsResult",
        "type": "object"
      },
      "ProjectDashboard": {
        "description": "`GET /v1/projects/{project_id}/dashboard?range=7d|30d|90d` — everything the per-project dashboard draws, in one response (docs/DASHBOARDS.md).",
        "properties": {
          "activity": {
            "items": {
              "$ref": "#/components/schemas/DashboardEvent"
            },
            "type": "array"
          },
          "age_heatmap": {
            "description": "Severity (y) × age bucket (x: `<7d`, `7-30d`, `30-90d`, `>90d`).",
            "items": {
              "$ref": "#/components/schemas/HeatCell"
            },
            "type": "array"
          },
          "assets": {
            "items": {
              "$ref": "#/components/schemas/DashboardAsset"
            },
            "type": "array"
          },
          "by_asset": {
            "description": "Open findings per asset, stacked by severity (top 12 + Other).",
            "items": {
              "$ref": "#/components/schemas/BarDatum"
            },
            "type": "array"
          },
          "ecosystems": {
            "description": "Packages per ecosystem (donut, at most 8 + Other).",
            "items": {
              "$ref": "#/components/schemas/Segment"
            },
            "type": "array"
          },
          "flow": {
            "$ref": "#/components/schemas/SankeyData",
            "description": "Dependency → CVE flow."
          },
          "flow_trend": {
            "description": "New vs resolved per day (two series, bar).",
            "items": {
              "$ref": "#/components/schemas/Series"
            },
            "type": "array"
          },
          "generated_at": {
            "format": "date-time",
            "type": "string"
          },
          "kpis": {
            "items": {
              "$ref": "#/components/schemas/Kpi"
            },
            "type": "array"
          },
          "open_trend": {
            "description": "Open findings per severity per day (stacked area), worst first.",
            "items": {
              "$ref": "#/components/schemas/Series"
            },
            "type": "array"
          },
          "project": {
            "$ref": "#/components/schemas/Project"
          },
          "range": {
            "description": "`7d`, `30d` or `90d`.",
            "type": "string"
          },
          "top_cves": {
            "items": {
              "$ref": "#/components/schemas/DashboardCve"
            },
            "type": "array"
          },
          "treemap": {
            "$ref": "#/components/schemas/TreemapNode",
            "description": "Assets → packages sized by open findings, coloured by worst severity."
          }
        },
        "required": [
          "project",
          "range",
          "generated_at",
          "kpis",
          "open_trend",
          "flow_trend",
          "by_asset",
          "ecosystems",
          "age_heatmap",
          "treemap",
          "flow",
          "top_cves",
          "assets",
          "activity"
        ],
        "title": "ProjectDashboard",
        "type": "object"
      },
      "ProjectPatch": {
        "description": "`PATCH /v1/projects/{project_id}` (absent fields stay; `\"description\": null` or `\"\"` clears it).",
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "ProjectPatch",
        "type": "object"
      },
      "ProjectRequest": {
        "description": "`POST /v1/projects` and `POST /api/projects`.",
        "properties": {
          "asset_ids": {
            "description": "Asset ids to add at once (`id` of `listAssets`); every id must be an asset of the workspace.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "assets": {
            "description": "Assets by id or by `{kind, target}` (also before their first scan).",
            "items": {
              "$ref": "#/components/schemas/ProjectAssetInput"
            },
            "type": "array"
          },
          "description": {
            "description": "At most 2000 characters; empty means none.",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "1 to 80 characters; names may repeat (the slug is unique).",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "title": "ProjectRequest",
        "type": "object"
      },
      "ProjectShape": {
        "description": "What a project is made of, from its assets' kinds.\n\n- `empty`: No assets yet.\n- `repo_and_images`: Repositories (or uploaded folders) and the images they build.\n- `repo`: Repositories or folders only.\n- `image`: Images (and base images) only.\n- `infrastructure`: Registries and cloud projects only.\n- `mixed`: Anything else (code and infrastructure together).",
        "enum": [
          "empty",
          "repo_and_images",
          "repo",
          "image",
          "infrastructure",
          "mixed"
        ],
        "title": "ProjectShape",
        "type": "string",
        "x-enumDescriptions": {
          "empty": "No assets yet.",
          "image": "Images (and base images) only.",
          "infrastructure": "Registries and cloud projects only.",
          "mixed": "Anything else (code and infrastructure together).",
          "repo": "Repositories or folders only.",
          "repo_and_images": "Repositories (or uploaded folders) and the images they build."
        }
      },
      "ProjectSource": {
        "description": "A source of a project (`GET /api/projects/{id}/sources`).",
        "properties": {
          "account": {
            "type": "string"
          },
          "asset_count": {
            "description": "Assets it put in the project at its last sync.",
            "format": "int64",
            "type": "integer"
          },
          "connector_id": {
            "format": "int64",
            "type": "integer"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "include_subgroups": {
            "type": "boolean"
          },
          "kind": {
            "$ref": "#/components/schemas/SourceKind"
          },
          "last_error": {
            "description": "Why the last sync could not list it (its assets stay).",
            "type": [
              "string",
              "null"
            ]
          },
          "last_synced_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "type": "string"
          },
          "project_id": {
            "format": "int64",
            "type": "integer"
          },
          "provider": {
            "description": "The connector's provider and account (`gitlab`, `acme`).",
            "type": "string"
          }
        },
        "required": [
          "id",
          "project_id",
          "connector_id",
          "provider",
          "account",
          "kind",
          "path",
          "include_subgroups",
          "asset_count",
          "created_at"
        ],
        "title": "ProjectSource",
        "type": "object"
      },
      "ProjectSourceRequest": {
        "description": "`POST /api/projects/{id}/sources` and `POST /api/projects/sources/preview`.",
        "properties": {
          "connector_id": {
            "format": "int64",
            "type": "integer"
          },
          "include_subgroups": {
            "description": "GitLab: subgroups' projects too (default true).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "kind": {
            "description": "Defaults to the connector's kind of source.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SourceKind"
              },
              {
                "type": "null"
              }
            ]
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "connector_id",
          "path"
        ],
        "title": "ProjectSourceRequest",
        "type": "object"
      },
      "ProjectSuggestion": {
        "description": "A suggested project (`GET /v1/project-suggestions`).",
        "properties": {
          "assets": {
            "items": {
              "$ref": "#/components/schemas/SuggestedAsset"
            },
            "type": "array"
          },
          "confidence": {
            "description": "0..1: 0.9 with a build edge, 0.6 on names only.",
            "format": "double",
            "type": "number"
          },
          "created_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "fingerprint": {
            "description": "`ps-` + 16 hex over the sorted core asset ids (sources and built images). A dismissed fingerprint is never suggested again.",
            "type": "string"
          },
          "id": {
            "description": "0 until stored.",
            "format": "int64",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "project_id": {
            "description": "The project made from it, once accepted.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "reason": {
            "description": "In words: \"acme/web builds ghcr.io/acme/web:1.2 and 1 more image\".",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SuggestionStatus"
          }
        },
        "required": [
          "id",
          "fingerprint",
          "name",
          "assets",
          "reason",
          "confidence",
          "status"
        ],
        "title": "ProjectSuggestion",
        "type": "object"
      },
      "RenderTemplate": {
        "description": "`POST /v1/templates/{template_id}/render`.",
        "properties": {
          "params": {
            "additionalProperties": {},
            "description": "Parameter values by name; strings are accepted for every type (`\"8080\"`, `\"true\"`, `\"a,b\"`), as the CLI sends them.",
            "type": "object"
          }
        },
        "title": "RenderTemplate",
        "type": "object"
      },
      "RenderedFile": {
        "description": "One generated file.",
        "properties": {
          "content": {
            "type": "string"
          },
          "path": {
            "description": "Relative to the repository root.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "content"
        ],
        "title": "RenderedFile",
        "type": "object"
      },
      "RenderedTemplate": {
        "description": "The output of a render.",
        "properties": {
          "files": {
            "items": {
              "$ref": "#/components/schemas/RenderedFile"
            },
            "type": "array"
          },
          "next_steps": {
            "type": "string"
          },
          "params": {
            "additionalProperties": {},
            "description": "Every parameter with the value used (defaults filled in).",
            "type": "object"
          },
          "template_id": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "template_id",
          "version",
          "params",
          "files",
          "next_steps"
        ],
        "title": "RenderedTemplate",
        "type": "object"
      },
      "ReportJob": {
        "description": "202 of `POST /v1/reports`: poll the job, then fetch the report.",
        "properties": {
          "job_id": {
            "format": "int64",
            "type": "integer"
          },
          "job_url": {
            "description": "`/v1/jobs/{job_id}`.",
            "format": "uri-reference",
            "type": "string"
          },
          "report_id": {
            "description": "Set once done.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "description": "queued | leased | running | done | failed | canceled | dead",
            "type": "string"
          }
        },
        "required": [
          "job_id",
          "status",
          "job_url"
        ],
        "title": "ReportJob",
        "type": "object"
      },
      "ReportListItem": {
        "description": "One entry of `GET /v1/reports` (headline numbers; the body is `GET /v1/reports/{report_id}`).",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "job_id": {
            "description": "The `report_build` job that made it.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "new_findings": {
            "format": "int64",
            "type": "integer"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts"
          },
          "period_end": {
            "format": "date-time",
            "type": "string"
          },
          "period_start": {
            "format": "date-time",
            "type": "string"
          },
          "project_ids": {
            "items": {
              "format": "int64",
              "type": "integer"
            },
            "type": "array"
          },
          "resolved": {
            "format": "int64",
            "type": "integer"
          },
          "scope": {
            "description": "`workspace`, `project` or `portfolio`.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "trigger": {
            "description": "`scan`, `monthly`, `manual` or `scheduled`.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "scope",
          "project_ids",
          "trigger",
          "title",
          "period_start",
          "period_end",
          "created_at",
          "open",
          "new_findings",
          "resolved"
        ],
        "title": "ReportListItem",
        "type": "object"
      },
      "ReportScope": {
        "description": "What a report covers: the whole `workspace`, one `project`, or a `portfolio` of projects (`product` / `product_id` / `product_ids` are accepted as deprecated names).",
        "oneOf": [
          {
            "properties": {
              "scope": {
                "const": "workspace"
              }
            },
            "required": [
              "scope"
            ],
            "type": "object"
          },
          {
            "properties": {
              "project_id": {
                "format": "int64",
                "type": "integer"
              },
              "scope": {
                "const": "project"
              }
            },
            "required": [
              "scope",
              "project_id"
            ],
            "type": "object"
          },
          {
            "properties": {
              "project_ids": {
                "items": {
                  "format": "int64",
                  "type": "integer"
                },
                "type": "array"
              },
              "scope": {
                "const": "portfolio"
              }
            },
            "required": [
              "scope"
            ],
            "type": "object"
          }
        ],
        "title": "ReportScope"
      },
      "Role": {
        "description": "A member's role in a workspace. Ordered: `viewer < member < admin < owner`.\n\n- `viewer`: Reads everything in the workspace.\n- `member`: Also starts scans, fixes and pipeline runs, and manages projects and reports.\n- `admin`: Also manages members, invitations, service tokens, webhooks and settings.\n- `owner`: Also renames or deletes the workspace; every workspace keeps at least one owner.",
        "enum": [
          "viewer",
          "member",
          "admin",
          "owner"
        ],
        "title": "Role",
        "type": "string",
        "x-enumDescriptions": {
          "admin": "Also manages members, invitations, service tokens, webhooks and settings.",
          "member": "Also starts scans, fixes and pipeline runs, and manages projects and reports.",
          "owner": "Also renames or deletes the workspace; every workspace keeps at least one owner.",
          "viewer": "Reads everything in the workspace."
        }
      },
      "SankeyData": {
        "description": "Dependency → CVE flow (`GET /v1/analytics/flow`): columns asset → package → CVE → severity, collapsed to the top N per column with \"+N more\" nodes, like the impact graph.",
        "properties": {
          "columns": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "links": {
            "items": {
              "$ref": "#/components/schemas/SankeyLink"
            },
            "type": "array"
          },
          "nodes": {
            "items": {
              "$ref": "#/components/schemas/SankeyNode"
            },
            "type": "array"
          }
        },
        "required": [
          "columns",
          "nodes",
          "links"
        ],
        "title": "SankeyData",
        "type": "object"
      },
      "SankeyLink": {
        "description": "A sankey link; `value` is its width.",
        "properties": {
          "color": {
            "description": "Colour of the flow; usually the severity of what flows.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SeriesColor"
              },
              {
                "type": "null"
              }
            ]
          },
          "source": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "value": {
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "source",
          "target",
          "value"
        ],
        "title": "SankeyLink",
        "type": "object"
      },
      "SankeyNode": {
        "description": "A sankey node in a column (0 = leftmost).",
        "properties": {
          "color": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SeriesColor"
              },
              {
                "type": "null"
              }
            ]
          },
          "column": {
            "maximum": 255,
            "minimum": 0,
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "label",
          "column"
        ],
        "title": "SankeyNode",
        "type": "object"
      },
      "SecurityDay": {
        "description": "Security state per day, for the whole workspace (`project_id` null) or one project (`security_daily`).",
        "properties": {
          "assets": {
            "format": "int64",
            "type": "integer"
          },
          "covered": {
            "description": "Scannable assets scanned successfully in the 7 days before.",
            "format": "int64",
            "type": "integer"
          },
          "day": {
            "format": "date",
            "type": "string"
          },
          "fix_prs": {
            "format": "int64",
            "type": "integer"
          },
          "mttr_hours": {
            "description": "Mean age in hours of the critical/high findings resolved that day.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "new_findings": {
            "description": "Findings that appeared / went away that day.",
            "format": "int64",
            "type": "integer"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts",
            "description": "Open findings at the end of the day."
          },
          "pipeline_failures": {
            "format": "int64",
            "type": "integer"
          },
          "project_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "resolved": {
            "format": "int64",
            "type": "integer"
          },
          "scannable": {
            "format": "int64",
            "type": "integer"
          },
          "scans_done": {
            "format": "int64",
            "type": "integer"
          },
          "scans_failed": {
            "format": "int64",
            "type": "integer"
          },
          "secrets_open": {
            "description": "Open secret incidents, fix PRs opened, pipeline runs failed.",
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "day",
          "open",
          "new_findings",
          "resolved",
          "scans_done",
          "scans_failed",
          "assets",
          "covered",
          "scannable",
          "secrets_open",
          "fix_prs",
          "pipeline_failures"
        ],
        "title": "SecurityDay",
        "type": "object"
      },
      "SecuritySeries": {
        "description": "`GET /v1/analytics/security?from=&to=&project_id=`.",
        "properties": {
          "days": {
            "description": "One per day, oldest first; days without data repeat the last.",
            "items": {
              "$ref": "#/components/schemas/SecurityDay"
            },
            "type": "array"
          },
          "from": {
            "format": "date",
            "type": "string"
          },
          "project_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "to": {
            "format": "date",
            "type": "string"
          }
        },
        "required": [
          "from",
          "to",
          "days"
        ],
        "title": "SecuritySeries",
        "type": "object"
      },
      "Segment": {
        "description": "A segment of a stacked bar, or a donut slice.",
        "properties": {
          "color": {
            "$ref": "#/components/schemas/SeriesColor"
          },
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "value": {
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "key",
          "label",
          "color",
          "value"
        ],
        "title": "Segment",
        "type": "object"
      },
      "Series": {
        "description": "A line/area/sparkline series. `key` is the entity (project id, severity, area) so colour and hover stay attached to it.",
        "properties": {
          "color": {
            "$ref": "#/components/schemas/SeriesColor"
          },
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "points": {
            "items": {
              "$ref": "#/components/schemas/TimePoint"
            },
            "type": "array"
          }
        },
        "required": [
          "key",
          "label",
          "color",
          "points"
        ],
        "title": "Series",
        "type": "object"
      },
      "SeriesColor": {
        "description": "A colour role, never a colour: `slot-1`..`slot-8` (categorical series), `sev-<severity>`, `status-<good|warning|serious|critical>`, `muted` or `accent`. Clients map roles to their palette.",
        "examples": [
          "slot-1"
        ],
        "pattern": "^(slot-[1-8]|sev-(critical|high|medium|low|none)|status-(good|warning|serious|critical)|muted|accent)$",
        "title": "SeriesColor",
        "type": "string"
      },
      "Severity": {
        "description": "CVSS severity band (upper case on the wire).\n\n- `NONE`: Score 0.0, or not scored yet.\n- `LOW`: CVSS 0.1 to 3.9.\n- `MEDIUM`: CVSS 4.0 to 6.9.\n- `HIGH`: CVSS 7.0 to 8.9.\n- `CRITICAL`: CVSS 9.0 to 10.0.",
        "enum": [
          "NONE",
          "LOW",
          "MEDIUM",
          "HIGH",
          "CRITICAL"
        ],
        "title": "Severity",
        "type": "string",
        "x-enumDescriptions": {
          "CRITICAL": "CVSS 9.0 to 10.0.",
          "HIGH": "CVSS 7.0 to 8.9.",
          "LOW": "CVSS 0.1 to 3.9.",
          "MEDIUM": "CVSS 4.0 to 6.9.",
          "NONE": "Score 0.0, or not scored yet."
        }
      },
      "SeverityCounts": {
        "description": "Open findings by severity (CRITICAL, HIGH, MEDIUM, LOW, NONE).",
        "properties": {
          "critical": {
            "format": "int64",
            "type": "integer"
          },
          "high": {
            "format": "int64",
            "type": "integer"
          },
          "low": {
            "format": "int64",
            "type": "integer"
          },
          "medium": {
            "format": "int64",
            "type": "integer"
          },
          "none": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "critical",
          "high",
          "medium",
          "low",
          "none"
        ],
        "title": "SeverityCounts",
        "type": "object"
      },
      "SharedCve": {
        "description": "A CVE open in at least two projects: fixing it once helps all.",
        "properties": {
          "assets": {
            "description": "Distinct assets it is open in.",
            "format": "int64",
            "type": "integer"
          },
          "cve_id": {
            "type": "string"
          },
          "fix": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_ids": {
            "items": {
              "format": "int64",
              "type": "integer"
            },
            "type": "array"
          },
          "score": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "severity": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Severity"
              },
              {
                "type": "null"
              }
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "cve_id",
          "project_ids",
          "assets"
        ],
        "title": "SharedCve",
        "type": "object"
      },
      "SourceKind": {
        "description": "What a project source is (docs/PROJECTS.md \"Sources\").\n\n- `gitlab_group`: A GitLab group or subgroup (`berry`, `prod-orm/api`).\n- `github_owner`: A GitHub organization or user (`acme`).\n- `dockerhub_namespace`: A Docker Hub namespace (`acme`).\n- `gcp_location`: An Artifact Registry location, optionally one repository in it (`us-central1`, `us-central1/web`).",
        "enum": [
          "gitlab_group",
          "github_owner",
          "dockerhub_namespace",
          "gcp_location"
        ],
        "title": "SourceKind",
        "type": "string",
        "x-enumDescriptions": {
          "dockerhub_namespace": "A Docker Hub namespace (`acme`).",
          "gcp_location": "An Artifact Registry location, optionally one repository in it (`us-central1`, `us-central1/web`).",
          "github_owner": "A GitHub organization or user (`acme`).",
          "gitlab_group": "A GitLab group or subgroup (`berry`, `prod-orm/api`)."
        }
      },
      "StatusTone": {
        "description": "Reserved status tones.",
        "enum": [
          "good",
          "warning",
          "serious",
          "critical"
        ],
        "title": "StatusTone",
        "type": "string"
      },
      "SuggestedAsset": {
        "description": "One asset of a suggestion.",
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "at_risk": {
            "description": "Critical or high open findings.",
            "type": "boolean"
          },
          "kind": {
            "$ref": "#/components/schemas/AssetKind"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/SuggestionRole"
          },
          "target": {
            "description": "What a scan of it starts with, when known.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "asset_id",
          "kind",
          "name",
          "role"
        ],
        "title": "SuggestedAsset",
        "type": "object"
      },
      "SuggestionRole": {
        "description": "Why an asset is in a suggestion.\n\n- `source`: The repository the project is named after.\n- `also_source`: Another repository that builds the same image.\n- `built_image`: An image the source builds (graph edge `built_image`).\n- `named_image`: An image whose repository name matches the source's (no edge).\n- `base_image`: A base image only this group uses.\n- `registry`: Registry or cloud project of its images (shared, not exclusive).",
        "enum": [
          "source",
          "also_source",
          "built_image",
          "named_image",
          "base_image",
          "registry"
        ],
        "title": "SuggestionRole",
        "type": "string",
        "x-enumDescriptions": {
          "also_source": "Another repository that builds the same image.",
          "base_image": "A base image only this group uses.",
          "built_image": "An image the source builds (graph edge `built_image`).",
          "named_image": "An image whose repository name matches the source's (no edge).",
          "registry": "Registry or cloud project of its images (shared, not exclusive).",
          "source": "The repository the project is named after."
        }
      },
      "SuggestionStatus": {
        "enum": [
          "open",
          "accepted",
          "dismissed"
        ],
        "title": "SuggestionStatus",
        "type": "string"
      },
      "TargetKind": {
        "description": "What a scan looks at.\n\n- `path`: A local folder uploaded by `cve scan path` (CLI only).\n- `git`: A Git repository URL (`https://github.com/acme/api`).\n- `image`: A container image reference (`ghcr.io/acme/api:1.4.2`).",
        "enum": [
          "path",
          "git",
          "image"
        ],
        "title": "TargetKind",
        "type": "string",
        "x-enumDescriptions": {
          "git": "A Git repository URL (`https://github.com/acme/api`).",
          "image": "A container image reference (`ghcr.io/acme/api:1.4.2`).",
          "path": "A local folder uploaded by `cve scan path` (CLI only)."
        }
      },
      "TemplateCategory": {
        "description": "What kind of file(s) a template produces.",
        "enum": [
          "terraform",
          "kubernetes",
          "helm",
          "dockerfile",
          "compose",
          "ci",
          "cve",
          "wrangler"
        ],
        "title": "TemplateCategory",
        "type": "string"
      },
      "TemplateParam": {
        "description": "One parameter of a template.",
        "properties": {
          "default": {
            "description": "The default (absent: required)."
          },
          "description": {
            "type": "string"
          },
          "max": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "max_items": {
            "description": "Most list items; default 32.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "max_len": {
            "description": "Longest string (or list item); default 128.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "min": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "description": "snake_case; what `--param name=value` and the render body use.",
            "type": "string"
          },
          "options": {
            "description": "The allowed values of an `enum`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "pattern": {
            "description": "Anchored regular expression every string (or list item) must match.",
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/ParamKind"
          }
        },
        "required": [
          "name",
          "title",
          "type"
        ],
        "title": "TemplateParam",
        "type": "object"
      },
      "TemplateProvider": {
        "description": "Where the generated infrastructure runs.",
        "enum": [
          "aws",
          "gcp",
          "cloudflare",
          "kubernetes",
          "docker",
          "github",
          "gitlab",
          "mycve"
        ],
        "title": "TemplateProvider",
        "type": "string"
      },
      "TemplatePullRequest": {
        "description": "The fix run that will open the pull request.",
        "properties": {
          "branch": {
            "type": [
              "string",
              "null"
            ]
          },
          "files": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "fix_run_id": {
            "format": "int64",
            "type": "integer"
          },
          "status": {
            "description": "`queued` (see `GET /api/fix-runs/{id}` for progress).",
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "template_id": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "fix_run_id",
          "template_id",
          "version",
          "target",
          "status",
          "files"
        ],
        "title": "TemplatePullRequest",
        "type": "object"
      },
      "TimePoint": {
        "description": "One point of a time series (x = a day or an instant, RFC 3339).",
        "properties": {
          "t": {
            "type": "string"
          },
          "v": {
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "t",
          "v"
        ],
        "title": "TimePoint",
        "type": "object"
      },
      "TokenKind": {
        "description": "What kind of bearer token a request carries.\n\n- `personal`: A user's personal access token: acts as that user, capped by the user's effective role in the workspace it is used in.\n- `service`: A workspace service token: not a user; bound to one workspace and acts with its `access` level (never as owner).",
        "enum": [
          "personal",
          "service"
        ],
        "title": "TokenKind",
        "type": "string",
        "x-enumDescriptions": {
          "personal": "A user's personal access token: acts as that user, capped by the user's effective role in the workspace it is used in.",
          "service": "A workspace service token: not a user; bound to one workspace and acts with its `access` level (never as owner)."
        }
      },
      "TreemapNode": {
        "description": "A treemap node: area = `value`; leaves may carry a severity colour.",
        "properties": {
          "children": {
            "items": {
              "$ref": "#/components/schemas/TreemapNode"
            },
            "type": "array"
          },
          "color": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SeriesColor"
              },
              {
                "type": "null"
              }
            ]
          },
          "href": {
            "type": [
              "string",
              "null"
            ]
          },
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "value": {
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "key",
          "label",
          "value",
          "children"
        ],
        "title": "TreemapNode",
        "type": "object"
      },
      "UpdateMember": {
        "description": "`PATCH /v1/members/{user_id}`.",
        "properties": {
          "role": {
            "$ref": "#/components/schemas/Role"
          }
        },
        "required": [
          "role"
        ],
        "title": "UpdateMember",
        "type": "object"
      },
      "UpdateWebhook": {
        "description": "`PATCH /v1/webhooks/{webhook_id}` (absent fields stay).",
        "properties": {
          "active": {
            "description": "true re-activates a disabled endpoint (and resets `failures`).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "events": {
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "url": {
            "format": "uri",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "UpdateWebhook",
        "type": "object"
      },
      "UpdateWorkspace": {
        "description": "`PATCH /v1/workspace` (owners).",
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "title": "UpdateWorkspace",
        "type": "object"
      },
      "UsageSummary": {
        "description": "`GET /v1/analytics/usage?from=&to=` (admins see `by_member`).",
        "properties": {
          "active_users": {
            "description": "Active members per day (distinct users with any event).",
            "items": {
              "format": "int64",
              "type": "integer"
            },
            "type": "array"
          },
          "areas": {
            "items": {
              "$ref": "#/components/schemas/AreaSummary"
            },
            "type": "array"
          },
          "by_member": {
            "additionalProperties": {
              "format": "int64",
              "type": "integer"
            },
            "description": "Admins and owners only: events per member (user id → count), never which pages or items.",
            "type": [
              "object",
              "null"
            ]
          },
          "from": {
            "format": "date",
            "type": "string"
          },
          "surfaces": {
            "additionalProperties": {
              "format": "int64",
              "type": "integer"
            },
            "type": "object"
          },
          "to": {
            "format": "date",
            "type": "string"
          }
        },
        "required": [
          "from",
          "to",
          "areas",
          "surfaces",
          "active_users"
        ],
        "title": "UsageSummary",
        "type": "object"
      },
      "V1Affected": {
        "properties": {
          "collection_url": {
            "format": "uri-reference",
            "type": [
              "string",
              "null"
            ]
          },
          "default_status": {
            "type": "string"
          },
          "package_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "product": {
            "type": [
              "string",
              "null"
            ]
          },
          "vendor": {
            "type": [
              "string",
              "null"
            ]
          },
          "versions": {
            "items": {
              "$ref": "#/components/schemas/V1AffectedVersion"
            },
            "type": "array"
          }
        },
        "required": [
          "default_status",
          "versions"
        ],
        "title": "V1Affected",
        "type": "object"
      },
      "V1AffectedVersion": {
        "properties": {
          "greater_than": {
            "type": [
              "string",
              "null"
            ]
          },
          "greater_than_or_equal": {
            "type": [
              "string",
              "null"
            ]
          },
          "less_than": {
            "type": [
              "string",
              "null"
            ]
          },
          "less_than_or_equal": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "version": {
            "type": [
              "string",
              "null"
            ]
          },
          "version_type": {
            "type": "string"
          }
        },
        "required": [
          "status",
          "version_type"
        ],
        "title": "V1AffectedVersion",
        "type": "object"
      },
      "V1Asset": {
        "description": "An asset of the workspace (docs/INFRASTRUCTURE.md).",
        "properties": {
          "at_risk": {
            "type": "boolean"
          },
          "connector_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "first_seen": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "gaps": {
            "description": "Coverage gap kinds (`never_scanned`, `stale`, ...).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "description": "`repo-3fa9c0d1e2b4`: a kind prefix and a hash of the target.",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/AssetKind"
          },
          "last_scan_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "last_seen": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "last_success": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "open": {
            "$ref": "#/components/schemas/SeverityCounts"
          },
          "packages": {
            "format": "int64",
            "type": "integer"
          },
          "private": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "provider": {
            "description": "`github`, `gitlab`, `git`, `dockerhub`, `ghcr`, `gcp`, `registry` or `local`.",
            "type": "string"
          },
          "scans": {
            "format": "int64",
            "type": "integer"
          },
          "stale": {
            "type": "boolean"
          },
          "target": {
            "description": "What a scan of it is started with.",
            "type": [
              "string",
              "null"
            ]
          },
          "watch_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "kind",
          "name",
          "provider",
          "scans",
          "open",
          "packages",
          "at_risk",
          "stale",
          "gaps"
        ],
        "title": "V1Asset",
        "type": "object"
      },
      "V1AssetDetail": {
        "description": "`GET /v1/assets/{asset_id}`: the asset, its recent scans and related assets (ids; fetch each for details).",
        "properties": {
          "asset": {
            "$ref": "#/components/schemas/V1Asset"
          },
          "related": {
            "description": "`[{asset_id, relation}]`, relation in `built_image`, `base_image`, `registry`, ...",
            "items": {
              "$ref": "#/components/schemas/V1RelatedAsset"
            },
            "type": "array"
          },
          "scan_ids": {
            "description": "Newest first, at most 30.",
            "items": {
              "format": "int64",
              "type": "integer"
            },
            "type": "array"
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "asset",
          "scan_ids",
          "related",
          "warnings"
        ],
        "title": "V1AssetDetail",
        "type": "object"
      },
      "V1Connector": {
        "description": "A connected provider account (never includes secrets).",
        "properties": {
          "account": {
            "type": "string"
          },
          "auth": {
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_via": {
            "type": "string"
          },
          "fixes_enabled": {
            "type": "boolean"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "last_sync": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "provider": {
            "description": "github | gitlab | dockerhub | gcp | gitguardian",
            "type": "string"
          },
          "scopes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "status": {
            "description": "connected | scanning | needs_attention | disconnected",
            "type": "string"
          }
        },
        "required": [
          "id",
          "provider",
          "account",
          "status",
          "auth",
          "created_via",
          "scopes",
          "fixes_enabled",
          "created_at"
        ],
        "title": "V1Connector",
        "type": "object"
      },
      "V1Cve": {
        "description": "A CVE as listed.",
        "properties": {
          "cve_id": {
            "type": "string"
          },
          "date_published": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "date_updated": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "in_stack": {
            "description": "Scan targets of this workspace with it open.",
            "format": "int64",
            "type": "integer"
          },
          "score": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "severity": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Severity"
              },
              {
                "type": "null"
              }
            ]
          },
          "state": {
            "description": "`PUBLISHED` or `REJECTED`.",
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "cve_id",
          "state",
          "in_stack"
        ],
        "title": "V1Cve",
        "type": "object"
      },
      "V1CveDetail": {
        "description": "`GET /v1/cves/{cve_id}`.",
        "properties": {
          "affected": {
            "items": {
              "$ref": "#/components/schemas/V1Affected"
            },
            "type": "array"
          },
          "assigner": {
            "type": [
              "string",
              "null"
            ]
          },
          "cve": {
            "$ref": "#/components/schemas/V1Cve"
          },
          "cwes": {
            "items": {
              "$ref": "#/components/schemas/V1Cwe"
            },
            "type": "array"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "metrics": {
            "items": {
              "$ref": "#/components/schemas/V1CveMetric"
            },
            "type": "array"
          },
          "references": {
            "items": {
              "$ref": "#/components/schemas/V1Reference"
            },
            "type": "array"
          }
        },
        "required": [
          "cve",
          "cwes",
          "metrics",
          "affected",
          "references"
        ],
        "title": "V1CveDetail",
        "type": "object"
      },
      "V1CveMetric": {
        "properties": {
          "provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "score": {
            "format": "double",
            "type": "number"
          },
          "severity": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Severity"
              },
              {
                "type": "null"
              }
            ]
          },
          "vector": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "version",
          "score",
          "vector"
        ],
        "title": "V1CveMetric",
        "type": "object"
      },
      "V1Cwe": {
        "properties": {
          "description": {
            "type": "string"
          },
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "description"
        ],
        "title": "V1Cwe",
        "type": "object"
      },
      "V1Finding": {
        "description": "One finding of a scan: a CVE in a package version.",
        "properties": {
          "confidence": {
            "description": "`high` (package name) or `medium` (product name).",
            "type": "string"
          },
          "cve_id": {
            "type": "string"
          },
          "ecosystem": {
            "$ref": "#/components/schemas/Ecosystem"
          },
          "package": {
            "type": "string"
          },
          "score": {
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "severity": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Severity"
              },
              {
                "type": "null"
              }
            ]
          },
          "source": {
            "description": "The manifest or layer it came from.",
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "cve_id",
          "package",
          "version",
          "ecosystem",
          "source",
          "confidence"
        ],
        "title": "V1Finding",
        "type": "object"
      },
      "V1FixRun": {
        "description": "An automatic fix run (dependency upgrades opened as a PR).",
        "properties": {
          "ai": {
            "type": "boolean"
          },
          "branch": {
            "type": [
              "string",
              "null"
            ]
          },
          "changes": {
            "description": "Planned or applied changes (shape: docs/API.md \"Fix runs\")."
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "finished_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "kind": {
            "$ref": "#/components/schemas/TargetKind"
          },
          "mode": {
            "description": "pr | branch | dry_run",
            "type": "string"
          },
          "pr_url": {
            "format": "uri-reference",
            "type": [
              "string",
              "null"
            ]
          },
          "scan_id": {
            "format": "int64",
            "type": "integer"
          },
          "started_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "queued | running | done | failed | no_changes",
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "trigger": {
            "description": "`manual` or `auto`.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "scan_id",
          "status",
          "mode",
          "kind",
          "target",
          "trigger",
          "ai",
          "changes",
          "created_at"
        ],
        "title": "V1FixRun",
        "type": "object"
      },
      "V1Invite": {
        "description": "An invitation into the workspace.",
        "properties": {
          "accepted_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "declined_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "invited_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "email",
          "role",
          "created_at"
        ],
        "title": "V1Invite",
        "type": "object"
      },
      "V1Job": {
        "description": "A unit of cloud work (docs/JOBS.md).",
        "properties": {
          "attempts": {
            "format": "int64",
            "type": "integer"
          },
          "cancel_requested": {
            "type": "boolean"
          },
          "enqueued_at": {
            "format": "date-time",
            "type": "string"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "finished_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "kind": {
            "description": "scan | connector_sync | cve_sync | fix | pipeline_job | report_build | analytics_rollup",
            "type": "string"
          },
          "max_attempts": {
            "format": "int64",
            "type": "integer"
          },
          "priority": {
            "format": "int64",
            "type": "integer"
          },
          "result": {},
          "started_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "queued | leased | running | done | failed | canceled | dead",
            "type": "string"
          },
          "subject": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "kind",
          "priority",
          "status",
          "attempts",
          "max_attempts",
          "cancel_requested",
          "enqueued_at"
        ],
        "title": "V1Job",
        "type": "object"
      },
      "V1Member": {
        "description": "A member of the workspace.",
        "properties": {
          "effective_role": {
            "$ref": "#/components/schemas/Role",
            "description": "`role` raised by the member's teams."
          },
          "identity": {
            "type": "string"
          },
          "joined_at": {
            "format": "date-time",
            "type": "string"
          },
          "kind": {
            "description": "`person` or `service`.",
            "type": "string"
          },
          "last_seen": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          },
          "teams": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "user_id": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "user_id",
          "identity",
          "kind",
          "role",
          "effective_role",
          "teams",
          "joined_at"
        ],
        "title": "V1Member",
        "type": "object"
      },
      "V1Notification": {
        "description": "One in-app notification of the caller (`GET /v1/notifications`). Service tokens have none (notifications belong to people).",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "email_status": {
            "description": "Email delivery: queued | sent | failed | suppressed | none.",
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "kind": {
            "description": "`scan.finished`, `scan.failed`, `cve.new`, `fix.finished`, `connector.problem`, `workspace.invite`, `workspace.membership`, `report.ready`, `digest.weekly`, `pipeline.failed`, `pipeline.succeeded`, `secret.found`, `job.dead_lettered`, `test`.",
            "type": "string"
          },
          "link": {
            "description": "The web page it is about (`/app/...` on mycve.io), when there is one.",
            "type": [
              "string",
              "null"
            ]
          },
          "read_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "workspace_id": {
            "description": "`null`: account-level (an invitation).",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "kind",
          "subject",
          "summary",
          "created_at",
          "email_status"
        ],
        "title": "V1Notification",
        "type": "object"
      },
      "V1OpenFinding": {
        "description": "An open finding of the workspace, with where it was found.",
        "properties": {
          "finding": {
            "$ref": "#/components/schemas/V1Finding"
          },
          "kind": {
            "$ref": "#/components/schemas/TargetKind"
          },
          "scan_id": {
            "format": "int64",
            "type": "integer"
          },
          "scanned_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "finding",
          "scan_id",
          "kind",
          "target"
        ],
        "title": "V1OpenFinding",
        "type": "object"
      },
      "V1Package": {
        "description": "A package of a scan's inventory.",
        "properties": {
          "ecosystem": {
            "$ref": "#/components/schemas/Ecosystem"
          },
          "name": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "version",
          "ecosystem",
          "source"
        ],
        "title": "V1Package",
        "type": "object"
      },
      "V1PipelineJob": {
        "description": "One job of a pipeline run.",
        "properties": {
          "allow_failure": {
            "type": "boolean"
          },
          "attempt": {
            "format": "int64",
            "type": "integer"
          },
          "duration_ms": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "exit_code": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "findings_summary": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SeverityCounts"
              },
              {
                "type": "null"
              }
            ]
          },
          "finished_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "needs": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "stage": {
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "created | queued | running | success | failed | canceled | skipped | manual",
            "type": "string"
          },
          "uses": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "stage",
          "status",
          "allow_failure",
          "needs",
          "attempt"
        ],
        "title": "V1PipelineJob",
        "type": "object"
      },
      "V1PipelineRun": {
        "description": "A pipeline run (a repository's `cve.yml`, docs/PIPELINES.md).",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "file": {
            "type": "string"
          },
          "finished_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "jobs": {
            "items": {
              "$ref": "#/components/schemas/V1PipelineJob"
            },
            "type": "array"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "ref": {
            "type": "string"
          },
          "sha": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "description": "push | api | schedule | ci | web",
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "queued | running | success | failed | canceled | skipped",
            "type": "string"
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "source",
          "target",
          "ref",
          "file",
          "status",
          "created_at",
          "jobs"
        ],
        "title": "V1PipelineRun",
        "type": "object"
      },
      "V1Reference": {
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "title": "V1Reference",
        "type": "object"
      },
      "V1RelatedAsset": {
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "relation": {
            "type": "string"
          }
        },
        "required": [
          "asset_id",
          "relation"
        ],
        "title": "V1RelatedAsset",
        "type": "object"
      },
      "V1Scan": {
        "description": "A scan of a repository, image or uploaded folder.",
        "properties": {
          "counts": {
            "$ref": "#/components/schemas/SeverityCounts",
            "description": "Findings per severity."
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "finding_count": {
            "format": "int64",
            "type": "integer"
          },
          "findings_url": {
            "description": "`/scans/{id}/findings`.",
            "format": "uri-reference",
            "type": "string"
          },
          "finished_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "kind": {
            "$ref": "#/components/schemas/TargetKind"
          },
          "package_count": {
            "format": "int64",
            "type": "integer"
          },
          "profile": {
            "description": "quick | standard | deep (docs/SCANS.md \"Profiles\").",
            "type": [
              "string",
              "null"
            ]
          },
          "started_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "queued | running | done | failed",
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "tasks_url": {
            "description": "`/scans/{id}/tasks`: the scan's tasks once scans are distributed.",
            "format": "uri-reference",
            "type": "string"
          },
          "watch_id": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "kind",
          "target",
          "status",
          "created_at",
          "package_count",
          "finding_count",
          "counts",
          "findings_url",
          "tasks_url"
        ],
        "title": "V1Scan",
        "type": "object"
      },
      "V1ScanTask": {
        "description": "One task of a distributed scan (docs/SCANS.md): a shard's analysis, a match, the merge, ...",
        "properties": {
          "attempts": {
            "format": "int64",
            "type": "integer"
          },
          "deps": {
            "description": "Keys of the tasks it waits for.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "duration_ms": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "finished_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "inputs": {
            "description": "Input files (or layers, commits) it reads.",
            "format": "int64",
            "type": "integer"
          },
          "job_id": {
            "description": "Its job in the job system (`/v1/jobs/{job_id}`), once queued.",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "key": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "shard": {
            "type": [
              "string",
              "null"
            ]
          },
          "stage": {
            "description": "plan | snapshot | analyze | assemble | match | merge",
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "pending | ready | queued | running | done | cached | failed | canceled | skipped",
            "type": "string"
          }
        },
        "required": [
          "key",
          "stage",
          "kind",
          "deps",
          "status",
          "inputs",
          "attempts"
        ],
        "title": "V1ScanTask",
        "type": "object"
      },
      "V1SecretIncident": {
        "description": "A GitGuardian incident mapped to the workspace (never a secret value).",
        "properties": {
          "connector_id": {
            "format": "int64",
            "type": "integer"
          },
          "detector": {
            "type": "string"
          },
          "first_seen": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "gitguardian_url": {
            "format": "uri-reference",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "last_seen": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "occurrences": {
            "format": "int64",
            "type": "integer"
          },
          "severity": {
            "type": "string"
          },
          "source": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "triggered | assigned | resolved | ignored",
            "type": "string"
          },
          "target": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "validity": {
            "description": "valid | invalid | unknown",
            "type": "string"
          }
        },
        "required": [
          "id",
          "connector_id",
          "status",
          "severity",
          "validity",
          "detector",
          "occurrences"
        ],
        "title": "V1SecretIncident",
        "type": "object"
      },
      "V1SecretsSummary": {
        "description": "`GET /v1/secret-incidents/summary`.",
        "properties": {
          "connected": {
            "type": "boolean"
          },
          "findings": {
            "format": "int64",
            "type": "integer"
          },
          "ignored": {
            "format": "int64",
            "type": "integer"
          },
          "last_sync": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "open": {
            "format": "int64",
            "type": "integer"
          },
          "resolved": {
            "format": "int64",
            "type": "integer"
          },
          "valid": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "connected",
          "open",
          "valid",
          "resolved",
          "ignored",
          "findings"
        ],
        "title": "V1SecretsSummary",
        "type": "object"
      },
      "V1Team": {
        "description": "A team of the workspace.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "members": {
            "description": "Identities of its members.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Role"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "members",
          "created_at"
        ],
        "title": "V1Team",
        "type": "object"
      },
      "V1Webhook": {
        "description": "An outgoing webhook endpoint of the workspace.",
        "properties": {
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "disabled_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "events": {
            "description": "Subscribed events (`apiv1::WebhookEvent`); empty = all.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "failures": {
            "description": "Consecutive failed deliveries (deactivated at 50).",
            "format": "int64",
            "type": "integer"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "secret_prefix": {
            "description": "`whsec_3fa9c0`: the start of the signing secret.",
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "active",
          "secret_prefix",
          "failures",
          "created_at",
          "updated_at"
        ],
        "title": "V1Webhook",
        "type": "object"
      },
      "V1WebhookDeliveryLog": {
        "description": "One delivery attempt record (`GET /v1/webhooks/{id}/deliveries`).",
        "properties": {
          "attempts": {
            "format": "int64",
            "type": "integer"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "delivery_id": {
            "description": "`whd_…` (the `Mycve-Delivery` header).",
            "type": "string"
          },
          "duration_ms": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "event": {
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "last_attempt_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "next_attempt_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "response_excerpt": {
            "type": [
              "string",
              "null"
            ]
          },
          "response_status": {
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/DeliveryStatus"
          }
        },
        "required": [
          "id",
          "delivery_id",
          "event",
          "status",
          "attempts",
          "created_at"
        ],
        "title": "V1WebhookDeliveryLog",
        "type": "object"
      },
      "V1Workspace": {
        "description": "A workspace the caller belongs to.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "members": {
            "format": "int64",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "personal": {
            "description": "The caller's own workspace (cannot be deleted or left).",
            "type": "boolean"
          },
          "role": {
            "description": "The caller's effective role here (null for a service token).",
            "oneOf": [
              {
                "$ref": "#/components/schemas/Role"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "personal",
          "members",
          "created_at"
        ],
        "title": "V1Workspace",
        "type": "object"
      },
      "WebhookDelivery": {
        "description": "The body of a webhook delivery.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "data": {
            "description": "The event's resource, in its v1 shape."
          },
          "event": {
            "$ref": "#/components/schemas/WebhookEvent"
          },
          "id": {
            "description": "`whd_…`, also `Mycve-Delivery`; receivers dedupe on it.",
            "type": "string"
          },
          "workspace_id": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "event",
          "workspace_id",
          "created_at",
          "data"
        ],
        "title": "WebhookDelivery",
        "type": "object"
      },
      "WebhookEvent": {
        "description": "Events an outgoing webhook can subscribe to (the `Mycve-Event` header and `event` of the body).",
        "enum": [
          "scan.completed",
          "scan.failed",
          "finding.new",
          "finding.resolved",
          "cve.affects_project",
          "report.ready",
          "pipeline_run.finished",
          "fix_run.finished",
          "secret.incident",
          "connector.attention",
          "ping"
        ],
        "title": "WebhookEvent",
        "type": "string"
      },
      "WebhookWithSecret": {
        "description": "`201` of `POST /v1/webhooks` and `200` of `.../rotate-secret`: the only time the signing secret is shown.",
        "properties": {
          "secret": {
            "type": "string"
          },
          "webhook": {
            "$ref": "#/components/schemas/V1Webhook"
          }
        },
        "required": [
          "secret",
          "webhook"
        ],
        "title": "WebhookWithSecret",
        "type": "object"
      }
    },
    "securitySchemes": {
      "bearer": {
        "bearerFormat": "mycve_pat_<40 hex> | mycve_svc_<40 hex>",
        "description": "A personal access token (`mycve_pat_…`) or a workspace service token (`mycve_svc_…`) in `Authorization: Bearer <token>`. The values listed in an operation's security requirement are the token access it needs: `read`, `write` or `admin` (ordered; a higher one includes the lower ones). On `https://mycve.io/api/v1` the browser's Cloudflare Access session works instead of a token. CI tokens (`mycve_ci_…`) are not accepted here.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "externalDocs": {
    "description": "Guides (authentication, pagination, errors, webhooks, ...)",
    "url": "https://docs.mycve.io"
  },
  "info": {
    "contact": {
      "name": "mycve",
      "url": "https://docs.mycve.io"
    },
    "description": "The mycve.io API: scan repositories and container images, follow findings and CVEs across your workspace, group assets into projects, run `cve.yml` pipelines and automatic fix pull requests, render secure infrastructure templates, and build reports. Everything the website shows is available here, with the same permissions.\n\n## Getting started\n\n1. **Create a token.** In the web app (Settings, API) or with the CLI: `cve tokens create --name laptop --scope read`. The token (`mycve_pat_…`) is shown once; store it in a secret manager.\n2. **Call the API** with `Authorization: Bearer <token>`:\n\n   ```sh\n   export MYCVE_TOKEN=mycve_pat_...\n   curl https://api.mycve.io/v1/me -H \"Authorization: Bearer $MYCVE_TOKEN\"\n   ```\n\n   `GET /me` (`getCaller`) tells you who you are, which workspace the request acts in and with what access.\n3. **Pick a workspace** with `X-Mycve-Workspace: <id>` (ids from `listWorkspaces`); without it a personal token acts in your personal workspace.\n4. **Read and write**: `listFindings`, `createScan`, `createPipelineRun`, `createReport`, ... A `write` token is needed for changes.\n\nThe machine-readable document is `GET /openapi.json` (public). Long-form guides: https://docs.mycve.io.\n\n## Servers\n\n| Server | Use it for |\n|---|---|\n| `https://api.mycve.io/v1` | Scripts, CI and services. Bearer tokens only. |\n| `https://mycve.io/api/v1` | The web app: the signed-in browser session (Cloudflare Access) works, tokens too. |\n\n## Authentication and tokens\n\n| Token | Prefix | Acts as | Workspace |\n|---|---|---|---|\n| Personal access token | `mycve_pat_` | You, capped by your current role in the workspace and by the token's scopes | Any workspace you are in (`X-Mycve-Workspace`), or the one it is pinned to |\n| Service token (admins create them) | `mycve_svc_` | The workspace, at its own access level (never owner) | Exactly its workspace |\n\nScopes are one access level, `read` (GET), `write` (create and change scans, projects, runs, fixes, reports) or `admin` (members, tokens, webhooks, settings), optionally narrowed with `group:<resource group>` (for example `group:scans`) and `project:<id>` items. Every operation lists the role, access and scope it needs under **Permissions**. Tokens expire after 90 days by default (at most 366). Revoke one with `revokeToken`.\n\nErrors: no token, or an unknown, expired or revoked one is `401 unauthenticated`; too little access, role or scope is `403 forbidden`.\n\n## Workspaces and roles\n\nEverything is workspace-scoped except `/me`, `/openapi.json`, `/health` and your own tokens. Roles are ordered `viewer < member < admin < owner`: viewers read, members also start scans, fixes, runs and manage projects and reports, admins also manage members, tokens and webhooks, owners also rename the workspace. A workspace you are not a member of answers `404`, never `403`, so ids do not leak.\n\n## Errors\n\nEvery error has the same body, whatever the status:\n\n```json\n{\"error\": {\"code\": \"validation_failed\", \"message\": \"invalid request (name: must not be empty)\",\n           \"details\": {\"fields\": [{\"field\": \"name\", \"issue\": \"must not be empty\"}]},\n           \"request_id\": \"req_00010203aabbccff\"}}\n```\n\nSwitch on `code` (see the `ErrorCode` schema), never on `message`. Quote `request_id` (also the `X-Request-Id` header) when you contact support. Retry `429`, `500` and `503` with backoff; do not retry other 4xx unchanged.\n\n## Pagination\n\nLists return `{\"data\": [...], \"next_cursor\": \"...\"}`. Pass `next_cursor` back as `?cursor=` until it is `null`. `limit` is 1 to 200 (default 50). Cursors are opaque and belong to the route that made them. There are no total counts; use the summary and analytics endpoints for numbers.\n\n## Idempotency\n\nEvery `POST` that creates something accepts `Idempotency-Key` (1 to 255 visible ASCII characters, a UUID is best). Retrying with the same key and body within 24 hours returns the stored response with `Idempotent-Replayed: true` instead of creating a second resource. The same key with a different body is `422 idempotency_key_reused`; a retry while the first request still runs is `409 conflict`.\n\n## Rate limits\n\nFixed 60-second windows per token (or user) and workspace, in three buckets: `read` 1200 requests (GETs), `write` 120 (other methods), `heavy` 10 (report builds, project-suggestion refreshes). Every authenticated response carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (seconds) and `RateLimit-Policy` (`120;w=60`). Over the limit: `429 rate_limited` with `Retry-After` (seconds) and `details.retry_after`.\n\n## Caching and concurrency (ETags)\n\nJSON `GET`s send a weak `ETag`. Send it back as `If-None-Match` and an unchanged resource answers `304 Not Modified` with no body (it still counts against the rate limit, but costs no bandwidth). `PATCH`, `PUT` and `DELETE` accept `If-Match` with the `ETag` of the resource's `GET`: if someone changed it since, `412 precondition_failed`.\n\n## Versioning and deprecation\n\n`/v1` only changes additively: new endpoints, new optional request fields, new response fields and new enum values. Ignore fields you do not know and tolerate new enum values. Anything breaking waits for `/v2`, which will run next to `/v1` for at least 12 months. An endpoint that is going away answers with `Deprecation` (RFC 9745), `Sunset` (RFC 8594) and `Link: <...>; rel=\"deprecation\"` headers at least 180 days before it is removed; after the sunset it answers `410 gone`. Deprecated operations are marked `deprecated: true` in this document. Deprecated now: the `/products*` and `/product-suggestions*` routes (aliases of `/projects*` and `/project-suggestions*`), sunset 2027-04-05.\n\n## Webhooks\n\nAdmins register HTTPS endpoints (`createWebhook`) for events such as `scan.completed`. Each delivery is a signed JSON `POST`; verify `Mycve-Signature` (`t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>`) before trusting it, reject timestamps more than 5 minutes off, and dedupe on `Mycve-Delivery`. Payloads, headers, retries and verification code are under **Webhook events**.\n",
    "license": {
      "name": "Proprietary",
      "url": "https://docs.mycve.io"
    },
    "summary": "Scan repositories and images, track CVEs and findings, run pipelines and fixes, and report on your projects.",
    "title": "mycve API",
    "version": "1.0.0",
    "x-mycve-docs": "https://docs.mycve.io"
  },
  "jsonSchemaDialect": "https://spec.openapis.org/oas/3.1/dialect/base",
  "openapi": "3.1.0",
  "paths": {
    "/analytics/flow": {
      "get": {
        "description": "The asset → package → CVE → severity flow of open findings as Sankey nodes and links, cut to the `top` nodes per column (the rest are merged into `+N more`). Shows which packages carry most of the risk.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:analytics`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getDependencyFlow",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "Limit to one project",
            "example": 12,
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Nodes per column before +N more (default 12)",
            "example": 12,
            "in": "query",
            "name": "top",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "columns": [
                        "column"
                      ],
                      "links": [
                        {
                          "color": "slot-1",
                          "source": "manual",
                          "target": "https://github.com/acme/payments-api",
                          "value": 1.5
                        }
                      ],
                      "nodes": [
                        {
                          "color": "slot-1",
                          "column": 3,
                          "id": "id",
                          "label": "payments-api"
                        }
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/SankeyData"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Asset → package → CVE → severity flow",
        "tags": [
          "Analytics"
        ],
        "x-apidog-folder": "Reports & analytics/Analytics",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/analytics/flow\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "analytics",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/analytics/insights": {
      "get": {
        "description": "The insights report: security and usage KPIs, feature-area adoption and (admins) per-member activity, as JSON or as a file (`format=md|html|csv`). Admins only, because it includes per-member usage.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:analytics`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getInsightsReport",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "First day (UTC)",
            "example": "2026-09-01",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Last day (UTC)",
            "example": "2026-09-30",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "7d | 30d | 90d",
            "example": "30d",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "type": "string"
            }
          },
          {
            "description": "json (default) | md | html | csv",
            "example": "json",
            "in": "query",
            "name": "format",
            "required": false,
            "schema": {
              "default": "json",
              "enum": [
                "json",
                "md",
                "html",
                "csv"
              ],
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "JSON",
                    "value": {
                      "active_users": [
                        3
                      ],
                      "areas": [
                        {
                          "area": "overview",
                          "daily": [
                            3
                          ],
                          "events": 3,
                          "users": 3
                        }
                      ],
                      "from": "2026-10-01",
                      "generated_at": "2026-10-01T09:30:00Z",
                      "members": [
                        {
                          "active_days": 90,
                          "events": 3,
                          "identity": "ana@acme.dev",
                          "user_id": 42
                        }
                      ],
                      "security": [
                        {
                          "delta": 1.5,
                          "key": "payments",
                          "label": "payments-api",
                          "tone": "good",
                          "trend": [
                            1.5
                          ],
                          "unit": "findings",
                          "up_is_good": false,
                          "value": 1.5
                        }
                      ],
                      "surfaces": {
                        "payments": 3
                      },
                      "to": "2026-10-01",
                      "usage": [
                        {
                          "delta": 1.5,
                          "key": "payments",
                          "label": "payments-api",
                          "tone": "good",
                          "trend": [
                            1.5
                          ],
                          "unit": "findings",
                          "up_is_good": false,
                          "value": 1.5
                        }
                      ],
                      "workspace_id": 7,
                      "workspace_name": "workspace name"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/InsightsReport"
                }
              },
              "text/csv": {
                "examples": {
                  "default": {
                    "summary": "text/csv",
                    "value": "kpi,value,previous\nopen_critical,2,5\nopen_high,5,7\n"
                  }
                },
                "schema": {
                  "type": "string"
                }
              },
              "text/html": {
                "examples": {
                  "default": {
                    "summary": "text/html",
                    "value": "<!doctype html><html><head><title>Insights: acme</title></head><body>...</body></html>"
                  }
                },
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "examples": {
                  "default": {
                    "summary": "text/markdown",
                    "value": "# Insights: acme, 2026-09-01 to 2026-09-30\n\n| KPI | Value | Change |\n|---|---|---|\n| Open critical | 2 | -3 |\n"
                  }
                },
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Content-Disposition": {
                "$ref": "#/components/headers/ContentDisposition"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "The insights report: security and usage KPIs, feature areas, members (admins)",
        "tags": [
          "Analytics"
        ],
        "x-apidog-folder": "Reports & analytics/Analytics",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/analytics/insights\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "analytics",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/analytics/kpis": {
      "get": {
        "description": "The KPI tiles of a range compared with the previous range of the same length: open critical and high, mean time to remediate, coverage, new and resolved findings; admins also get usage KPIs (active members, API calls).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:analytics`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getAnalyticsKpis",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "First day (UTC), default 30 days before `to`",
            "example": "2026-09-01",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Last day (UTC), default today",
            "example": "2026-09-30",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "7d | 30d | 90d (instead of from)",
            "example": "30d",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "type": "string"
            }
          },
          {
            "description": "Limit to one project",
            "example": 12,
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "from": "2026-10-01",
                      "generated_at": "2026-10-01T09:30:00Z",
                      "project_id": 12,
                      "rolled_up_at": "2026-10-01T09:30:00Z",
                      "security": [
                        {
                          "delta": 1.5,
                          "key": "payments",
                          "label": "payments-api",
                          "tone": "good",
                          "trend": [
                            1.5
                          ],
                          "unit": "findings",
                          "up_is_good": false,
                          "value": 1.5
                        }
                      ],
                      "to": "2026-10-01",
                      "usage": [
                        {
                          "delta": 1.5,
                          "key": "payments",
                          "label": "payments-api",
                          "tone": "good",
                          "trend": [
                            1.5
                          ],
                          "unit": "findings",
                          "up_is_good": false,
                          "value": 1.5
                        }
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsKpis"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Security KPIs (and, for admins, usage KPIs) of a range",
        "tags": [
          "Analytics"
        ],
        "x-apidog-folder": "Reports & analytics/Analytics",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/analytics/kpis\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "analytics",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/analytics/security": {
      "get": {
        "description": "Daily security rollups of the workspace (or one project): open findings by severity, new and resolved findings, scans and coverage per day. Feeds trend charts; rolled up nightly, so today is partial.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:analytics`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getSecuritySeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "First day (UTC), default 30 days before `to`",
            "example": "2026-09-01",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Last day (UTC), default today",
            "example": "2026-09-30",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "7d | 30d | 90d (instead of from)",
            "example": "30d",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "type": "string"
            }
          },
          {
            "description": "Limit to one project",
            "example": 12,
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "days": [
                        {
                          "assets": 3,
                          "covered": 3,
                          "day": "2026-10-01",
                          "fix_prs": 3,
                          "mttr_hours": 30.5,
                          "new_findings": 3,
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "pipeline_failures": 3,
                          "project_id": 12,
                          "resolved": 3,
                          "scannable": 3,
                          "scans_done": 3,
                          "scans_failed": 3,
                          "secrets_open": 3
                        }
                      ],
                      "from": "2026-10-01",
                      "project_id": 12,
                      "to": "2026-10-01"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/SecuritySeries"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Daily security rollups (workspace or project)",
        "tags": [
          "Analytics"
        ],
        "x-apidog-folder": "Reports & analytics/Analytics",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/analytics/security\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/analytics/security\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/analytics/security\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/analytics/security\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/analytics/security\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "analytics",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/analytics/usage": {
      "get": {
        "description": "Usage per feature area (scans, findings, projects, pipelines, ...) over a range: events and active users. Admins also get the per-member breakdown; others see totals only.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:analytics`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getUsageSummary",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "First day (UTC), default 30 days before `to`",
            "example": "2026-09-01",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Last day (UTC), default today",
            "example": "2026-09-30",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "7d | 30d | 90d (instead of from)",
            "example": "30d",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "type": "string"
            }
          },
          {
            "description": "Limit to one project",
            "example": 12,
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "active_users": [
                        3
                      ],
                      "areas": [
                        {
                          "area": "overview",
                          "daily": [
                            3
                          ],
                          "events": 3,
                          "users": 3
                        }
                      ],
                      "by_member": {
                        "payments": 3
                      },
                      "from": "2026-10-01",
                      "surfaces": {
                        "payments": 3
                      },
                      "to": "2026-10-01"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/UsageSummary"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Usage per feature area (admins also see per member)",
        "tags": [
          "Analytics"
        ],
        "x-apidog-folder": "Reports & analytics/Analytics",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/analytics/usage\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "analytics",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/assets": {
      "get": {
        "description": "The infrastructure inventory: repositories, images, base images, registries, cloud projects and uploaded folders, riskiest first, with open findings, last scan and coverage gaps. Filter to what is at risk, stale (no successful scan in 7 days) or has coverage gaps.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:assets`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listAssets",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "repository | image | base_image | registry | cloud_project | path",
            "example": "image",
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "enum": [
                "repository",
                "image",
                "base_image",
                "registry",
                "cloud_project",
                "path"
              ],
              "type": "string"
            }
          },
          {
            "description": "github | gitlab | dockerhub | gcp | ...",
            "example": "github",
            "in": "query",
            "name": "provider",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Critical or high open",
            "example": true,
            "in": "query",
            "name": "at_risk",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Not scanned successfully in 7 days",
            "example": true,
            "in": "query",
            "name": "stale",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Has a coverage gap",
            "example": true,
            "in": "query",
            "name": "gaps",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Name or target contains",
            "example": "payments",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "risk (default) | name | last_scan | findings | kind",
            "example": "risk",
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "risk",
              "enum": [
                "risk",
                "name",
                "last_scan",
                "findings",
                "kind"
              ],
              "type": "string"
            }
          },
          {
            "description": "Only assets of this connector (`id` of `listConnectors`).",
            "example": 3,
            "in": "query",
            "name": "connector",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "at_risk": false,
                          "connector_id": 3,
                          "first_seen": "2026-10-01T09:30:00Z",
                          "gaps": [
                            "gap"
                          ],
                          "id": "id",
                          "kind": "repository",
                          "last_scan_id": 4812,
                          "last_seen": "2026-10-01T09:30:00Z",
                          "last_success": "2026-10-01T09:30:00Z",
                          "name": "Payments",
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "packages": 3,
                          "private": false,
                          "provider": "github",
                          "scans": 3,
                          "stale": false,
                          "target": "https://github.com/acme/payments-api",
                          "watch_id": 3
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Asset"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Repositories, images, registries, cloud projects and folders of the workspace",
        "tags": [
          "Assets"
        ],
        "x-apidog-folder": "Assets & infrastructure",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/assets?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/assets?limit=50\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/assets?limit=50\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/assets?limit=50\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/assets?limit=50\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "assets",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/assets/{asset_id}": {
      "get": {
        "description": "One asset with its scan history and related assets (the images built from a repository, an image's base image and registry, ...).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:assets`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getAsset",
        "parameters": [
          {
            "description": "Asset id: a kind prefix (`repo`, `img`, `base`, `reg`, `gcp`, `path`), a dash and 12 hex (`id` of `listAssets`).",
            "example": "repo-3fa9c0d1e2b4",
            "in": "path",
            "name": "asset_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset": {
                        "at_risk": false,
                        "connector_id": 3,
                        "first_seen": "2026-10-01T09:30:00Z",
                        "gaps": [
                          "gap"
                        ],
                        "id": "id",
                        "kind": "repository",
                        "last_scan_id": 4812,
                        "last_seen": "2026-10-01T09:30:00Z",
                        "last_success": "2026-10-01T09:30:00Z",
                        "name": "Payments",
                        "open": {
                          "critical": 2,
                          "high": 5,
                          "low": 4,
                          "medium": 11,
                          "none": 0
                        },
                        "packages": 3,
                        "private": false,
                        "provider": "github",
                        "scans": 3,
                        "stale": false,
                        "target": "https://github.com/acme/payments-api",
                        "watch_id": 3
                      },
                      "related": [
                        {
                          "asset_id": "repo-3fa9c0d1e2b4",
                          "relation": "relation"
                        }
                      ],
                      "scan_ids": [
                        4812
                      ],
                      "warnings": [
                        "warning"
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1AssetDetail"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "One asset with its scan history and related assets",
        "tags": [
          "Assets"
        ],
        "x-apidog-folder": "Assets & infrastructure",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/assets/repo-3fa9c0d1e2b4\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "assets",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/connectors": {
      "get": {
        "description": "Connected provider accounts (GitHub, GitLab, registries, cloud projects) with their status and whether they may push fixes. Credentials are never returned; connect and disconnect on the website or with `cve connect`.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:connectors`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listConnectors",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "account": "acme",
                          "auth": "auth",
                          "created_at": "2026-10-01T09:30:00Z",
                          "created_via": "created via",
                          "fixes_enabled": false,
                          "id": 3,
                          "last_sync": "2026-10-01T09:30:00Z",
                          "note": "note",
                          "provider": "github",
                          "scopes": [
                            "write",
                            "group:scans"
                          ],
                          "status": "done"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Connector"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Connected provider accounts (never secrets)",
        "tags": [
          "Connectors"
        ],
        "x-apidog-folder": "Integrations",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/connectors?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "connectors",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/connectors/{connector_id}": {
      "get": {
        "description": "One connector: provider, account, scopes, last sync and whether it needs attention.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:connectors`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getConnector",
        "parameters": [
          {
            "description": "Connector id (`id` of `listConnectors`).",
            "example": 3,
            "in": "path",
            "name": "connector_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "account": "acme",
                      "auth": "auth",
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_via": "created via",
                      "fixes_enabled": false,
                      "id": 3,
                      "last_sync": "2026-10-01T09:30:00Z",
                      "note": "note",
                      "provider": "github",
                      "scopes": [
                        "write",
                        "group:scans"
                      ],
                      "status": "done"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Connector"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "One connector",
        "tags": [
          "Connectors"
        ],
        "x-apidog-folder": "Integrations",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/connectors/3\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "connectors",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/cves": {
      "get": {
        "description": "Search the CVE database by id prefix, package, product or words, optionally only CVEs open in this workspace (`in_stack=true`) and at or above a severity. The database is shared by all workspaces; `in_stack` is per workspace.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:cves`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listCves",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "CVE id prefix, package, product or words",
            "example": "xz",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "CRITICAL | HIGH | MEDIUM | LOW | NONE (and above for lists of CVEs)",
            "example": "HIGH",
            "in": "query",
            "name": "severity",
            "required": false,
            "schema": {
              "enum": [
                "CRITICAL",
                "HIGH",
                "MEDIUM",
                "LOW",
                "NONE"
              ],
              "type": "string"
            }
          },
          {
            "description": "Only CVEs open in this workspace",
            "example": true,
            "in": "query",
            "name": "in_stack",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "cve_id": "CVE-2024-3094",
                          "date_published": "2026-10-01T09:30:00Z",
                          "date_updated": "2026-10-01T09:30:00Z",
                          "in_stack": 3,
                          "score": 9.8,
                          "severity": "HIGH",
                          "state": "done",
                          "title": "Payments API"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Cve"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Search the CVE database",
        "tags": [
          "CVEs"
        ],
        "x-apidog-folder": "CVEs",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/cves?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/cves?limit=50\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/cves?limit=50\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/cves?limit=50\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/cves?limit=50\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "cves",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/cves/{cve_id}": {
      "get": {
        "description": "One CVE: description, CVSS metrics, CWEs, affected package versions, references, and whether it is open in this workspace.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:cves`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getCve",
        "parameters": [
          {
            "description": "A CVE id, case-insensitive (`CVE-2024-3094`). GHSA and other aliases resolve when the database knows them.",
            "example": "CVE-2024-3094",
            "in": "path",
            "name": "cve_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "affected": [
                        {
                          "collection_url": "https://mycve.io/",
                          "default_status": "default status",
                          "package_name": "openssl",
                          "product": "product",
                          "vendor": "vendor",
                          "versions": [
                            {
                              "greater_than": "greater than",
                              "greater_than_or_equal": "greater than or equal",
                              "less_than": "less than",
                              "less_than_or_equal": "less than or equal",
                              "status": "done",
                              "version": "3.0.13",
                              "version_type": "version type"
                            }
                          ]
                        }
                      ],
                      "assigner": "assigner",
                      "cve": {
                        "cve_id": "CVE-2024-3094",
                        "date_published": "2026-10-01T09:30:00Z",
                        "date_updated": "2026-10-01T09:30:00Z",
                        "in_stack": 3,
                        "score": 9.8,
                        "severity": "HIGH",
                        "state": "done",
                        "title": "Payments API"
                      },
                      "cwes": [
                        {
                          "description": "Card processing services and their container images",
                          "id": "id"
                        }
                      ],
                      "description": "Card processing services and their container images",
                      "metrics": [
                        {
                          "provider": "github",
                          "score": 9.8,
                          "severity": "HIGH",
                          "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
                          "version": "3.0.13"
                        }
                      ],
                      "references": [
                        {
                          "name": "Payments",
                          "url": "https://hooks.acme.dev/mycve"
                        }
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1CveDetail"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A CVE: description, metrics, CWEs, affected versions, references",
        "tags": [
          "CVEs"
        ],
        "x-apidog-folder": "CVEs",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/cves/CVE-2024-3094\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/cves/CVE-2024-3094\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/cves/CVE-2024-3094\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/cves/CVE-2024-3094\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/cves/CVE-2024-3094\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "cves",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/events": {
      "post": {
        "description": "Record product-usage events from a client (the web app and the CLI use it; integrations rarely need it). At most 50 events per batch, from a closed list of names and properties: nothing free-form is stored. Events that are not client-sendable, invalid, or from an opted-out user are dropped, never an error. A service token's events are dropped. Send `X-Cve-Analytics: off` to drop all.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:analytics`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "sendEvents",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "cli": {
                  "summary": "A CLI command with its outcome",
                  "value": {
                    "events": [
                      {
                        "name": "cli.command",
                        "props": {
                          "duration": "10-60s",
                          "status": "ok"
                        },
                        "route": "scan path"
                      }
                    ]
                  }
                },
                "page_view": {
                  "summary": "A page view",
                  "value": {
                    "events": [
                      {
                        "name": "page.viewed",
                        "project_id": 12,
                        "route": "/projects/:id"
                      }
                    ]
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/EventBatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "accepted": 1,
                      "dropped": 0
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/EventBatchResult"
                }
              }
            },
            "description": "Accepted: the work continues in the background",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Record usage events (web app and CLI)",
        "tags": [
          "Analytics"
        ],
        "x-apidog-folder": "Reports & analytics/Analytics",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/events\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"events\":[{\"name\":\"page.viewed\",\"project_id\":12,\"route\":\"/projects/:id\"}]}'"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "analytics",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/findings": {
      "get": {
        "description": "Open findings of the workspace: the findings of the latest finished scan of every target, highest score first. Filter by minimum severity or by a CVE id prefix, package or target. This is the list to alert or gate on.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:findings`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listFindings",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "CRITICAL | HIGH | MEDIUM | LOW | NONE (and above for lists of CVEs)",
            "example": "CRITICAL",
            "in": "query",
            "name": "severity",
            "required": false,
            "schema": {
              "enum": [
                "CRITICAL",
                "HIGH",
                "MEDIUM",
                "LOW",
                "NONE"
              ],
              "type": "string"
            }
          },
          {
            "description": "CVE id prefix, package or target substring",
            "example": "CVE-2024",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "finding": {
                            "confidence": "high",
                            "cve_id": "CVE-2024-3094",
                            "ecosystem": "debian",
                            "package": "xz-utils",
                            "score": 10.0,
                            "severity": "CRITICAL",
                            "source": "osv",
                            "title": "Malicious code in xz-utils",
                            "version": "5.6.0"
                          },
                          "kind": "image",
                          "scan_id": 4812,
                          "scanned_at": "2026-10-01T09:34:12Z",
                          "target": "ghcr.io/acme/payments-api:1.4.2"
                        }
                      ],
                      "next_cursor": "eyJ2IjoxLCJrIjpbIm8iLDUwXX0"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1OpenFinding"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Open findings: the latest finished scan of every target, highest score first",
        "tags": [
          "Findings"
        ],
        "x-apidog-folder": "Scans & findings/Findings",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/findings?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/findings?limit=50\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/findings?limit=50\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/findings?limit=50\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/findings?limit=50\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "findings",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/fixes": {
      "get": {
        "description": "Automatic fix runs, newest first: which scan they fix, their status, the branch and pull request they opened, and the changes.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:fixes`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listFixes",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "Of one scan",
            "example": 4812,
            "in": "query",
            "name": "scan_id",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "ai": false,
                          "branch": "mycve/fix-4812",
                          "changes": null,
                          "created_at": "2026-10-01T09:30:00Z",
                          "error": "connection refused",
                          "finished_at": "2026-10-01T09:34:12Z",
                          "id": 77,
                          "kind": "git",
                          "mode": "pr",
                          "pr_url": "https://github.com/acme/payments-api/pull/318",
                          "scan_id": 4812,
                          "started_at": "2026-10-01T09:30:00Z",
                          "status": "done",
                          "target": "https://github.com/acme/payments-api",
                          "trigger": "manual"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1FixRun"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Fix runs, newest first",
        "tags": [
          "Fixes"
        ],
        "x-apidog-folder": "Fixes",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/fixes?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "fixes",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Start a fix run for a finished scan of a repository: dependency upgrades (and, unless `ai: false`, an AI step for code changes) opened as a pull request (`mode: pr`, default), pushed as a branch, or computed only (`dry_run`). Answers 202 with the run; if the repository already has an active fix run, that run is returned instead of starting another. Needs a connector with fix permission (`cve connect github --fixes`).\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:fixes`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The scan has not finished: fix runs need a `done` scan.\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "createFix",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "dry_run": {
                  "summary": "Compute the changes only, no AI, at most 10",
                  "value": {
                    "ai": false,
                    "max_changes": 10,
                    "mode": "dry_run",
                    "scan_id": 4812
                  }
                },
                "pull_request": {
                  "summary": "Open a pull request",
                  "value": {
                    "scan_id": 4812
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateFix"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "ai": false,
                      "branch": "mycve/fix-4812",
                      "changes": null,
                      "created_at": "2026-10-01T09:30:00Z",
                      "error": "connection refused",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 77,
                      "kind": "git",
                      "mode": "pr",
                      "pr_url": "https://github.com/acme/payments-api/pull/318",
                      "scan_id": 4812,
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api",
                      "trigger": "manual"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1FixRun"
                }
              }
            },
            "description": "Accepted: the work continues in the background",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The scan has not finished: fix runs need a `done` scan."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Start a fix run for a scan (202; the target's active run if one is going)",
        "tags": [
          "Fixes"
        ],
        "x-apidog-folder": "Fixes",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/fixes\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"scan_id\":4812}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/fixes\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"scan_id\": 4812\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/fixes\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"scan_id\": 4812,\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/fixes\", strings.NewReader(`{\"scan_id\":4812}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/fixes\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"scan_id\": 4812\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "fixes",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/fixes/{fix_id}": {
      "get": {
        "description": "A fix run with its changes (package, from and to versions, the findings each change resolves) and its pull request.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:fixes`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getFix",
        "parameters": [
          {
            "description": "Fix run id (`id` of `listFixes` or `createFix`).",
            "example": 77,
            "in": "path",
            "name": "fix_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "ai": false,
                      "branch": "mycve/fix-4812",
                      "changes": null,
                      "created_at": "2026-10-01T09:30:00Z",
                      "error": "connection refused",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 77,
                      "kind": "git",
                      "mode": "pr",
                      "pr_url": "https://github.com/acme/payments-api/pull/318",
                      "scan_id": 4812,
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api",
                      "trigger": "manual"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1FixRun"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A fix run with its changes",
        "tags": [
          "Fixes"
        ],
        "x-apidog-folder": "Fixes",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/fixes/77\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "fixes",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/health": {
      "get": {
        "description": "Liveness of the API and whether it reaches its database. Use it for uptime checks and status pages; it needs no token and counts against no rate limit. `status` is `ok`, or `degraded` (with 503) when the database is unreachable.\n\n**Authentication**: none; this endpoint is public (no token, no workspace).\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.\n\n**Errors specific to this endpoint**\n\n- `503` `unavailable`: The database is unreachable (`status: degraded`).",
        "operationId": "getHealth",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "db": true,
                      "status": "ok",
                      "version": "v1"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The database is unreachable (`status: degraded`)."
          }
        },
        "security": [],
        "summary": "Liveness and database reachability (no auth)",
        "tags": [
          "Meta"
        ],
        "x-apidog-folder": "Meta",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/health\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "meta",
        "x-mycve-min-role": "viewer"
      }
    },
    "/infrastructure/drift": {
      "get": {
        "description": "Image tags declared in IaC (Kubernetes manifests, Compose, Terraform) compared with what the registries hold: missing tags, digests that moved, and newer releases.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:assets`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getInfrastructureDrift",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "declared": 3,
                      "hints": [
                        {
                          "declared": "declared",
                          "image": "ghcr.io/acme/payments-api:1.4.2",
                          "kind": "tag_not_in_registry",
                          "message": "connection refused",
                          "paths": [
                            "cve.yml"
                          ],
                          "registry_tags": [
                            "registry tag"
                          ],
                          "severity": "HIGH",
                          "target": "https://github.com/acme/payments-api"
                        }
                      ],
                      "registry_images": 3
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/DriftReport"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Image tags declared in IaC against what registries hold",
        "tags": [
          "Assets"
        ],
        "x-apidog-folder": "Assets & infrastructure",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/infrastructure/drift\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "assets",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/infrastructure/posture": {
      "get": {
        "description": "Misconfiguration findings of the workspace's infrastructure-as-code (Dockerfiles, Kubernetes, Terraform, CI) grouped by rule, with the affected files, the remediation, and the templates that fix them (`renderTemplate`).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:assets`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getInfrastructurePosture",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "by_area": {
                        "payments": 3
                      },
                      "by_severity": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "findings": 3,
                      "rules": [
                        {
                          "area": "area",
                          "controls": [
                            "control"
                          ],
                          "examples": [
                            "example"
                          ],
                          "findings": 3,
                          "remediation": "remediation",
                          "rule_id": "MYCVE-IAC-002",
                          "severity": "HIGH",
                          "targets": 3,
                          "templates": [
                            "template"
                          ],
                          "title": "Payments API"
                        }
                      ],
                      "targets_affected": 3,
                      "targets_scanned": 3,
                      "templates_in_use": {
                        "payments": 3
                      }
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/PostureSummary"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Misconfiguration findings by rule, with remediations and templates",
        "tags": [
          "Assets"
        ],
        "x-apidog-folder": "Assets & infrastructure",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/infrastructure/posture\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "assets",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/invites": {
      "get": {
        "description": "Pending and past invitations of the workspace, newest first. Admins only, since they include email addresses of people who are not members yet.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listInvites",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "accepted_at": "2026-10-01T09:30:00Z",
                          "created_at": "2026-10-01T09:30:00Z",
                          "declined_at": "2026-10-01T09:30:00Z",
                          "email": "ana@acme.dev",
                          "id": 19,
                          "invited_by": "ana@acme.dev",
                          "role": "member",
                          "team": "platform"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Invite"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Invitations of the workspace (admins)",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/invites?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Invite someone by email with a role (never above your own) and optionally into a team. Inviting an address that already has a pending invitation refreshes it (new expiry, new role) instead of creating a second one. The invitee accepts on the website after signing in.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The address already belongs to a member of the workspace.",
        "operationId": "createInvite",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "into_team": {
                  "summary": "Invite into a team",
                  "value": {
                    "email": "lee@acme.dev",
                    "role": "viewer",
                    "team": "platform"
                  }
                },
                "member": {
                  "summary": "Invite a member",
                  "value": {
                    "email": "sam@acme.dev",
                    "role": "member"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateInvite"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "accepted_at": "2026-10-01T09:30:00Z",
                      "created_at": "2026-10-01T09:30:00Z",
                      "declined_at": "2026-10-01T09:30:00Z",
                      "email": "ana@acme.dev",
                      "id": 19,
                      "invited_by": "ana@acme.dev",
                      "role": "member",
                      "team": "platform"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Invite"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The address already belongs to a member of the workspace."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Invite an email (refreshes a pending invitation)",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/invites\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"sam@acme.dev\",\"role\":\"member\"}'"
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/invites/{invite_id}": {
      "delete": {
        "description": "Withdraw a pending invitation; its link stops working. Accepted invitations cannot be withdrawn (remove the member instead).\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "revokeInvite",
        "parameters": [
          {
            "description": "Invitation id (`id` of `listInvites`).",
            "example": 19,
            "in": "path",
            "name": "invite_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Withdraw an invitation",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X DELETE \"https://api.mycve.io/v1/invites/19\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/jobs": {
      "get": {
        "description": "Jobs of the job system for this workspace: scans, connector syncs, fixes, pipeline jobs and report builds, newest first. Filter by kind, status (`active` = queued, leased or running) or subject (`scan:4812`).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:pipelines`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listJobs",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "scan | connector_sync | cve_sync | fix | pipeline_job",
            "example": "scan",
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "enum": [
                "scan",
                "connector_sync",
                "cve_sync",
                "fix",
                "pipeline_job"
              ],
              "type": "string"
            }
          },
          {
            "description": "queued | leased | running | done | failed | canceled | dead | active",
            "example": "active",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "queued",
                "leased",
                "running",
                "done",
                "failed",
                "canceled",
                "dead",
                "active"
              ],
              "type": "string"
            }
          },
          {
            "description": "scan:12, fix:3, connector:2, ...",
            "example": "scan:4812",
            "in": "query",
            "name": "subject",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "attempts": 1,
                          "cancel_requested": false,
                          "enqueued_at": "2026-10-01T09:30:00Z",
                          "error": "connection refused",
                          "finished_at": "2026-10-01T09:34:12Z",
                          "id": 3120,
                          "kind": "git",
                          "max_attempts": 3,
                          "priority": 3,
                          "result": null,
                          "started_at": "2026-10-01T09:30:00Z",
                          "status": "done",
                          "subject": "scan:4812"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Job"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Jobs of the workspace (scans, fixes, syncs, pipeline jobs, reports)",
        "tags": [
          "Pipelines"
        ],
        "x-apidog-folder": "Pipelines",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/jobs?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "pipelines",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/jobs/{job_id}": {
      "get": {
        "description": "One job: status, attempts, timing, error, and `result` when done (a report build's `report_id`, ...). This is what `createReport`'s `job_url` points at.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:pipelines`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getJob",
        "parameters": [
          {
            "description": "Job id (`id` of `listJobs`, or `job_id` of an accepted report build).",
            "example": 3120,
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "attempts": 1,
                      "cancel_requested": false,
                      "enqueued_at": "2026-10-01T09:30:00Z",
                      "error": "connection refused",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 3120,
                      "kind": "git",
                      "max_attempts": 3,
                      "priority": 3,
                      "result": null,
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "subject": "scan:4812"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Job"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A job",
        "tags": [
          "Pipelines"
        ],
        "x-apidog-folder": "Pipelines",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/jobs/3120\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "pipelines",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/jobs/{job_id}/cancel": {
      "post": {
        "description": "Cancel a job: a queued one at once, a running one at its next heartbeat (`cancel_requested: true` until then). A finished job is a 409.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:pipelines`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The job already finished.",
        "operationId": "cancelJob",
        "parameters": [
          {
            "description": "Job id (`id` of `listJobs`, or `job_id` of an accepted report build).",
            "example": 3120,
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "attempts": 1,
                      "cancel_requested": false,
                      "enqueued_at": "2026-10-01T09:30:00Z",
                      "error": "connection refused",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 3120,
                      "kind": "git",
                      "max_attempts": 3,
                      "priority": 3,
                      "result": null,
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "subject": "scan:4812"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Job"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The job already finished."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Cancel a job (a running one stops at its next heartbeat)",
        "tags": [
          "Pipelines"
        ],
        "x-apidog-folder": "Pipelines",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/jobs/3120/cancel\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "pipelines",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/me": {
      "get": {
        "description": "Who the API thinks you are: your user (null for a service token), the token in use (null for a browser session), the workspace this request acts in, your role there, and the effective access after capping the token by your role. Call it first when wiring up a new token or debugging a 403.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: always allowed (`meta` works for group-limited tokens too).\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getCaller",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "access": "write",
                      "identity": "ana@acme.dev",
                      "role": "admin",
                      "scopes": [
                        "write"
                      ],
                      "token": {
                        "created_at": "2026-09-01T09:30:00Z",
                        "created_by": "ana@acme.dev",
                        "expires_at": "2026-11-30T09:30:00Z",
                        "id": 58,
                        "kind": "personal",
                        "last_used_at": "2026-10-01T09:30:00Z",
                        "name": "laptop",
                        "prefix": "mycve_pat_3fa9c0d1",
                        "revoked_at": null,
                        "scopes": [
                          "write"
                        ],
                        "workspace_id": null
                      },
                      "user_id": 42,
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Caller"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Who is calling, in which workspace, with what access",
        "tags": [
          "Meta"
        ],
        "x-apidog-folder": "Meta",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/me\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/me\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/me\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/me\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/me\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "meta",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/me/analytics": {
      "get": {
        "description": "Whether you opted out of product-usage analytics. Opting out stops usage events about you; essential events (API calls, scans started, runs, fixes, reports) are still recorded without your user id. Service tokens have no preference (403).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: always allowed (`meta` works for group-limited tokens too).\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.\n\n**Errors specific to this endpoint**\n\n- `403` `forbidden`: A service token has no analytics preference.",
        "operationId": "getAnalyticsPreference",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "opt_out": false
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsPreference"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden",
            "description": "A service token has no analytics preference."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Your usage-analytics preference",
        "tags": [
          "Meta"
        ],
        "x-apidog-folder": "Meta",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/me/analytics\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "meta",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "put": {
        "description": "Opt out of (or back into) product-usage analytics for your user, in every workspace. Takes effect for the next event.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: always allowed (`meta` works for group-limited tokens too).\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).\n\n**Errors specific to this endpoint**\n\n- `403` `forbidden`: A service token has no analytics preference.",
        "operationId": "setAnalyticsPreference",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "opt_in": {
                  "summary": "Opt back in",
                  "value": {
                    "opt_out": false
                  }
                },
                "opt_out": {
                  "summary": "Opt out",
                  "value": {
                    "opt_out": true
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/AnalyticsPreference"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "opt_out": true
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsPreference"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden",
            "description": "A service token has no analytics preference."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Opt out of (or back into) usage analytics",
        "tags": [
          "Meta"
        ],
        "x-apidog-folder": "Meta",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X PUT \"https://api.mycve.io/v1/me/analytics\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"opt_out\":true}'"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "meta",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/members": {
      "get": {
        "description": "Members of the workspace with their effective role (the highest of their direct role and their teams' roles) and how they got it.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listMembers",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "effective_role": "member",
                          "identity": "ana@acme.dev",
                          "joined_at": "2026-10-01T09:30:00Z",
                          "kind": "git",
                          "last_seen": "2026-10-01T09:30:00Z",
                          "role": "member",
                          "teams": [
                            "platform"
                          ],
                          "user_id": 42
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Member"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Members and their effective roles",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/members?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/members/{user_id}": {
      "delete": {
        "description": "Remove a member from the workspace. Their personal tokens stop working in this workspace at once; resources they created stay. You cannot remove the last owner.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The member is the last owner of the workspace.",
        "operationId": "removeMember",
        "parameters": [
          {
            "description": "The member's user id (`user_id` of `listMembers`).",
            "example": 42,
            "in": "path",
            "name": "user_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The member is the last owner of the workspace."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Remove a member",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X DELETE \"https://api.mycve.io/v1/members/42\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      },
      "patch": {
        "description": "Change a member's direct role. You can never grant a role above your own, only owners make or unmake owners, and the last owner cannot be demoted. The change applies to the member's personal tokens on their next request.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The change would leave the workspace without an owner.",
        "operationId": "updateMember",
        "parameters": [
          {
            "description": "The member's user id (`user_id` of `listMembers`).",
            "example": 42,
            "in": "path",
            "name": "user_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "demote": {
                  "summary": "Read-only access",
                  "value": {
                    "role": "viewer"
                  }
                },
                "promote": {
                  "summary": "Make a member an admin",
                  "value": {
                    "role": "admin"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/UpdateMember"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "effective_role": "member",
                      "identity": "ana@acme.dev",
                      "joined_at": "2026-10-01T09:30:00Z",
                      "kind": "git",
                      "last_seen": "2026-10-01T09:30:00Z",
                      "role": "member",
                      "teams": [
                        "platform"
                      ],
                      "user_id": 42
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Member"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The change would leave the workspace without an owner."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Change a member's role (never above your own)",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X PATCH \"https://api.mycve.io/v1/members/42\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"role\":\"admin\"}'"
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/notifications": {
      "get": {
        "description": "Your in-app notifications in this workspace (and account-level ones such as invitations), newest first: scans finished or failed, new CVEs on watched targets, fix runs, connector problems, pipeline runs, valid secrets, dead-lettered jobs, reports. `unread=true` lists only unread ones; `all=true` every workspace's. A service token has none (an empty list). Mark them read on the website or with `cve notifications read`.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:notifications`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listNotifications",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "Only unread ones",
            "example": true,
            "in": "query",
            "name": "unread",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Every workspace's, not only this one's (account-level ones are always in)",
            "example": false,
            "in": "query",
            "name": "all",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "created_at": "2026-10-04T08:00:00Z",
                          "email_status": "sent",
                          "id": 41,
                          "kind": "cve.new",
                          "link": "/app/inventory/12",
                          "read_at": null,
                          "subject": "CVE-2021-23337 now affects acme/web via lodash 4.17.20",
                          "summary": "CVE-2021-23337 now affects acme/web via lodash 4.17.20",
                          "workspace_id": 4
                        }
                      ],
                      "next_cursor": null
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Notification"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Your in-app notifications in this workspace (newest first)",
        "tags": [
          "Notifications"
        ],
        "x-apidog-folder": "Account & workspaces/Notifications",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/notifications?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "notifications",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/openapi.json": {
      "get": {
        "description": "This OpenAPI 3.1 document, generated from the server's Rust types and route table, so it always matches the deployment that serves it. Import it into Apidog, Postman, Insomnia or a client generator; Apidog can also re-import it on a schedule from this URL.\n\n**Authentication**: none; this endpoint is public (no token, no workspace).\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getOpenApi",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "info": {
                        "title": "mycve API",
                        "version": "1.0.0"
                      },
                      "openapi": "3.1.0",
                      "paths": {}
                    }
                  }
                },
                "schema": {}
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [],
        "summary": "This document",
        "tags": [
          "Meta"
        ],
        "x-apidog-folder": "Meta",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/openapi.json\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/openapi.json\", {\n  method: \"GET\",\n  headers: {\n    \n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/openapi.json\",\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/openapi.json\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/openapi.json\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "meta",
        "x-mycve-min-role": "viewer"
      }
    },
    "/pipeline-runs": {
      "get": {
        "description": "`cve.yml` pipeline runs of the workspace, newest first, from pushes, schedules, CI (`cve ci run`), the website and this API. Filter by status, source, repository or ref.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:pipelines`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listPipelineRuns",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "queued | running | success | failed | canceled | skipped",
            "example": "failed",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "queued",
                "running",
                "success",
                "failed",
                "canceled",
                "skipped"
              ],
              "type": "string"
            }
          },
          {
            "description": "push | api | schedule | ci | web",
            "example": "api",
            "in": "query",
            "name": "source",
            "required": false,
            "schema": {
              "enum": [
                "push",
                "api",
                "schedule",
                "ci",
                "web"
              ],
              "type": "string"
            }
          },
          {
            "description": "Repository URL or owner/name",
            "example": "acme/payments-api",
            "in": "query",
            "name": "target",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Branch or tag",
            "example": "main",
            "in": "query",
            "name": "ref",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "created_at": "2026-10-01T09:30:00Z",
                          "created_by": "ana@acme.dev",
                          "error": "connection refused",
                          "file": "cve.yml",
                          "finished_at": "2026-10-01T09:34:12Z",
                          "id": 905,
                          "jobs": [
                            {
                              "allow_failure": false,
                              "attempt": 3,
                              "duration_ms": 1840,
                              "error": "connection refused",
                              "exit_code": 3,
                              "findings_summary": {
                                "critical": 2,
                                "high": 5,
                                "low": 4,
                                "medium": 11,
                                "none": 0
                              },
                              "finished_at": "2026-10-01T09:34:12Z",
                              "id": 101,
                              "name": "Payments",
                              "needs": [
                                "need"
                              ],
                              "stage": "stage",
                              "started_at": "2026-10-01T09:30:00Z",
                              "status": "done",
                              "uses": "uses"
                            }
                          ],
                          "name": "Payments",
                          "ref": "main",
                          "sha": "9fceb02d0ae598e95dc970b74767f19372d61af8",
                          "source": "manual",
                          "started_at": "2026-10-01T09:30:00Z",
                          "status": "done",
                          "target": "https://github.com/acme/payments-api"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1PipelineRun"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Pipeline runs, newest first",
        "tags": [
          "Pipelines"
        ],
        "x-apidog-folder": "Pipelines",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/pipeline-runs?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "pipelines",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Run a connected repository's pipeline (`cve.yml`, or another file) on a branch, tag or commit, optionally only some jobs (and what they need) and with extra variables. The repository is given as its URL (`target`) or as a connector and `repo`. The run is created and its jobs queued (201); follow it with `getPipelineRun`. Use this to trigger security pipelines from your own CI or deploy tooling; from a CI job itself, `cve ci run` does the same with a CI token.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:pipelines`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `413` `payload_too_large`: The pipeline file is larger than the limit.\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "createPipelineRun",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "connector": {
                  "summary": "Through a connector, at a commit, another file",
                  "value": {
                    "connector_id": 3,
                    "file": ".cve/nightly.yml",
                    "ref": "main",
                    "repo": "acme/payments-api",
                    "sha": "9fceb02d0ae598e95dc970b74767f19372d61af8"
                  }
                },
                "default_branch": {
                  "summary": "The default branch",
                  "value": {
                    "target": "https://github.com/acme/payments-api"
                  }
                },
                "release": {
                  "summary": "A tag, two jobs, a variable",
                  "value": {
                    "jobs": [
                      "scan",
                      "secrets"
                    ],
                    "ref": "v1.4.2",
                    "tag": true,
                    "target": "https://github.com/acme/payments-api",
                    "variables": {
                      "DEPLOY_ENV": "staging"
                    }
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreatePipelineRun"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "error": null,
                      "file": "cve.yml",
                      "finished_at": null,
                      "id": 905,
                      "jobs": [
                        {
                          "allow_failure": false,
                          "attempt": 1,
                          "duration_ms": null,
                          "error": null,
                          "exit_code": null,
                          "findings_summary": null,
                          "finished_at": null,
                          "id": 1,
                          "name": "scan",
                          "needs": [],
                          "stage": "test",
                          "started_at": null,
                          "status": "queued",
                          "uses": "cve/scan"
                        }
                      ],
                      "name": "security",
                      "ref": "main",
                      "sha": "9fceb02d0ae598e95dc970b74767f19372d61af8",
                      "source": "api",
                      "started_at": null,
                      "status": "queued",
                      "target": "https://github.com/acme/payments-api"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1PipelineRun"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge",
            "description": "The pipeline file is larger than the limit."
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Run a connected repository's cve.yml",
        "tags": [
          "Pipelines"
        ],
        "x-apidog-folder": "Pipelines",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/pipeline-runs\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"target\":\"https://github.com/acme/payments-api\"}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/pipeline-runs\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"target\": \"https://github.com/acme/payments-api\"\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/pipeline-runs\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"target\": \"https://github.com/acme/payments-api\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/pipeline-runs\", strings.NewReader(`{\"target\":\"https://github.com/acme/payments-api\"}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/pipeline-runs\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"target\": \"https://github.com/acme/payments-api\"\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "pipelines",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/pipeline-runs/{run_id}": {
      "get": {
        "description": "A run with its jobs (stage, status, attempts, duration, findings summary). Poll it until `status` is `success`, `failed`, `canceled` or `skipped`.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:pipelines`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getPipelineRun",
        "parameters": [
          {
            "description": "Pipeline run id (`id` of `listPipelineRuns` or `createPipelineRun`).",
            "example": 905,
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "error": "connection refused",
                      "file": "cve.yml",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 905,
                      "jobs": [
                        {
                          "allow_failure": false,
                          "attempt": 3,
                          "duration_ms": 1840,
                          "error": "connection refused",
                          "exit_code": 3,
                          "findings_summary": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "finished_at": "2026-10-01T09:34:12Z",
                          "id": 101,
                          "name": "Payments",
                          "needs": [
                            "need"
                          ],
                          "stage": "stage",
                          "started_at": "2026-10-01T09:30:00Z",
                          "status": "done",
                          "uses": "uses"
                        }
                      ],
                      "name": "Payments",
                      "ref": "main",
                      "sha": "9fceb02d0ae598e95dc970b74767f19372d61af8",
                      "source": "manual",
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1PipelineRun"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A run and its jobs",
        "tags": [
          "Pipelines"
        ],
        "x-apidog-folder": "Pipelines",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/pipeline-runs/905\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/pipeline-runs/905\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/pipeline-runs/905\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/pipeline-runs/905\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/pipeline-runs/905\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "pipelines",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/pipeline-runs/{run_id}/cancel": {
      "post": {
        "description": "Cancel a queued or running run: queued jobs are canceled, running ones stop at their next heartbeat. Canceling a finished run is a 409.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:pipelines`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The run already finished.\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "cancelPipelineRun",
        "parameters": [
          {
            "description": "Pipeline run id (`id` of `listPipelineRuns` or `createPipelineRun`).",
            "example": 905,
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "error": "connection refused",
                      "file": "cve.yml",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 905,
                      "jobs": [
                        {
                          "allow_failure": false,
                          "attempt": 3,
                          "duration_ms": 1840,
                          "error": "connection refused",
                          "exit_code": 3,
                          "findings_summary": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "finished_at": "2026-10-01T09:34:12Z",
                          "id": 101,
                          "name": "Payments",
                          "needs": [
                            "need"
                          ],
                          "stage": "stage",
                          "started_at": "2026-10-01T09:30:00Z",
                          "status": "done",
                          "uses": "uses"
                        }
                      ],
                      "name": "Payments",
                      "ref": "main",
                      "sha": "9fceb02d0ae598e95dc970b74767f19372d61af8",
                      "source": "manual",
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1PipelineRun"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The run already finished."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Cancel a run",
        "tags": [
          "Pipelines"
        ],
        "x-apidog-folder": "Pipelines",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/pipeline-runs/905/cancel\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "pipelines",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/product-suggestions": {
      "get": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `listProjectSuggestions` (`GET /project-suggestions`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nProjects suggested from the infrastructure graph (a repository and the images built from it, their base images and registries), with a reason and a confidence. Accept one with `acceptProjectSuggestion`, or dismiss it.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listProductSuggestions",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "open (default) | accepted | dismissed",
            "example": "open",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "default": "open",
              "enum": [
                "open",
                "accepted",
                "dismissed"
              ],
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "assets": [
                            {
                              "asset_id": "repo-3fa9c0d1e2b4",
                              "at_risk": false,
                              "kind": "repository",
                              "name": "Payments",
                              "role": "source",
                              "target": "https://github.com/acme/payments-api"
                            }
                          ],
                          "confidence": 1.5,
                          "created_at": "2026-10-01T09:30:00Z",
                          "fingerprint": "fingerprint",
                          "id": 31,
                          "name": "Payments",
                          "project_id": 12,
                          "reason": "connection refused",
                          "status": "open"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ProjectSuggestion"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Suggested projects from the infrastructure graph",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/product-suggestions?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/product-suggestions/refresh": {
      "post": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `refreshProjectSuggestions` (`POST /project-suggestions/refresh`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nRecompute suggestions now from the current inventory. Accepted and dismissed suggestions are kept (a dismissed group is never suggested again); open ones are replaced. In the `heavy` rate bucket.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `heavy`, 10 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "refreshProductSuggestions",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "assets": [
                            {
                              "asset_id": "repo-3fa9c0d1e2b4",
                              "at_risk": false,
                              "kind": "repository",
                              "name": "Payments",
                              "role": "source",
                              "target": "https://github.com/acme/payments-api"
                            }
                          ],
                          "confidence": 1.5,
                          "created_at": "2026-10-01T09:30:00Z",
                          "fingerprint": "fingerprint",
                          "id": 31,
                          "name": "Payments",
                          "project_id": 12,
                          "reason": "connection refused",
                          "status": "open"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ProjectSuggestion"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Recompute suggestions now",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/product-suggestions/refresh\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "heavy"
      }
    },
    "/product-suggestions/{suggestion_id}/accept": {
      "post": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `acceptProjectSuggestion` (`POST /project-suggestions/{suggestion_id}/accept`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nCreate the suggested project, optionally renamed or with only some of its assets. The suggestion becomes `accepted` and points at the new project (`project_id`).\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The suggestion was already accepted or dismissed.",
        "operationId": "acceptProductSuggestion",
        "parameters": [
          {
            "description": "Project suggestion id (`id` of `listProjectSuggestions`).",
            "example": 31,
            "in": "path",
            "name": "suggestion_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "as_is": {
                  "summary": "Accept as suggested",
                  "value": {}
                },
                "edited": {
                  "summary": "Rename and keep two assets",
                  "value": {
                    "asset_ids": [
                      "repo-3fa9c0d1e2b4",
                      "img-8b1e4f2a9c3d"
                    ],
                    "name": "Payments"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/AcceptSuggestion"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The suggestion was already accepted or dismissed."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Create the suggested project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/product-suggestions/31/accept\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/product-suggestions/{suggestion_id}/dismiss": {
      "post": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `dismissProjectSuggestion` (`POST /project-suggestions/{suggestion_id}/dismiss`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nNever suggest this group of assets again (until it changes). Dismissing an already decided suggestion does nothing.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "dismissProductSuggestion",
        "parameters": [
          {
            "description": "Project suggestion id (`id` of `listProjectSuggestions`).",
            "example": 31,
            "in": "path",
            "name": "suggestion_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Never suggest this group again",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/product-suggestions/31/dismiss\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/products": {
      "get": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `listProjects` (`GET /projects`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nProjects of the workspace with their open findings by severity, at-risk flag, shape and scan coverage. A project is one reference of code: a repository and the images it builds, just a repository, just an image, or just infrastructure. Tokens limited to projects see only theirs.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listProducts",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "name | -name | created_at | -created_at | risk | -risk",
            "example": "-risk",
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "enum": [
                "name",
                "-name",
                "created_at",
                "-created_at",
                "risk",
                "-risk"
              ],
              "type": "string"
            }
          },
          {
            "description": "Name contains",
            "example": "pay",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "asset_count": 3,
                          "at_risk": false,
                          "auto": false,
                          "color_slot": 3,
                          "coverage": 0.82,
                          "created_at": "2026-10-01T09:30:00Z",
                          "created_by": "ana@acme.dev",
                          "description": "Card processing services and their container images",
                          "id": 12,
                          "name": "Payments",
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "shape": "empty",
                          "slug": "payments",
                          "updated_at": "2026-10-01T09:34:12Z",
                          "workspace_id": 7
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "List projects",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/products?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/products?limit=50\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/products?limit=50\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/products?limit=50\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/products?limit=50\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `createProject` (`POST /projects`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nCreate a project from assets of the workspace: ids from `listAssets` (`asset_ids`), or assets by kind and target (`assets`), which also works for a repository or image that was never scanned yet (its id is the one its first scan will give it). At most 500 per call. Names may repeat; the slug (unique) and the colour are chosen for you. The asset list can be empty and filled later with `addProjectAssets`.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The workspace has the maximum number of projects.",
        "operationId": "createProduct",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "by_address": {
                  "summary": "A repository and an image by address, before their first scan",
                  "value": {
                    "assets": [
                      {
                        "kind": "repository",
                        "target": "https://github.com/acme/shop"
                      },
                      {
                        "kind": "image",
                        "target": "ghcr.io/acme/shop:1.4"
                      }
                    ],
                    "name": "Web shop"
                  }
                },
                "empty": {
                  "summary": "An empty project, filled later",
                  "value": {
                    "name": "Docs site"
                  }
                },
                "with_assets": {
                  "summary": "A project with a repository and its image",
                  "value": {
                    "asset_ids": [
                      "repo-3fa9c0d1e2b4",
                      "img-8b1e4f2a9c3d"
                    ],
                    "description": "Card processing API and its image",
                    "name": "Payments"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The workspace has the maximum number of projects."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Create a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/products\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"asset_ids\":[\"repo-3fa9c0d1e2b4\",\"img-8b1e4f2a9c3d\"],\"description\":\"Card processing API and its image\",\"name\":\"Payments\"}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/products\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"asset_ids\": [\n      \"repo-3fa9c0d1e2b4\",\n      \"img-8b1e4f2a9c3d\"\n    ],\n    \"description\": \"Card processing API and its image\",\n    \"name\": \"Payments\"\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/products\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"asset_ids\": [\n            \"repo-3fa9c0d1e2b4\",\n            \"img-8b1e4f2a9c3d\",\n        ],\n        \"description\": \"Card processing API and its image\",\n        \"name\": \"Payments\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/products\", strings.NewReader(`{\"asset_ids\":[\"repo-3fa9c0d1e2b4\",\"img-8b1e4f2a9c3d\"],\"description\":\"Card processing API and its image\",\"name\":\"Payments\"}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/products\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"asset_ids\": [\n            \"repo-3fa9c0d1e2b4\",\n            \"img-8b1e4f2a9c3d\"\n          ],\n          \"description\": \"Card processing API and its image\",\n          \"name\": \"Payments\"\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/products/{product_id}": {
      "delete": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `deleteProject` (`DELETE /projects/{project_id}`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nDelete a project (admins). Its assets, scans and findings are untouched (they are not owned by the project); reports already built for it keep their frozen content.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).",
        "operationId": "deleteProduct",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`); the deprecated name of `project_id`.",
            "example": 12,
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Delete a project (its assets stay)",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X DELETE \"https://api.mycve.io/v1/products/12\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      },
      "get": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `getProject` (`GET /projects/{project_id}`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nOne project with its headline numbers (asset count, shape, open findings by severity, coverage). Its dashboard data is `getProjectDashboard`; its assets `listProjectAssets`. The slug works in place of the id.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getProduct",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`); the deprecated name of `project_id`.",
            "example": 12,
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Get a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/products/12\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "patch": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `updateProject` (`PATCH /projects/{project_id}`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nRename a project or change its description; absent fields stay, `\"description\": null` clears it. Renaming keeps the id and slug, so links keep working.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).",
        "operationId": "updateProduct",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`); the deprecated name of `project_id`.",
            "example": 12,
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "describe": {
                  "summary": "Change the description",
                  "value": {
                    "description": "Card processing, refunds and payouts"
                  }
                },
                "rename": {
                  "summary": "Rename",
                  "value": {
                    "name": "Payments platform"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectPatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Rename or describe a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X PATCH \"https://api.mycve.io/v1/products/12\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Payments platform\"}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/products/{product_id}/assets": {
      "get": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `listProjectAssets` (`GET /projects/{project_id}/assets`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nThe assets of a project with their kind, scan target, and how each was added (by hand, from a suggestion). Live risk and last scan of each are in `listAssets` / `getAsset`.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listProductAssets",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`); the deprecated name of `project_id`.",
            "example": 12,
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "added_at": "2026-10-01T09:30:00Z",
                          "added_by": "added by",
                          "asset_id": "repo-3fa9c0d1e2b4",
                          "kind": "repository",
                          "name": "Payments",
                          "source": "manual",
                          "target": "https://github.com/acme/payments-api"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ProjectAsset"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Assets of a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/products/12/assets?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `addProjectAssets` (`POST /projects/{project_id}/assets`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nAdd assets to a project (1 to 500 per call), by id (`asset_ids`) or by kind and target (`assets`, also before their first scan). Ids already in the project are ignored; the answer says how many were added (`added`) and which ids are not assets of this workspace (`unknown`, skipped).\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.",
        "operationId": "addProductAssets",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`); the deprecated name of `project_id`.",
            "example": 12,
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "add": {
                  "summary": "Add a repository and an image",
                  "value": {
                    "asset_ids": [
                      "repo-3fa9c0d1e2b4",
                      "img-8b1e4f2a9c3d"
                    ]
                  }
                },
                "by_address": {
                  "summary": "Add an image by reference",
                  "value": {
                    "assets": [
                      {
                        "kind": "image",
                        "target": "ghcr.io/acme/shop:1.5"
                      }
                    ]
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectAssetsChange"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "added": 3,
                      "removed": 3,
                      "unknown": [
                        "unknown"
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectAssetsResult"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Add assets",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/products/12/assets\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"asset_ids\":[\"repo-3fa9c0d1e2b4\",\"img-8b1e4f2a9c3d\"]}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/products/{product_id}/assets/remove": {
      "post": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `removeProjectAssets` (`POST /projects/{project_id}/assets/remove`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nRemove assets from a project by id, or by target (a POST so the list can be long). The assets themselves stay in the workspace; ids not in the project come back in `unknown`.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.",
        "operationId": "removeProductAssets",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`); the deprecated name of `project_id`.",
            "example": 12,
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "by_target": {
                  "summary": "Remove by target",
                  "value": {
                    "assets": [
                      {
                        "target": "ghcr.io/acme/shop:1.4"
                      }
                    ]
                  }
                },
                "remove": {
                  "summary": "Remove one image",
                  "value": {
                    "asset_ids": [
                      "img-8b1e4f2a9c3d"
                    ]
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectAssetsChange"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "added": 3,
                      "removed": 3,
                      "unknown": [
                        "unknown"
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectAssetsResult"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Remove assets",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/products/12/assets/remove\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"asset_ids\":[\"img-8b1e4f2a9c3d\"]}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/products/{product_id}/dashboard": {
      "get": {
        "deprecated": true,
        "description": "**Deprecated** since 2026-10-04: use `getProjectDashboard` (`GET /projects/{project_id}/dashboard`). This route answers exactly like it, with `Deprecation`, `Sunset` and `Link: <...>; rel=\"deprecation\"` headers, until 2027-04-05; after that it answers `410 gone`. See [the migration notes](https://docs.mycve.io/15-cli-api-migration-map-2469116m0).\n\nEverything the project dashboard draws in one call: KPI tiles, the severity trend, top CVEs, the dependency flow, assets by risk and coverage, for a 7, 30 or 90 day range. Use it to build your own dashboard or export the numbers.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getProductDashboard",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`); the deprecated name of `project_id`.",
            "example": 12,
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "7d | 30d | 90d (default 30d)",
            "example": "30d",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "default": "30d",
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "activity": [
                        {
                          "at": "2026-10-01T09:30:00Z",
                          "href": "/app/projects/payments",
                          "kind": "git",
                          "status": "done",
                          "title": "Payments API"
                        }
                      ],
                      "age_heatmap": [
                        {
                          "value": 1.5,
                          "x": "2026-10-01",
                          "y": "y"
                        }
                      ],
                      "assets": [
                        {
                          "asset_id": "repo-3fa9c0d1e2b4",
                          "at_risk": false,
                          "kind": "repository",
                          "last_scan_at": "2026-10-01T09:30:00Z",
                          "name": "Payments",
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "packages": 3,
                          "provider": "github",
                          "stale": false
                        }
                      ],
                      "by_asset": [
                        {
                          "href": "/app/projects/payments",
                          "label": "payments-api",
                          "segments": [
                            {
                              "color": "slot-1",
                              "key": "payments",
                              "label": "payments-api",
                              "value": 1.5
                            }
                          ]
                        }
                      ],
                      "ecosystems": [
                        {
                          "color": "slot-1",
                          "key": "payments",
                          "label": "payments-api",
                          "value": 1.5
                        }
                      ],
                      "flow": {
                        "columns": [
                          "column"
                        ],
                        "links": [
                          {
                            "color": "slot-1",
                            "source": "manual",
                            "target": "https://github.com/acme/payments-api",
                            "value": 1.5
                          }
                        ],
                        "nodes": [
                          {
                            "color": "slot-1",
                            "column": 3,
                            "id": "id",
                            "label": "payments-api"
                          }
                        ]
                      },
                      "flow_trend": [
                        {
                          "color": "slot-1",
                          "key": "payments",
                          "label": "payments-api",
                          "points": [
                            {
                              "t": "2026-10-01",
                              "v": 1.5
                            }
                          ]
                        }
                      ],
                      "generated_at": "2026-10-01T09:30:00Z",
                      "kpis": [
                        {
                          "delta": 1.5,
                          "key": "payments",
                          "label": "payments-api",
                          "tone": "good",
                          "trend": [
                            1.5
                          ],
                          "unit": "findings",
                          "up_is_good": false,
                          "value": 1.5
                        }
                      ],
                      "open_trend": [
                        {
                          "color": "slot-1",
                          "key": "payments",
                          "label": "payments-api",
                          "points": [
                            {
                              "t": "2026-10-01",
                              "v": 1.5
                            }
                          ]
                        }
                      ],
                      "project": {
                        "asset_count": 3,
                        "at_risk": false,
                        "auto": false,
                        "color_slot": 3,
                        "coverage": 0.82,
                        "created_at": "2026-10-01T09:30:00Z",
                        "created_by": "ana@acme.dev",
                        "description": "Card processing services and their container images",
                        "id": 12,
                        "name": "Payments",
                        "open": {
                          "critical": 2,
                          "high": 5,
                          "low": 4,
                          "medium": 11,
                          "none": 0
                        },
                        "shape": "empty",
                        "slug": "payments",
                        "updated_at": "2026-10-01T09:34:12Z",
                        "workspace_id": 7
                      },
                      "range": "range",
                      "top_cves": [
                        {
                          "assets": [
                            "asset"
                          ],
                          "cve_id": "CVE-2024-3094",
                          "exploited": false,
                          "fix": "3.0.14",
                          "packages": [
                            "openssl"
                          ],
                          "score": 9.8,
                          "severity": "HIGH",
                          "title": "Payments API"
                        }
                      ],
                      "treemap": {
                        "children": [],
                        "color": "slot-1",
                        "href": "/app/projects/payments",
                        "key": "payments",
                        "label": "payments-api",
                        "value": 1.5
                      }
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectDashboard"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Everything the project dashboard draws",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/products/12/dashboard\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/project-suggestions": {
      "get": {
        "description": "Projects suggested from the infrastructure graph (a repository and the images built from it, their base images and registries), with a reason and a confidence. Accept one with `acceptProjectSuggestion`, or dismiss it.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listProjectSuggestions",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "open (default) | accepted | dismissed",
            "example": "open",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "default": "open",
              "enum": [
                "open",
                "accepted",
                "dismissed"
              ],
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "assets": [
                            {
                              "asset_id": "repo-3fa9c0d1e2b4",
                              "at_risk": false,
                              "kind": "repository",
                              "name": "Payments",
                              "role": "source",
                              "target": "https://github.com/acme/payments-api"
                            }
                          ],
                          "confidence": 1.5,
                          "created_at": "2026-10-01T09:30:00Z",
                          "fingerprint": "fingerprint",
                          "id": 31,
                          "name": "Payments",
                          "project_id": 12,
                          "reason": "connection refused",
                          "status": "open"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ProjectSuggestion"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Suggested projects from the infrastructure graph",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/project-suggestions?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/project-suggestions/refresh": {
      "post": {
        "description": "Recompute suggestions now from the current inventory. Accepted and dismissed suggestions are kept (a dismissed group is never suggested again); open ones are replaced. In the `heavy` rate bucket.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `heavy`, 10 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "refreshProjectSuggestions",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "assets": [
                            {
                              "asset_id": "repo-3fa9c0d1e2b4",
                              "at_risk": false,
                              "kind": "repository",
                              "name": "Payments",
                              "role": "source",
                              "target": "https://github.com/acme/payments-api"
                            }
                          ],
                          "confidence": 1.5,
                          "created_at": "2026-10-01T09:30:00Z",
                          "fingerprint": "fingerprint",
                          "id": 31,
                          "name": "Payments",
                          "project_id": 12,
                          "reason": "connection refused",
                          "status": "open"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ProjectSuggestion"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Recompute suggestions now",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/project-suggestions/refresh\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "heavy"
      }
    },
    "/project-suggestions/{suggestion_id}/accept": {
      "post": {
        "description": "Create the suggested project, optionally renamed or with only some of its assets. The suggestion becomes `accepted` and points at the new project (`project_id`).\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The suggestion was already accepted or dismissed.",
        "operationId": "acceptProjectSuggestion",
        "parameters": [
          {
            "description": "Project suggestion id (`id` of `listProjectSuggestions`).",
            "example": 31,
            "in": "path",
            "name": "suggestion_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "as_is": {
                  "summary": "Accept as suggested",
                  "value": {}
                },
                "edited": {
                  "summary": "Rename and keep two assets",
                  "value": {
                    "asset_ids": [
                      "repo-3fa9c0d1e2b4",
                      "img-8b1e4f2a9c3d"
                    ],
                    "name": "Payments"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/AcceptSuggestion"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The suggestion was already accepted or dismissed."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Create the suggested project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/project-suggestions/31/accept\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/project-suggestions/{suggestion_id}/dismiss": {
      "post": {
        "description": "Never suggest this group of assets again (until it changes). Dismissing an already decided suggestion does nothing.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "dismissProjectSuggestion",
        "parameters": [
          {
            "description": "Project suggestion id (`id` of `listProjectSuggestions`).",
            "example": 31,
            "in": "path",
            "name": "suggestion_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Never suggest this group again",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/project-suggestions/31/dismiss\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/projects": {
      "get": {
        "description": "Projects of the workspace with their open findings by severity, at-risk flag, shape and scan coverage. A project is one reference of code: a repository and the images it builds, just a repository, just an image, or just infrastructure. Tokens limited to projects see only theirs.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listProjects",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "name | -name | created_at | -created_at | risk | -risk",
            "example": "-risk",
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "enum": [
                "name",
                "-name",
                "created_at",
                "-created_at",
                "risk",
                "-risk"
              ],
              "type": "string"
            }
          },
          {
            "description": "Name contains",
            "example": "pay",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "asset_count": 3,
                          "at_risk": false,
                          "auto": false,
                          "color_slot": 3,
                          "coverage": 0.82,
                          "created_at": "2026-10-01T09:30:00Z",
                          "created_by": "ana@acme.dev",
                          "description": "Card processing services and their container images",
                          "id": 12,
                          "name": "Payments",
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "shape": "empty",
                          "slug": "payments",
                          "updated_at": "2026-10-01T09:34:12Z",
                          "workspace_id": 7
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "List projects",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/projects?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/projects?limit=50\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/projects?limit=50\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/projects?limit=50\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/projects?limit=50\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Create a project from assets of the workspace: ids from `listAssets` (`asset_ids`), or assets by kind and target (`assets`), which also works for a repository or image that was never scanned yet (its id is the one its first scan will give it). At most 500 per call. Names may repeat; the slug (unique) and the colour are chosen for you. The asset list can be empty and filled later with `addProjectAssets`.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The workspace has the maximum number of projects.",
        "operationId": "createProject",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "by_address": {
                  "summary": "A repository and an image by address, before their first scan",
                  "value": {
                    "assets": [
                      {
                        "kind": "repository",
                        "target": "https://github.com/acme/shop"
                      },
                      {
                        "kind": "image",
                        "target": "ghcr.io/acme/shop:1.4"
                      }
                    ],
                    "name": "Web shop"
                  }
                },
                "empty": {
                  "summary": "An empty project, filled later",
                  "value": {
                    "name": "Docs site"
                  }
                },
                "with_assets": {
                  "summary": "A project with a repository and its image",
                  "value": {
                    "asset_ids": [
                      "repo-3fa9c0d1e2b4",
                      "img-8b1e4f2a9c3d"
                    ],
                    "description": "Card processing API and its image",
                    "name": "Payments"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The workspace has the maximum number of projects."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Create a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/projects\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"asset_ids\":[\"repo-3fa9c0d1e2b4\",\"img-8b1e4f2a9c3d\"],\"description\":\"Card processing API and its image\",\"name\":\"Payments\"}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/projects\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"asset_ids\": [\n      \"repo-3fa9c0d1e2b4\",\n      \"img-8b1e4f2a9c3d\"\n    ],\n    \"description\": \"Card processing API and its image\",\n    \"name\": \"Payments\"\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/projects\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"asset_ids\": [\n            \"repo-3fa9c0d1e2b4\",\n            \"img-8b1e4f2a9c3d\",\n        ],\n        \"description\": \"Card processing API and its image\",\n        \"name\": \"Payments\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/projects\", strings.NewReader(`{\"asset_ids\":[\"repo-3fa9c0d1e2b4\",\"img-8b1e4f2a9c3d\"],\"description\":\"Card processing API and its image\",\"name\":\"Payments\"}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/projects\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"asset_ids\": [\n            \"repo-3fa9c0d1e2b4\",\n            \"img-8b1e4f2a9c3d\"\n          ],\n          \"description\": \"Card processing API and its image\",\n          \"name\": \"Payments\"\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/projects/{project_id}": {
      "delete": {
        "description": "Delete a project (admins). Its assets, scans and findings are untouched (they are not owned by the project); reports already built for it keep their frozen content.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).",
        "operationId": "deleteProject",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Delete a project (its assets stay)",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X DELETE \"https://api.mycve.io/v1/projects/12\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      },
      "get": {
        "description": "One project with its headline numbers (asset count, shape, open findings by severity, coverage). Its dashboard data is `getProjectDashboard`; its assets `listProjectAssets`. The slug works in place of the id.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getProject",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Get a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/projects/12\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "patch": {
        "description": "Rename a project or change its description; absent fields stay, `\"description\": null` clears it. Renaming keeps the id and slug, so links keep working.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).",
        "operationId": "updateProject",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "describe": {
                  "summary": "Change the description",
                  "value": {
                    "description": "Card processing, refunds and payouts"
                  }
                },
                "rename": {
                  "summary": "Rename",
                  "value": {
                    "name": "Payments platform"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectPatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "asset_count": 3,
                      "at_risk": false,
                      "auto": false,
                      "color_slot": 3,
                      "coverage": 0.82,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "id": 12,
                      "name": "Payments",
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "shape": "empty",
                      "slug": "payments",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Rename or describe a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X PATCH \"https://api.mycve.io/v1/projects/12\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Payments platform\"}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/projects/{project_id}/assets": {
      "get": {
        "description": "The assets of a project with their kind, scan target, and how each was added (by hand, from a suggestion). Live risk and last scan of each are in `listAssets` / `getAsset`.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listProjectAssets",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "added_at": "2026-10-01T09:30:00Z",
                          "added_by": "added by",
                          "asset_id": "repo-3fa9c0d1e2b4",
                          "kind": "repository",
                          "name": "Payments",
                          "source": "manual",
                          "target": "https://github.com/acme/payments-api"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ProjectAsset"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Assets of a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/projects/12/assets?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Add assets to a project (1 to 500 per call), by id (`asset_ids`) or by kind and target (`assets`, also before their first scan). Ids already in the project are ignored; the answer says how many were added (`added`) and which ids are not assets of this workspace (`unknown`, skipped).\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.",
        "operationId": "addProjectAssets",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "add": {
                  "summary": "Add a repository and an image",
                  "value": {
                    "asset_ids": [
                      "repo-3fa9c0d1e2b4",
                      "img-8b1e4f2a9c3d"
                    ]
                  }
                },
                "by_address": {
                  "summary": "Add an image by reference",
                  "value": {
                    "assets": [
                      {
                        "kind": "image",
                        "target": "ghcr.io/acme/shop:1.5"
                      }
                    ]
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectAssetsChange"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "added": 3,
                      "removed": 3,
                      "unknown": [
                        "unknown"
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectAssetsResult"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Add assets",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/projects/12/assets\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"asset_ids\":[\"repo-3fa9c0d1e2b4\",\"img-8b1e4f2a9c3d\"]}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/projects/{project_id}/assets/remove": {
      "post": {
        "description": "Remove assets from a project by id, or by target (a POST so the list can be long). The assets themselves stay in the workspace; ids not in the project come back in `unknown`.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.",
        "operationId": "removeProjectAssets",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "by_target": {
                  "summary": "Remove by target",
                  "value": {
                    "assets": [
                      {
                        "target": "ghcr.io/acme/shop:1.4"
                      }
                    ]
                  }
                },
                "remove": {
                  "summary": "Remove one image",
                  "value": {
                    "asset_ids": [
                      "img-8b1e4f2a9c3d"
                    ]
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectAssetsChange"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "added": 3,
                      "removed": 3,
                      "unknown": [
                        "unknown"
                      ]
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectAssetsResult"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Remove assets",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/projects/12/assets/remove\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"asset_ids\":[\"img-8b1e4f2a9c3d\"]}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/projects/{project_id}/dashboard": {
      "get": {
        "description": "Everything the project dashboard draws in one call: KPI tiles, the severity trend, top CVEs, the dependency flow, assets by risk and coverage, for a 7, 30 or 90 day range. Use it to build your own dashboard or export the numbers.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getProjectDashboard",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "7d | 30d | 90d (default 30d)",
            "example": "30d",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "default": "30d",
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "activity": [
                        {
                          "at": "2026-10-01T09:30:00Z",
                          "href": "/app/projects/payments",
                          "kind": "git",
                          "status": "done",
                          "title": "Payments API"
                        }
                      ],
                      "age_heatmap": [
                        {
                          "value": 1.5,
                          "x": "2026-10-01",
                          "y": "y"
                        }
                      ],
                      "assets": [
                        {
                          "asset_id": "repo-3fa9c0d1e2b4",
                          "at_risk": false,
                          "kind": "repository",
                          "last_scan_at": "2026-10-01T09:30:00Z",
                          "name": "Payments",
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "packages": 3,
                          "provider": "github",
                          "stale": false
                        }
                      ],
                      "by_asset": [
                        {
                          "href": "/app/projects/payments",
                          "label": "payments-api",
                          "segments": [
                            {
                              "color": "slot-1",
                              "key": "payments",
                              "label": "payments-api",
                              "value": 1.5
                            }
                          ]
                        }
                      ],
                      "ecosystems": [
                        {
                          "color": "slot-1",
                          "key": "payments",
                          "label": "payments-api",
                          "value": 1.5
                        }
                      ],
                      "flow": {
                        "columns": [
                          "column"
                        ],
                        "links": [
                          {
                            "color": "slot-1",
                            "source": "manual",
                            "target": "https://github.com/acme/payments-api",
                            "value": 1.5
                          }
                        ],
                        "nodes": [
                          {
                            "color": "slot-1",
                            "column": 3,
                            "id": "id",
                            "label": "payments-api"
                          }
                        ]
                      },
                      "flow_trend": [
                        {
                          "color": "slot-1",
                          "key": "payments",
                          "label": "payments-api",
                          "points": [
                            {
                              "t": "2026-10-01",
                              "v": 1.5
                            }
                          ]
                        }
                      ],
                      "generated_at": "2026-10-01T09:30:00Z",
                      "kpis": [
                        {
                          "delta": 1.5,
                          "key": "payments",
                          "label": "payments-api",
                          "tone": "good",
                          "trend": [
                            1.5
                          ],
                          "unit": "findings",
                          "up_is_good": false,
                          "value": 1.5
                        }
                      ],
                      "open_trend": [
                        {
                          "color": "slot-1",
                          "key": "payments",
                          "label": "payments-api",
                          "points": [
                            {
                              "t": "2026-10-01",
                              "v": 1.5
                            }
                          ]
                        }
                      ],
                      "project": {
                        "asset_count": 3,
                        "at_risk": false,
                        "auto": false,
                        "color_slot": 3,
                        "coverage": 0.82,
                        "created_at": "2026-10-01T09:30:00Z",
                        "created_by": "ana@acme.dev",
                        "description": "Card processing services and their container images",
                        "id": 12,
                        "name": "Payments",
                        "open": {
                          "critical": 2,
                          "high": 5,
                          "low": 4,
                          "medium": 11,
                          "none": 0
                        },
                        "shape": "empty",
                        "slug": "payments",
                        "updated_at": "2026-10-01T09:34:12Z",
                        "workspace_id": 7
                      },
                      "range": "range",
                      "top_cves": [
                        {
                          "assets": [
                            "asset"
                          ],
                          "cve_id": "CVE-2024-3094",
                          "exploited": false,
                          "fix": "3.0.14",
                          "packages": [
                            "openssl"
                          ],
                          "score": 9.8,
                          "severity": "HIGH",
                          "title": "Payments API"
                        }
                      ],
                      "treemap": {
                        "children": [],
                        "color": "slot-1",
                        "href": "/app/projects/payments",
                        "key": "payments",
                        "label": "payments-api",
                        "value": 1.5
                      }
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectDashboard"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Everything the project dashboard draws",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/projects/12/dashboard\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/projects/{project_id}/sources": {
      "get": {
        "description": "Project sources connect provider groups or namespaces so their repositories and images join the project. A source is scoped by its connector's granted access.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listProjectSources",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "account": "acme",
                          "asset_count": 3,
                          "connector_id": 3,
                          "created_at": "2026-10-01T09:30:00Z",
                          "created_by": "ana@acme.dev",
                          "id": 101,
                          "include_subgroups": false,
                          "kind": "gitlab_group",
                          "last_error": "last error",
                          "last_synced_at": "2026-10-01T09:30:00Z",
                          "path": "cve.yml",
                          "project_id": 12,
                          "provider": "github"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ProjectSource"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Sources connected to a project",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/projects/12/sources?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Connect and immediately sync a GitLab group, GitHub owner, Docker Hub namespace, or Artifact Registry location. The connector must already exist in this workspace.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.",
        "operationId": "createProjectSource",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "github_owner": {
                  "summary": "Connect a GitHub organization",
                  "value": {
                    "connector_id": 12,
                    "kind": "github_owner",
                    "path": "acme"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/ProjectSourceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "account": "acme",
                      "asset_count": 3,
                      "connector_id": 3,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "id": 101,
                      "include_subgroups": false,
                      "kind": "gitlab_group",
                      "last_error": "last error",
                      "last_synced_at": "2026-10-01T09:30:00Z",
                      "path": "cve.yml",
                      "project_id": 12,
                      "provider": "github"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectSource"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Connect a GitLab group, GitHub owner, Docker Hub namespace, or Artifact Registry location",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/projects/12/sources\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"connector_id\":12,\"kind\":\"github_owner\",\"path\":\"acme\"}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/projects/{project_id}/sources/{source_id}": {
      "delete": {
        "description": "Disconnect a source. Assets no longer included by another source or manual membership return to the workspace's automatic projects.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "deleteProjectSource",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The project source ID returned by `listProjectSources`.",
            "example": 1,
            "in": "path",
            "name": "source_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Disconnect a project source",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X DELETE \"https://api.mycve.io/v1/projects/12/sources/1\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/projects/{project_id}/sources/{source_id}/sync": {
      "post": {
        "description": "Refresh a source from the provider using the linked connector's current credentials and access.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:projects`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "syncProjectSource",
        "parameters": [
          {
            "description": "Project id (`id` of `listProjects`).",
            "example": 12,
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The project source ID returned by `listProjectSources`.",
            "example": 1,
            "in": "path",
            "name": "source_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "account": "acme",
                      "asset_count": 3,
                      "connector_id": 3,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "id": 101,
                      "include_subgroups": false,
                      "kind": "gitlab_group",
                      "last_error": "last error",
                      "last_synced_at": "2026-10-01T09:30:00Z",
                      "path": "cve.yml",
                      "project_id": 12,
                      "provider": "github"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ProjectSource"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Refresh the assets discovered from a project source",
        "tags": [
          "Projects"
        ],
        "x-apidog-folder": "Projects",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/projects/12/sources/1/sync\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "projects",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/reports": {
      "get": {
        "description": "Reports of the workspace, newest first, with their headline numbers (open by severity, new and resolved findings). Project-limited tokens see only project and portfolio reports within their projects.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:reports`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listReports",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "workspace | project | portfolio",
            "example": "project",
            "in": "query",
            "name": "scope",
            "required": false,
            "schema": {
              "enum": [
                "workspace",
                "project",
                "portfolio"
              ],
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "created_at": "2026-10-01T09:30:00Z",
                          "id": 214,
                          "job_id": 3120,
                          "new_findings": 3,
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "period_end": "2026-10-01T00:00:00Z",
                          "period_start": "2026-09-01T00:00:00Z",
                          "project_ids": [
                            12
                          ],
                          "resolved": 3,
                          "scope": "workspace",
                          "title": "Payments API",
                          "trigger": "manual"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ReportListItem"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Reports of the workspace, newest first",
        "tags": [
          "Reports"
        ],
        "x-apidog-folder": "Reports & analytics/Reports",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/reports?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "reports",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Build a workspace, project or portfolio report in the background. Answers 202 with the `report_build` job: poll `getJob` (or `job_url`) until it is `done`, then read `report_id` with `getReport` or `downloadReport`. The period defaults to the last 30 days. Reports are frozen when built. In the `heavy` rate bucket (10 a minute).\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:reports`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `heavy`, 10 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "createReport",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "portfolio": {
                  "summary": "A portfolio of three projects",
                  "value": {
                    "formats": [
                      "html",
                      "json"
                    ],
                    "scope": {
                      "project_ids": [
                        12,
                        15,
                        21
                      ],
                      "scope": "portfolio"
                    }
                  }
                },
                "project": {
                  "summary": "One project, September",
                  "value": {
                    "formats": [
                      "html",
                      "csv"
                    ],
                    "period_end": "2026-10-01T00:00:00Z",
                    "period_start": "2026-09-01T00:00:00Z",
                    "scope": {
                      "project_id": 12,
                      "scope": "project"
                    }
                  }
                },
                "workspace": {
                  "summary": "The workspace, last 30 days",
                  "value": {
                    "formats": [
                      "html"
                    ],
                    "scope": {
                      "scope": "workspace"
                    }
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateReport"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "job_id": 3120,
                      "job_url": "/v1/jobs/3120",
                      "report_id": null,
                      "status": "queued"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ReportJob"
                }
              }
            },
            "description": "Accepted: the work continues in the background",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Build a workspace, project or portfolio report in the background",
        "tags": [
          "Reports"
        ],
        "x-apidog-folder": "Reports & analytics/Reports",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/reports\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"formats\":[\"html\"],\"scope\":{\"scope\":\"workspace\"}}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/reports\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"formats\": [\n      \"html\"\n    ],\n    \"scope\": {\n      \"scope\": \"workspace\"\n    }\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/reports\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"formats\": [\n            \"html\",\n        ],\n        \"scope\": {\n            \"scope\": \"workspace\",\n        },\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/reports\", strings.NewReader(`{\"formats\":[\"html\"],\"scope\":{\"scope\":\"workspace\"}}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/reports\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"formats\": [\n            \"html\"\n          ],\n          \"scope\": {\n            \"scope\": \"workspace\"\n          }\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "reports",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "heavy"
      }
    },
    "/reports/{report_id}": {
      "get": {
        "description": "A report's frozen body as built: `ReportData` for workspace and project reports, `PortfolioReport` for portfolio reports (also typed at `getPortfolioReport`).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:reports`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.\n\n**Errors specific to this endpoint**\n\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "getReport",
        "parameters": [
          {
            "description": "Report id (`id` of `listReports`, or `report_id` of a finished `report_build` job).",
            "example": 214,
            "in": "path",
            "name": "report_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "generated_at": "2026-10-01T00:05:00Z",
                      "id": 214,
                      "identity": "ana@acme.dev",
                      "new_findings": [],
                      "new_total": 9,
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "open_findings": [],
                      "period_end": "2026-10-01T00:00:00Z",
                      "period_start": "2026-09-01T00:00:00Z",
                      "resolved": [],
                      "resolved_total": 14,
                      "scan_id": null,
                      "targets": [],
                      "title": "Payments · September 2026",
                      "trigger": "manual"
                    }
                  }
                },
                "schema": {}
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A report's frozen body (ReportData, or PortfolioReport for scope portfolio)",
        "tags": [
          "Reports"
        ],
        "x-apidog-folder": "Reports & analytics/Reports",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/reports/214\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "reports",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/reports/{report_id}/download": {
      "get": {
        "description": "A report as a file: `html` (print it to PDF from a browser; `pdf` returns the same printable HTML), `md`, `csv` or `json`. Portfolio CSVs have two tables: `table=projects` (default) or `table=shared` (CVEs shared by several projects).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:reports`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.\n\n**Errors specific to this endpoint**\n\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "downloadReport",
        "parameters": [
          {
            "description": "Report id (`id` of `listReports`, or `report_id` of a finished `report_build` job).",
            "example": 214,
            "in": "path",
            "name": "report_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "html (default) | pdf | md | csv | json",
            "example": "html",
            "in": "query",
            "name": "format",
            "required": false,
            "schema": {
              "default": "html",
              "enum": [
                "html",
                "pdf",
                "md",
                "csv",
                "json"
              ],
              "type": "string"
            }
          },
          {
            "description": "portfolio CSV: projects (default) | shared",
            "example": "projects",
            "in": "query",
            "name": "table",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "JSON",
                    "value": ""
                  }
                },
                "schema": {
                  "type": "string"
                }
              },
              "text/csv": {
                "examples": {
                  "default": {
                    "summary": "text/csv",
                    "value": "cve_id,severity,score,package,version,fixed_version,asset\nCVE-2024-3094,CRITICAL,10.0,xz-utils,5.6.0,5.6.1,img-8b1e4f2a9c3d\n"
                  }
                },
                "schema": {
                  "type": "string"
                }
              },
              "text/html": {
                "examples": {
                  "default": {
                    "summary": "text/html",
                    "value": "<!doctype html><html><head><title>Payments · September 2026</title></head><body>...</body></html>"
                  }
                },
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "examples": {
                  "default": {
                    "summary": "text/markdown",
                    "value": "# Payments · September 2026\n\n| Severity | Open |\n|---|---|\n| Critical | 2 |\n"
                  }
                },
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Content-Disposition": {
                "$ref": "#/components/headers/ContentDisposition"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A report as a file: html (print it to PDF), md, csv, json",
        "tags": [
          "Reports"
        ],
        "x-apidog-folder": "Reports & analytics/Reports",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/reports/214/download\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/reports/214/download\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/reports/214/download\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/reports/214/download\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/reports/214/download\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "reports",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/reports/{report_id}/portfolio": {
      "get": {
        "description": "A portfolio report, typed: projects ranked by risk score, the CVEs several projects share (fix once, help many), and the weekly trend per project.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:reports`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.\n\n**Errors specific to this endpoint**\n\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "getPortfolioReport",
        "parameters": [
          {
            "description": "Report id (`id` of `listReports`, or `report_id` of a finished `report_build` job).",
            "example": 214,
            "in": "path",
            "name": "report_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "generated_at": "2026-10-01T09:30:00Z",
                      "id": 214,
                      "period_end": "2026-10-01T00:00:00Z",
                      "period_start": "2026-09-01T00:00:00Z",
                      "projects": [
                        {
                          "assets": 3,
                          "color_slot": 3,
                          "covered": 3,
                          "mttr_hours": 30.5,
                          "name": "Payments",
                          "new_findings": 3,
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "open_start": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "project_id": 12,
                          "rank": 1,
                          "resolved": 3,
                          "risk_score": 72.5,
                          "scannable": 3,
                          "slug": "payments",
                          "stale_assets": 3,
                          "top_cves": [
                            "CVE-2024-3094"
                          ],
                          "trend": [
                            9,
                            7,
                            6,
                            4
                          ]
                        }
                      ],
                      "scope": {
                        "scope": "workspace"
                      },
                      "shared_cves": [
                        {
                          "assets": 3,
                          "cve_id": "CVE-2024-3094",
                          "fix": "3.0.14",
                          "project_ids": [
                            12
                          ],
                          "score": 9.8,
                          "severity": "HIGH",
                          "title": "Payments API"
                        }
                      ],
                      "title": "Payments API",
                      "totals": {
                        "assets": 3,
                        "at_risk_projects": 3,
                        "coverage": 0.82,
                        "mttr_hours": 30.5,
                        "new_findings": 3,
                        "open": {
                          "critical": 2,
                          "high": 5,
                          "low": 4,
                          "medium": 11,
                          "none": 0
                        },
                        "projects": 3,
                        "resolved": 3,
                        "scans_done": 3,
                        "scans_failed": 3,
                        "unassigned_assets": 3
                      },
                      "trigger": "manual",
                      "weekly": [
                        {
                          "new_findings": 3,
                          "open": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "resolved": 3,
                          "week_start": "2026-10-01"
                        }
                      ],
                      "workspace_id": 7
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/PortfolioReport"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A portfolio report (projects ranked by risk, shared CVEs, weekly trend)",
        "tags": [
          "Reports"
        ],
        "x-apidog-folder": "Reports & analytics/Reports",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/reports/214/portfolio\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "reports",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/scans": {
      "get": {
        "description": "Scans of the workspace, newest first, with their status and finding counts (from the website, the CLI, CI, pipelines and this API).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:scans`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listScans",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "counts": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "created_at": "2026-10-01T09:30:00Z",
                          "error": "connection refused",
                          "finding_count": 3,
                          "findings_url": "/v1/scans/4812/findings",
                          "finished_at": "2026-10-01T09:34:12Z",
                          "id": 4812,
                          "kind": "git",
                          "package_count": 3,
                          "profile": "profile",
                          "started_at": "2026-10-01T09:30:00Z",
                          "status": "done",
                          "target": "https://github.com/acme/payments-api",
                          "tasks_url": "/v1/scans/4812/tasks",
                          "watch_id": 3
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Scan"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Scans of the workspace, newest first",
        "tags": [
          "Scans"
        ],
        "x-apidog-folder": "Scans & findings/Scans",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/scans?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "scans",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Scan a Git repository (`kind: git`, an HTTPS URL) or a container image (`kind: image`, a reference). The scan is queued on the scan pool (201, `status: queued`); follow it with `getScan` or the `scan.completed` / `scan.failed` webhooks, then read `listScanFindings`. Private repositories and registries use the workspace's connectors. Folders are uploaded with `cve scan path` instead.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:scans`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "createScan",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "image": {
                  "summary": "A container image",
                  "value": {
                    "kind": "image",
                    "target": "ghcr.io/acme/payments-api:1.4.2"
                  }
                },
                "profile": {
                  "summary": "With an explicit scan profile",
                  "value": {
                    "kind": "git",
                    "profile": "default",
                    "target": "https://gitlab.com/acme/web"
                  }
                },
                "repository": {
                  "summary": "A Git repository",
                  "value": {
                    "kind": "git",
                    "target": "https://github.com/acme/payments-api"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateScan"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "counts": {
                        "critical": 0,
                        "high": 0,
                        "low": 0,
                        "medium": 0,
                        "none": 0
                      },
                      "created_at": "2026-10-01T09:30:00Z",
                      "error": null,
                      "finding_count": 0,
                      "findings_url": "/v1/scans/4812/findings",
                      "finished_at": null,
                      "id": 4812,
                      "kind": "git",
                      "package_count": 0,
                      "profile": null,
                      "started_at": null,
                      "status": "queued",
                      "target": "https://github.com/acme/payments-api",
                      "tasks_url": "/v1/scans/4812/tasks",
                      "watch_id": null
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Scan"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Scan a repository or image (queued on the scan pool)",
        "tags": [
          "Scans"
        ],
        "x-apidog-folder": "Scans & findings/Scans",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/scans\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"kind\":\"git\",\"target\":\"https://github.com/acme/payments-api\"}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/scans\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"kind\": \"git\",\n    \"target\": \"https://github.com/acme/payments-api\"\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/scans\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"kind\": \"git\",\n        \"target\": \"https://github.com/acme/payments-api\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/scans\", strings.NewReader(`{\"kind\":\"git\",\"target\":\"https://github.com/acme/payments-api\"}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/scans\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"kind\": \"git\",\n          \"target\": \"https://github.com/acme/payments-api\"\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "scans",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/scans/{scan_id}": {
      "get": {
        "description": "A scan with its status (`queued`, `running`, `done`, `failed`), timing and finding counts by severity. Poll it after `createScan` (every 5 to 10 seconds is plenty), or subscribe to the scan webhooks.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:scans`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getScan",
        "parameters": [
          {
            "description": "Scan id (`id` of `listScans` or `createScan`).",
            "example": 4812,
            "in": "path",
            "name": "scan_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "counts": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "created_at": "2026-10-01T09:30:00Z",
                      "error": null,
                      "finding_count": 22,
                      "findings_url": "/v1/scans/4812/findings",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 4812,
                      "kind": "git",
                      "package_count": 412,
                      "profile": null,
                      "started_at": "2026-10-01T09:30:04Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api",
                      "tasks_url": "/v1/scans/4812/tasks",
                      "watch_id": null
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Scan"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A scan and its counts",
        "tags": [
          "Scans"
        ],
        "x-apidog-folder": "Scans & findings/Scans",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/scans/4812\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/scans/4812\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/scans/4812\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/scans/4812\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/scans/4812\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "scans",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/scans/{scan_id}/findings": {
      "get": {
        "description": "The findings of one scan (a CVE in a package version), highest score first. Unlike `listFindings`, this is exactly what that scan saw, even if newer scans exist.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:scans`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listScanFindings",
        "parameters": [
          {
            "description": "Scan id (`id` of `listScans` or `createScan`).",
            "example": 4812,
            "in": "path",
            "name": "scan_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "confidence": "confidence",
                          "cve_id": "CVE-2024-3094",
                          "ecosystem": "npm",
                          "package": "openssl",
                          "score": 9.8,
                          "severity": "HIGH",
                          "source": "manual",
                          "title": "Payments API",
                          "version": "3.0.13"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Finding"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Findings of one scan",
        "tags": [
          "Scans"
        ],
        "x-apidog-folder": "Scans & findings/Scans",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/scans/4812/findings?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/scans/4812/findings?limit=50\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.get(\n    \"https://api.mycve.io/v1/scans/4812/findings?limit=50\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"GET\", \"https://api.mycve.io/v1/scans/4812/findings?limit=50\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .get(\"https://api.mycve.io/v1/scans/4812/findings?limit=50\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "scans",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/scans/{scan_id}/packages": {
      "get": {
        "description": "The package inventory (SBOM-like) of one scan: every package and version found, with its ecosystem and where it was found.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:scans`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listScanPackages",
        "parameters": [
          {
            "description": "Scan id (`id` of `listScans` or `createScan`).",
            "example": 4812,
            "in": "path",
            "name": "scan_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "ecosystem": "npm",
                          "name": "Payments",
                          "source": "manual",
                          "version": "3.0.13"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Package"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "The inventory of one scan",
        "tags": [
          "Scans"
        ],
        "x-apidog-folder": "Scans & findings/Scans",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/scans/4812/packages?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "scans",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/scans/{scan_id}/tasks": {
      "get": {
        "description": "The tasks of a distributed scan (docs/SCANS.md): plan, per-shard snapshot and analysis, assemble, match and merge, with their status, attempts, job id and timing. A scan that ran in one piece (small repositories, images, or distributed scans switched off) has none: an empty list. 404 for an unknown scan.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:scans`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listScanTasks",
        "parameters": [
          {
            "description": "Scan id (`id` of `listScans` or `createScan`).",
            "example": 4812,
            "in": "path",
            "name": "scan_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "attempts": 1,
                          "deps": [
                            "snapshot:web"
                          ],
                          "duration_ms": 8400,
                          "error": null,
                          "finished_at": "2026-10-04T09:12:11Z",
                          "inputs": 412,
                          "job_id": 9021,
                          "key": "analyze:web",
                          "kind": "extract",
                          "shard": "web",
                          "stage": "analyze",
                          "started_at": "2026-10-04T09:12:03Z",
                          "status": "done"
                        }
                      ],
                      "next_cursor": null
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1ScanTask"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Tasks of a distributed scan (empty for a scan that ran in one piece)",
        "tags": [
          "Scans"
        ],
        "x-apidog-folder": "Scans & findings/Scans",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/scans/4812/tasks?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "scans",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/secret-incidents": {
      "get": {
        "description": "Leaked-secret incidents from GitGuardian mapped to the workspace's repositories, open first: detector, validity (is the secret still live?), occurrences and a link to GitGuardian. Needs the GitGuardian connector.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:secrets`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listSecretIncidents",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "triggered | assigned | resolved | ignored",
            "example": "triggered",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "triggered",
                "assigned",
                "resolved",
                "ignored"
              ],
              "type": "string"
            }
          },
          {
            "description": "valid | invalid | unknown",
            "example": "valid",
            "in": "query",
            "name": "validity",
            "required": false,
            "schema": {
              "enum": [
                "valid",
                "invalid",
                "unknown"
              ],
              "type": "string"
            }
          },
          {
            "description": "critical | high | medium | low | info | unknown",
            "example": "critical",
            "in": "query",
            "name": "severity",
            "required": false,
            "schema": {
              "enum": [
                "critical",
                "high",
                "medium",
                "low",
                "info",
                "unknown"
              ],
              "type": "string"
            }
          },
          {
            "description": "Target or source URL contains",
            "example": "payments-api",
            "in": "query",
            "name": "repo",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "connector_id": 3,
                          "detector": "AWS Keys",
                          "first_seen": "2026-10-01T09:30:00Z",
                          "gitguardian_url": "https://dashboard.gitguardian.com/workspace/1/incidents/6021",
                          "id": 6021,
                          "last_seen": "2026-10-01T09:30:00Z",
                          "occurrences": 3,
                          "severity": "high",
                          "source": "manual",
                          "status": "done",
                          "target": "https://github.com/acme/payments-api",
                          "updated_at": "2026-10-01T09:34:12Z",
                          "validity": "validity"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1SecretIncident"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "GitGuardian incidents mapped to the workspace (open first)",
        "tags": [
          "Secrets"
        ],
        "x-apidog-folder": "Secrets",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/secret-incidents?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "secrets",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/secret-incidents/summary": {
      "get": {
        "description": "Counts of open, valid (still live), resolved and ignored secret incidents: the numbers for a security dashboard tile.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:secrets`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getSecretsSummary",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "connected": false,
                      "findings": 3,
                      "ignored": 3,
                      "last_sync": "2026-10-01T09:30:00Z",
                      "open": 3,
                      "resolved": 3,
                      "valid": 3
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1SecretsSummary"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Counts of open, valid, resolved and ignored incidents",
        "tags": [
          "Secrets"
        ],
        "x-apidog-folder": "Secrets",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/secret-incidents/summary\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "secrets",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/teams": {
      "get": {
        "description": "Teams of the workspace with their members and the role a team grants. A member's effective role is the highest of their own and their teams'.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listTeams",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "created_at": "2026-10-01T09:30:00Z",
                          "id": 4,
                          "members": [
                            "member"
                          ],
                          "name": "Payments",
                          "role": "member"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Team"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Teams and their members",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/teams?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/templates": {
      "get": {
        "description": "Secure-by-default infrastructure templates: Terraform modules, Kubernetes and Helm, Dockerfiles, Compose, CI workflows and `cve.yml` starters, each with the compliance controls it meets and the posture rules it remediates.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:templates`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listTemplates",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "terraform | kubernetes | helm | dockerfile | compose | ci | cve | wrangler",
            "example": "dockerfile",
            "in": "query",
            "name": "category",
            "required": false,
            "schema": {
              "enum": [
                "terraform",
                "kubernetes",
                "helm",
                "dockerfile",
                "compose",
                "ci",
                "cve",
                "wrangler"
              ],
              "type": "string"
            }
          },
          {
            "description": "aws | gcp | cloudflare | kubernetes | docker | github | gitlab | mycve",
            "example": "docker",
            "in": "query",
            "name": "provider",
            "required": false,
            "schema": {
              "enum": [
                "aws",
                "gcp",
                "cloudflare",
                "kubernetes",
                "docker",
                "github",
                "gitlab",
                "mycve"
              ],
              "type": "string"
            }
          },
          {
            "description": "Id, title, summary or tag contains",
            "example": "node",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "category": "terraform",
                          "compliance": [
                            {
                              "control": "control",
                              "framework": "framework",
                              "title": "Payments API"
                            }
                          ],
                          "id": "id",
                          "provider": "aws",
                          "remediates": [
                            "remediate"
                          ],
                          "summary": "Card processing services and their container images",
                          "tags": [
                            "docker",
                            "node"
                          ],
                          "title": "Payments API",
                          "version": "3.0.13"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/IacTemplateSummary"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Secure-by-default infrastructure templates",
        "tags": [
          "Templates"
        ],
        "x-apidog-folder": "Templates",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/templates?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "templates",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/templates/{template_id}": {
      "get": {
        "description": "A template with its parameters (types, defaults, validation patterns), the files it renders and its controls. Read it before `renderTemplate` to know which `params` to send.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:templates`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getTemplate",
        "parameters": [
          {
            "description": "Template id (`id` of `listTemplates`), kebab-case.",
            "example": "dockerfile-node",
            "in": "path",
            "name": "template_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "category": "terraform",
                      "compliance": [
                        {
                          "control": "control",
                          "framework": "framework",
                          "title": "Payments API"
                        }
                      ],
                      "delivery": [
                        "render"
                      ],
                      "description": "Card processing services and their container images",
                      "files": [
                        "cve.yml"
                      ],
                      "id": "id",
                      "next_steps": "next steps",
                      "params": [
                        {
                          "default": null,
                          "description": "Card processing services and their container images",
                          "max": 3,
                          "max_items": 3,
                          "max_len": 3,
                          "min": 3,
                          "name": "Payments",
                          "options": [
                            "option"
                          ],
                          "pattern": "pattern",
                          "title": "Payments API",
                          "type": "string"
                        }
                      ],
                      "provider": "aws",
                      "remediates": [
                        "remediate"
                      ],
                      "summary": "Card processing services and their container images",
                      "tags": [
                        "docker",
                        "node"
                      ],
                      "title": "Payments API",
                      "version": "3.0.13"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/IacTemplate"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "A template with its parameters, files and controls",
        "tags": [
          "Templates"
        ],
        "x-apidog-folder": "Templates",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/templates/dockerfile-node\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "templates",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/templates/{template_id}/pull-requests": {
      "post": {
        "description": "Render a template into a repository through a pull request (or a branch, or a dry run), opened by mycve's fix runner. The repository needs a finished scan in the workspace and a connector allowed to push fixes. Answers 202 with the fix run that will open it; follow it with `getFix`.\n\n**Permissions**\n\n- Minimum workspace role: `member` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `write` (tokens with `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:templates`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: A fix run is already queued or running for this repository; retry when it finishes.\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "createTemplatePullRequest",
        "parameters": [
          {
            "description": "Template id (`id` of `listTemplates`), kebab-case.",
            "example": "dockerfile-node",
            "in": "path",
            "name": "template_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "branch": {
                  "summary": "Push a branch by asset id",
                  "value": {
                    "mode": "branch",
                    "params": {},
                    "repo": "repo-3fa9c0d1e2b4"
                  }
                },
                "pull_request": {
                  "summary": "Open a pull request",
                  "value": {
                    "params": {
                      "app": "payments-api",
                      "port": 3000
                    },
                    "repo": "https://github.com/acme/payments-api"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateTemplatePullRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "branch": "mycve/fix-4812",
                      "files": [
                        "cve.yml"
                      ],
                      "fix_run_id": 77,
                      "status": "done",
                      "target": "https://github.com/acme/payments-api",
                      "template_id": "dockerfile-node",
                      "version": "3.0.13"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/TemplatePullRequest"
                }
              }
            },
            "description": "Accepted: the work continues in the background",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "A fix run is already queued or running for this repository; retry when it finishes."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Render into a repository through a pull request (mycve cloud, needs a fix token)",
        "tags": [
          "Templates"
        ],
        "x-apidog-folder": "Templates",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/templates/dockerfile-node/pull-requests\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"params\":{\"app\":\"payments-api\",\"port\":3000},\"repo\":\"https://github.com/acme/payments-api\"}'"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "templates",
        "x-mycve-min-role": "member",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/templates/{template_id}/render": {
      "post": {
        "description": "Render a template with your parameters: the files come back in JSON (or as a zip with `format=zip`). Nothing is stored except an audit row, so it only needs read access. Parameters are validated against the template (422 with the field). Strings are accepted for every type, as the CLI sends them.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:templates`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "renderTemplate",
        "parameters": [
          {
            "description": "Template id (`id` of `listTemplates`), kebab-case.",
            "example": "dockerfile-node",
            "in": "path",
            "name": "template_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "json (default) or zip",
            "example": "json",
            "in": "query",
            "name": "format",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "defaults": {
                  "summary": "All defaults",
                  "value": {
                    "params": {}
                  }
                },
                "node_service": {
                  "summary": "A Node.js service on port 3000",
                  "value": {
                    "params": {
                      "app": "payments-api",
                      "build": true,
                      "entrypoint": "dist/server.js",
                      "health_path": "/healthz",
                      "port": 3000
                    }
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/RenderTemplate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "JSON",
                    "value": {
                      "files": [
                        {
                          "content": "FROM node:24.21.0-bookworm-slim AS build\n",
                          "path": "cve.yml"
                        }
                      ],
                      "next_steps": "next steps",
                      "params": {
                        "app": "payments-api",
                        "port": 8080
                      },
                      "template_id": "dockerfile-node",
                      "version": "3.0.13"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/RenderedTemplate"
                }
              },
              "application/zip": {
                "examples": {
                  "default": {
                    "summary": "application/zip",
                    "value": "(binary zip of the rendered files)"
                  }
                },
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "OK",
            "headers": {
              "Content-Disposition": {
                "$ref": "#/components/headers/ContentDisposition"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Render a template (stores nothing but an audit row); format=zip downloads it",
        "tags": [
          "Templates"
        ],
        "x-apidog-folder": "Templates",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/templates/dockerfile-node/render\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"params\":{}}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/templates/dockerfile-node/render\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"params\": {}\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/templates/dockerfile-node/render\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    json={\n        \"params\": {},\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/templates/dockerfile-node/render\", strings.NewReader(`{\"params\":{}}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/templates/dockerfile-node/render\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .json(&serde_json::json!({\n          \"params\": {}\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "templates",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/tokens": {
      "get": {
        "description": "Your personal tokens; admins (people, not service tokens) also see the workspace's service tokens. Secrets are never returned, only the `prefix` (`mycve_pat_3fa9c0d1`) to tell them apart, and `last_used_at`. Revoked tokens are hidden unless `include_revoked=true`.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: any signed-in caller manages their own tokens; a new token never gets more access, groups or projects than the request that creates it (a `read` token cannot create tokens, a service token cannot create tokens).\n- Token scope: a token limited to resource groups needs `group:tokens`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listTokens",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "description": "Also list revoked tokens (default false)",
            "example": false,
            "in": "query",
            "name": "include_revoked",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "created_at": "2026-10-01T09:30:00Z",
                          "created_by": "ana@acme.dev",
                          "expires_at": "2026-12-30T09:30:00Z",
                          "id": 58,
                          "kind": "personal",
                          "last_used_at": "2026-10-01T09:34:12Z",
                          "name": "Payments",
                          "prefix": "mycve_pat_3fa9c0d1",
                          "revoked_at": "2026-10-01T09:30:00Z",
                          "scopes": [
                            "write",
                            "group:scans"
                          ],
                          "workspace_id": 7
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/ApiToken"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Your personal tokens, and (admins) the workspace's service tokens",
        "tags": [
          "Tokens"
        ],
        "x-apidog-folder": "Tokens & webhooks/Tokens",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/tokens?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "tokens",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Create a personal access token, or (admins) a workspace service token. The token is in the response **once**; only its hash is stored. A new token never gets more than the request that creates it: at most your effective access, and a group- or project-limited token can only create tokens limited to a subset. A `read` token or a service token cannot create tokens. Expiry defaults to 90 days (at most 366); `expires_in_days: null` (no expiry) is for service tokens created by an owner.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: any signed-in caller manages their own tokens; a new token never gets more access, groups or projects than the request that creates it (a `read` token cannot create tokens, a service token cannot create tokens).\n- Token scope: a token limited to resource groups needs `group:tokens`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.",
        "operationId": "createToken",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "ci_read": {
                  "summary": "A read-only personal token for a dashboard",
                  "value": {
                    "expires_in_days": 30,
                    "kind": "personal",
                    "name": "grafana",
                    "scopes": [
                      "read"
                    ]
                  }
                },
                "project": {
                  "summary": "A personal token limited to one project",
                  "value": {
                    "kind": "personal",
                    "name": "payments-team",
                    "scopes": [
                      "read",
                      "project:12"
                    ]
                  }
                },
                "scanner": {
                  "summary": "A service token that may only start scans and read findings",
                  "value": {
                    "expires_in_days": 90,
                    "kind": "service",
                    "name": "ci-scanner",
                    "scopes": [
                      "write",
                      "group:scans",
                      "group:findings"
                    ]
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateToken"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "info": {
                        "created_at": "2026-10-01T09:30:00Z",
                        "created_by": "ana@acme.dev",
                        "expires_at": "2026-12-30T09:30:00Z",
                        "id": 59,
                        "kind": "service",
                        "last_used_at": null,
                        "name": "ci-scanner",
                        "prefix": "mycve_svc_3fa9c0d1",
                        "revoked_at": null,
                        "scopes": [
                          "write",
                          "group:scans",
                          "group:findings"
                        ],
                        "workspace_id": 7
                      },
                      "token": "[REDACTED]"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/CreatedToken"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Create a token (shown once)",
        "tags": [
          "Tokens"
        ],
        "x-apidog-folder": "Tokens & webhooks/Tokens",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/tokens\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"expires_in_days\":30,\"kind\":\"personal\",\"name\":\"grafana\",\"scopes\":[\"read\"]}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/tokens\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"expires_in_days\": 30,\n    \"kind\": \"personal\",\n    \"name\": \"grafana\",\n    \"scopes\": [\n      \"read\"\n    ]\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/tokens\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"expires_in_days\": 30,\n        \"kind\": \"personal\",\n        \"name\": \"grafana\",\n        \"scopes\": [\n            \"read\",\n        ],\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/tokens\", strings.NewReader(`{\"expires_in_days\":30,\"kind\":\"personal\",\"name\":\"grafana\",\"scopes\":[\"read\"]}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/tokens\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"expires_in_days\": 30,\n          \"kind\": \"personal\",\n          \"name\": \"grafana\",\n          \"scopes\": [\n            \"read\"\n          ]\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "tokens",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/tokens/{token_id}": {
      "delete": {
        "description": "Revoke a token at once; requests with it are 401 from now on. You can revoke your own tokens; admins also the workspace's service tokens. Revoking is permanent (create a new token instead).\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: any signed-in caller manages their own tokens; a new token never gets more access, groups or projects than the request that creates it (a `read` token cannot create tokens, a service token cannot create tokens).\n- Token scope: a token limited to resource groups needs `group:tokens`.\n- Project-limited tokens (`project:<id>`): allowed; results are limited to those projects and anything outside them is 404.\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "revokeToken",
        "parameters": [
          {
            "description": "Token id (`id` of `listTokens`; not the token itself).",
            "example": 58,
            "in": "path",
            "name": "token_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "write"
            ]
          }
        ],
        "summary": "Revoke a token",
        "tags": [
          "Tokens"
        ],
        "x-apidog-folder": "Tokens & webhooks/Tokens",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X DELETE \"https://api.mycve.io/v1/tokens/58\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "write",
        "x-mycve-group": "tokens",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/webhooks": {
      "get": {
        "description": "Outgoing webhook endpoints of the workspace with their subscribed events, whether they are active, and their consecutive failures. Secrets are shown only by prefix.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listWebhooks",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "active": true,
                          "created_at": "2026-10-01T09:30:00Z",
                          "created_by": "ana@acme.dev",
                          "description": "Card processing services and their container images",
                          "disabled_reason": "connection refused",
                          "events": [
                            "scan.completed",
                            "scan.failed"
                          ],
                          "failures": 0,
                          "id": 5,
                          "secret_prefix": "whsec_3fa9c0",
                          "updated_at": "2026-10-01T09:34:12Z",
                          "url": "https://hooks.acme.dev/mycve"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Webhook"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Webhook endpoints of the workspace",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/webhooks?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "read"
      },
      "post": {
        "description": "Add an HTTPS endpoint for some events (empty `events` = all; `ping` is never subscribed, it is sent on request). The signing secret (`whsec_…`) is in the response **once**: store it to verify `Mycve-Signature`. URLs must be `https://` on a public host name (no IP literals, localhost, `.local`, `.internal`, credentials or mycve.io). At most 20 endpoints per workspace.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Idempotency**: send `Idempotency-Key` (a UUID) to retry safely: the same key and body within 24 hours replays the stored response with `Idempotent-Replayed: true`; the same key with a different body is 422 `idempotency_key_reused`; while the first request is still running, 409 `conflict`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The workspace already has the maximum of 20 endpoints.\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "createWebhook",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "all": {
                  "summary": "Every event",
                  "value": {
                    "events": [],
                    "url": "https://hooks.acme.dev/mycve-all"
                  }
                },
                "scans": {
                  "summary": "Scan results only",
                  "value": {
                    "description": "Scan results to the SOC",
                    "events": [
                      "scan.completed",
                      "scan.failed"
                    ],
                    "url": "https://hooks.acme.dev/mycve"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateWebhook"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "secret": "whsec_3fa9c0d1e2b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f30",
                      "webhook": {
                        "active": true,
                        "created_at": "2026-10-01T09:30:00Z",
                        "created_by": "ana@acme.dev",
                        "description": "Scan results to the SOC",
                        "disabled_reason": null,
                        "events": [
                          "scan.completed",
                          "scan.failed"
                        ],
                        "failures": 0,
                        "id": 5,
                        "secret_prefix": "whsec_3fa9c0",
                        "updated_at": "2026-10-01T09:30:00Z",
                        "url": "https://hooks.acme.dev/mycve"
                      }
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            },
            "description": "Created",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The workspace already has the maximum of 20 endpoints."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Add an endpoint (the signing secret is shown once)",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/webhooks\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"description\":\"Scan results to the SOC\",\"events\":[\"scan.completed\",\"scan.failed\"],\"url\":\"https://hooks.acme.dev/mycve\"}'"
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/webhooks\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"description\": \"Scan results to the SOC\",\n    \"events\": [\n      \"scan.completed\",\n      \"scan.failed\"\n    ],\n    \"url\": \"https://hooks.acme.dev/mycve\"\n  }),\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/webhooks\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"description\": \"Scan results to the SOC\",\n        \"events\": [\n            \"scan.completed\",\n            \"scan.failed\",\n        ],\n        \"url\": \"https://hooks.acme.dev/mycve\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/webhooks\", strings.NewReader(`{\"description\":\"Scan results to the SOC\",\"events\":[\"scan.completed\",\"scan.failed\"],\"url\":\"https://hooks.acme.dev/mycve\"}`))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\treq.Header.Set(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/webhooks\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .header(\"Idempotency-Key\", \"4f1c2a0e-8a9b-4c3d-9e2f-1a2b3c4d5e6f\") // unique per logical request\n        .json(&serde_json::json!({\n          \"description\": \"Scan results to the SOC\",\n          \"events\": [\n            \"scan.completed\",\n            \"scan.failed\"\n          ],\n          \"url\": \"https://hooks.acme.dev/mycve\"\n        }))\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/webhooks/{webhook_id}": {
      "delete": {
        "description": "Remove an endpoint and its delivery log. Pending deliveries are dropped.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).",
        "operationId": "deleteWebhook",
        "parameters": [
          {
            "description": "Webhook endpoint id (`id` of `listWebhooks`).",
            "example": 5,
            "in": "path",
            "name": "webhook_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Remove an endpoint and its delivery log",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X DELETE \"https://api.mycve.io/v1/webhooks/5\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      },
      "get": {
        "description": "One endpoint with its events, state and failure count (`disabled_reason` says why it was turned off).\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getWebhook",
        "parameters": [
          {
            "description": "Webhook endpoint id (`id` of `listWebhooks`).",
            "example": 5,
            "in": "path",
            "name": "webhook_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "active": true,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "disabled_reason": "connection refused",
                      "events": [
                        "scan.completed",
                        "scan.failed"
                      ],
                      "failures": 0,
                      "id": 5,
                      "secret_prefix": "whsec_3fa9c0",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "url": "https://hooks.acme.dev/mycve"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Webhook"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "An endpoint",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/webhooks/5\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "read"
      },
      "patch": {
        "description": "Change an endpoint's URL, description or events; absent fields stay. `active: true` re-enables an endpoint disabled after 50 failures in a row and resets the count; `active: false` pauses deliveries.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).",
        "operationId": "updateWebhook",
        "parameters": [
          {
            "description": "Webhook endpoint id (`id` of `listWebhooks`).",
            "example": 5,
            "in": "path",
            "name": "webhook_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "events": {
                  "summary": "Subscribe to more events",
                  "value": {
                    "description": "Scans and new findings",
                    "events": [
                      "scan.completed",
                      "scan.failed",
                      "finding.new"
                    ]
                  }
                },
                "move": {
                  "summary": "Move to a new URL",
                  "value": {
                    "url": "https://hooks.acme.dev/v2/mycve"
                  }
                },
                "reactivate": {
                  "summary": "Re-enable after failures",
                  "value": {
                    "active": true
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhook"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "active": true,
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "description": "Card processing services and their container images",
                      "disabled_reason": "connection refused",
                      "events": [
                        "scan.completed",
                        "scan.failed"
                      ],
                      "failures": 0,
                      "id": 5,
                      "secret_prefix": "whsec_3fa9c0",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "url": "https://hooks.acme.dev/mycve"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Webhook"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Change the URL, events or description; re-activate",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X PATCH \"https://api.mycve.io/v1/webhooks/5\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"active\":true}'"
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/webhooks/{webhook_id}/deliveries": {
      "get": {
        "description": "The delivery log of an endpoint (30 days), newest first: event, status (`pending`, `delivering`, `succeeded`, `failed`, `dead`), attempts, your endpoint's response status and the start of its body, and timing.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "description": "Webhook endpoint id (`id` of `listWebhooks`).",
            "example": 5,
            "in": "path",
            "name": "webhook_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "attempts": 1,
                          "created_at": "2026-10-01T09:30:00Z",
                          "delivery_id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                          "duration_ms": 1840,
                          "event": "scan.completed",
                          "id": 8841,
                          "last_attempt_at": "2026-10-01T09:34:12Z",
                          "next_attempt_at": "2026-12-30T09:30:00Z",
                          "response_excerpt": "response excerpt",
                          "response_status": 200,
                          "status": "succeeded"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1WebhookDeliveryLog"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Delivery log, newest first (30 days)",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/webhooks/5/deliveries?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "read"
      }
    },
    "/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver": {
      "post": {
        "description": "Send a delivery again, with the same id and body (a new timestamp and signature), and reset its retries: for after you fixed your endpoint. A delivery being sent right now cannot be redelivered (404).\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.",
        "operationId": "redeliverWebhook",
        "parameters": [
          {
            "description": "Webhook endpoint id (`id` of `listWebhooks`).",
            "example": 5,
            "in": "path",
            "name": "webhook_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Delivery id: `whd_` + 24 hex (`delivery_id` of `listWebhookDeliveries`, also the `Mycve-Delivery` header).",
            "example": "whd_5e0c2f9a1b3d4c6e7f809a1b",
            "in": "path",
            "name": "delivery_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "attempts": 1,
                      "created_at": "2026-10-01T09:30:00Z",
                      "delivery_id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                      "duration_ms": 1840,
                      "event": "scan.completed",
                      "id": 8841,
                      "last_attempt_at": "2026-10-01T09:34:12Z",
                      "next_attempt_at": "2026-12-30T09:30:00Z",
                      "response_excerpt": "response excerpt",
                      "response_status": 200,
                      "status": "succeeded"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1WebhookDeliveryLog"
                }
              }
            },
            "description": "Accepted: the work continues in the background",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Send a delivery again (resets its retries)",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/webhooks/5/deliveries/whd_5e0c2f9a1b3d4c6e7f809a1b/redeliver\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/webhooks/{webhook_id}/ping": {
      "post": {
        "description": "Queue a `ping` delivery to this endpoint now (202) to test reachability and your signature check; see the result in `listWebhookDeliveries`.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Errors specific to this endpoint**\n\n- `409` `conflict`: The endpoint is disabled: PATCH `active: true` first.",
        "operationId": "pingWebhook",
        "parameters": [
          {
            "description": "Webhook endpoint id (`id` of `listWebhooks`).",
            "example": 5,
            "in": "path",
            "name": "webhook_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "delivery_id": "whd_5e0c2f9a1b3d4c6e7f809a1b"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/PingResult"
                }
              }
            },
            "description": "Accepted: the work continues in the background",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict",
            "description": "The endpoint is disabled: PATCH `active: true` first."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Send a `ping` delivery now",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/webhooks/5/ping\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          },
          {
            "label": "TypeScript (fetch)",
            "lang": "TypeScript",
            "source": "const res = await fetch(\"https://api.mycve.io/v1/webhooks/5/ping\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.MYCVE_TOKEN}`,\n    \"X-Mycve-Workspace\": \"7\",\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);\n}\nconst data = await res.json();\nconsole.log(data);"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mycve.io/v1/webhooks/5/ping\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['MYCVE_TOKEN']}\",\n        \"X-Mycve-Workspace\": \"7\",\n    },\n    timeout=30,\n)\nresp.raise_for_status()  # the body is {\"error\": {\"code\", \"message\", ...}} on failure\nprint(resp.json())"
          },
          {
            "label": "Go (net/http)",
            "lang": "Go",
            "source": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\treq, err := http.NewRequest(\"POST\", \"https://api.mycve.io/v1/webhooks/5/ping\", nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"MYCVE_TOKEN\"))\n\treq.Header.Set(\"X-Mycve-Workspace\", \"7\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer resp.Body.Close()\n\tout, _ := io.ReadAll(resp.Body)\n\tfmt.Println(resp.Status, string(out))\n}"
          },
          {
            "label": "Rust (reqwest)",
            "lang": "Rust",
            "source": "// Cargo.toml: reqwest = { version = \"0.12\", features = [\"json\"] }, serde_json = \"1\",\n// tokio = { version = \"1\", features = [\"full\"] }\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let resp = reqwest::Client::new()\n        .post(\"https://api.mycve.io/v1/webhooks/5/ping\")\n        .bearer_auth(std::env::var(\"MYCVE_TOKEN\")?)\n        .header(\"X-Mycve-Workspace\", \"7\")\n        .send()\n        .await?;\n    println!(\"{} {}\", resp.status(), resp.text().await?);\n    Ok(())\n}"
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/webhooks/{webhook_id}/rotate-secret": {
      "post": {
        "description": "Make a new signing secret (shown once). For 24 hours every delivery carries two signatures, `v1=` with the new and `v1=` with the old secret, so you can roll the secret on the receiver without dropping deliveries.\n\n**Permissions**\n\n- Minimum workspace role: `admin` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:webhooks`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Errors specific to this endpoint**\n\n- `503` `unavailable`: The queue or another binding is unreachable (or the database): nothing was created; retry shortly.",
        "operationId": "rotateWebhookSecret",
        "parameters": [
          {
            "description": "Webhook endpoint id (`id` of `listWebhooks`).",
            "example": 5,
            "in": "path",
            "name": "webhook_id",
            "required": true,
            "schema": {
              "format": "int64",
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Workspace"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "secret": "whsec_3fa9c0d1e2b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f30",
                      "webhook": {
                        "active": true,
                        "created_at": "2026-10-01T09:30:00Z",
                        "created_by": "ana@acme.dev",
                        "description": "Card processing services and their container images",
                        "disabled_reason": "connection refused",
                        "events": [
                          "scan.completed",
                          "scan.failed"
                        ],
                        "failures": 0,
                        "id": 5,
                        "secret_prefix": "whsec_3fa9c0",
                        "updated_at": "2026-10-01T09:34:12Z",
                        "url": "https://hooks.acme.dev/mycve"
                      }
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable",
            "description": "The queue or another binding is unreachable (or the database): nothing was created; retry shortly."
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "New signing secret; the old one keeps signing for 24 hours",
        "tags": [
          "Webhooks"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhooks",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X POST \"https://api.mycve.io/v1/webhooks/5/rotate-secret\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "webhooks",
        "x-mycve-min-role": "admin",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/workspace": {
      "get": {
        "description": "The workspace this request acts in (from `X-Mycve-Workspace`, the token's workspace, or your personal one), with your role in it and its member count.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "getWorkspace",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "created_at": "2026-10-01T09:30:00Z",
                      "id": 7,
                      "members": 6,
                      "name": "Payments",
                      "personal": false,
                      "role": "member"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Workspace"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "The workspace this request acts in",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/workspace\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      },
      "patch": {
        "description": "Rename the workspace. Owners only. A name has 1 to 80 characters (trimmed) and no control characters; an invalid name is 400.\n\n**Permissions**\n\n- Minimum workspace role: `owner` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `admin` (tokens with `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `write`, 120 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Concurrency**: send `If-Match` with the `ETag` of the `GET` of the same path to change it only if nobody else did since you read it (412 `precondition_failed` otherwise).",
        "operationId": "updateWorkspace",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "rename": {
                  "summary": "Rename",
                  "value": {
                    "name": "Acme Security"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/UpdateWorkspace"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "created_at": "2026-10-01T09:30:00Z",
                      "id": 7,
                      "members": 6,
                      "name": "Payments",
                      "personal": false,
                      "role": "member"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/V1Workspace"
                }
              }
            },
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "admin"
            ]
          }
        ],
        "summary": "Rename the workspace (owners)",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -X PATCH \"https://api.mycve.io/v1/workspace\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Acme Security\"}'"
          }
        ],
        "x-mycve-access": "admin",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "owner",
        "x-mycve-rate-bucket": "write"
      }
    },
    "/workspaces": {
      "get": {
        "description": "The workspaces you belong to, with your role in each: use an `id` as `X-Mycve-Workspace`. A service token sees only its own workspace, and a token pinned to a workspace only that one.\n\n**Permissions**\n\n- Minimum workspace role: `viewer` (browser sessions and personal tokens; a personal token is also capped by its owner's current role).\n- Token access: `read` (tokens with `read`, `write` or `admin`).\n- Token scope: a token limited to resource groups needs `group:workspaces`.\n- Project-limited tokens (`project:<id>`): 403 (this route is not project-scoped).\n\n**Rate limit**: bucket `read`, 1200 requests per 60-second window per token (or user) and workspace; every response carries `RateLimit-*` headers, and 429 comes with `Retry-After`.\n\n**Pagination**: returns `{\"data\": [...], \"next_cursor\": ...}`. Pass `next_cursor` back as `cursor` until it is `null`; `limit` is 1 to 200 (default 50). There is no total count.\n\n**Caching**: the response has a weak `ETag`; send it back in `If-None-Match` to get `304 Not Modified` with no body when nothing changed.",
        "operationId": "listWorkspaces",
        "parameters": [
          {
            "$ref": "#/components/parameters/Workspace"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "A typical response",
                    "value": {
                      "data": [
                        {
                          "created_at": "2026-10-01T09:30:00Z",
                          "id": 7,
                          "members": 6,
                          "name": "Payments",
                          "personal": false,
                          "role": "member"
                        }
                      ],
                      "next_cursor": "next cursor"
                    }
                  }
                },
                "schema": {
                  "description": "One page of a list. Lists are keyset-paginated and stable under inserts; there is no total count.",
                  "properties": {
                    "data": {
                      "description": "The items of this page (at most `limit`).",
                      "items": {
                        "$ref": "#/components/schemas/V1Workspace"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass as `cursor` to get the next page; null on the last page. Opaque: do not parse or build it.",
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "bearer": [
              "read"
            ]
          }
        ],
        "summary": "Workspaces you belong to (a service token: its own)",
        "tags": [
          "Workspaces"
        ],
        "x-apidog-folder": "Account & workspaces/Workspaces",
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl \"https://api.mycve.io/v1/workspaces?limit=50\" \\\n  -H \"Authorization: Bearer $MYCVE_TOKEN\" \\\n  -H \"X-Mycve-Workspace: 7\""
          }
        ],
        "x-mycve-access": "read",
        "x-mycve-group": "workspaces",
        "x-mycve-min-role": "viewer",
        "x-mycve-rate-bucket": "read"
      }
    }
  },
  "security": [
    {
      "bearer": []
    }
  ],
  "servers": [
    {
      "description": "API host: bearer tokens only (no browser session)",
      "url": "https://api.mycve.io/v1"
    },
    {
      "description": "Website mount: the signed-in browser session (Cloudflare Access), or a token",
      "url": "https://mycve.io/api/v1"
    }
  ],
  "tags": [
    {
      "description": "Who is calling (`/me`), your analytics preference, service health and this document.",
      "name": "Meta",
      "x-mycve-group": "meta"
    },
    {
      "description": "The workspaces you belong to, the active workspace, its members and their roles, invitations and teams. Choose the workspace of a request with `X-Mycve-Workspace`.",
      "name": "Workspaces",
      "x-mycve-group": "workspaces"
    },
    {
      "description": "A project is one reference of code: a repository and the images it builds, just a repository, just an image, or just infrastructure (\"Payments\", \"Web shop\"), each with its own dashboard, risk and reports. Assets can be added by id or by kind and target before their first scan. Suggestions propose projects from the infrastructure graph. The `/products` and `/product-suggestions` routes are deprecated aliases.",
      "name": "Projects",
      "x-mycve-group": "projects"
    },
    {
      "description": "The infrastructure inventory: repositories, images, base images, registries, cloud projects and uploaded folders, with their risk and scan coverage; IaC posture (misconfigurations) and image-tag drift.",
      "name": "Assets",
      "x-mycve-group": "assets"
    },
    {
      "description": "Scans of a Git repository or a container image: start one, follow it, and read its findings and package inventory. Folders are scanned with `cve scan path` (CLI).",
      "name": "Scans",
      "x-mycve-group": "scans"
    },
    {
      "description": "Open findings across the workspace: the latest finished scan of every target, highest score first.",
      "name": "Findings",
      "x-mycve-group": "findings"
    },
    {
      "description": "Search the CVE database and read one CVE: description, CVSS metrics, CWEs, affected versions and references.",
      "name": "CVEs",
      "x-mycve-group": "cves"
    },
    {
      "description": "Workspace, project and portfolio reports, built in the background (`createReport` returns a job), frozen when built, downloadable as HTML, Markdown, CSV or JSON.",
      "name": "Reports",
      "x-mycve-group": "reports"
    },
    {
      "description": "Daily security rollups, KPIs, the dependency flow (asset to package to CVE to severity), usage per feature area, and usage events sent by clients.",
      "name": "Analytics",
      "x-mycve-group": "analytics"
    },
    {
      "description": "`cve.yml` pipeline runs of connected repositories, and the jobs of the job system (scans, fixes, syncs, pipeline jobs, report builds).",
      "name": "Pipelines",
      "x-mycve-group": "pipelines"
    },
    {
      "description": "Automatic fix runs: dependency upgrades for a scan's findings, opened as a pull request, a branch, or a dry run.",
      "name": "Fixes",
      "x-mycve-group": "fixes"
    },
    {
      "description": "Connected provider accounts (GitHub, GitLab, registries, cloud projects). Secrets are never returned.",
      "name": "Connectors",
      "x-mycve-group": "connectors"
    },
    {
      "description": "Leaked-secret incidents from GitGuardian, mapped to the workspace's repositories.",
      "name": "Secrets",
      "x-mycve-group": "secrets"
    },
    {
      "description": "Your notifications in the workspace.",
      "name": "Notifications",
      "x-mycve-group": "notifications"
    },
    {
      "description": "Secure-by-default infrastructure templates (Terraform, Kubernetes, Helm, Dockerfiles, CI, `cve.yml`): browse, render, or open them as a pull request.",
      "name": "Templates",
      "x-mycve-group": "templates"
    },
    {
      "description": "Personal access tokens and workspace service tokens. A token is shown once, when it is created.",
      "name": "Tokens",
      "x-mycve-group": "tokens"
    },
    {
      "description": "Outgoing webhook endpoints of the workspace (admins): add, update, rotate the signing secret, ping, and read the delivery log. The events and their payloads are under **Webhook events**.",
      "name": "Webhooks",
      "x-mycve-group": "webhooks"
    },
    {
      "description": "Signed `POST` requests mycve sends to your HTTPS endpoints (managed with the **Webhooks** endpoints). Every delivery has the `WebhookDelivery` envelope (`id`, `event`, `workspace_id`, `created_at`, `data`) and the headers `Mycve-Event`, `Mycve-Delivery` and `Mycve-Signature`.",
      "name": "Webhook events"
    }
  ],
  "webhooks": {
    "connector.attention": {
      "post": {
        "description": "A connector needs attention: its credentials expired or were revoked, or syncs keep failing. `data` is the connector (`getConnector` shape); reconnect it on the website.\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onConnectorAttention",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A connector.attention delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "account": "acme",
                      "auth": "auth",
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_via": "created via",
                      "fixes_enabled": false,
                      "id": 3,
                      "last_sync": "2026-10-01T09:30:00Z",
                      "note": "note",
                      "provider": "github",
                      "scopes": [
                        "write",
                        "group:scans"
                      ],
                      "status": "done"
                    },
                    "event": "connector.attention",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1Connector"
                      },
                      "event": {
                        "const": "connector.attention"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "connector.attention event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "cve.affects_project": {
      "post": {
        "description": "A newly published or updated CVE affects assets of one of your projects. `data` has the CVE, the project and the affected asset ids.\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onCveAffectsProject",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A cve.affects_project delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "asset_ids": [
                        "repo-3fa9c0d1e2b4",
                        "img-8b1e4f2a9c3d"
                      ],
                      "cve": {
                        "cve_id": "CVE-2024-3094",
                        "date_published": "2026-10-01T09:30:00Z",
                        "date_updated": "2026-10-01T09:30:00Z",
                        "in_stack": 3,
                        "score": 9.8,
                        "severity": "HIGH",
                        "state": "done",
                        "title": "Payments API"
                      },
                      "project": {
                        "asset_count": 3,
                        "at_risk": false,
                        "auto": false,
                        "color_slot": 3,
                        "coverage": 0.82,
                        "created_at": "2026-10-01T09:30:00Z",
                        "created_by": "ana@acme.dev",
                        "description": "Card processing services and their container images",
                        "id": 12,
                        "name": "Payments",
                        "open": {
                          "critical": 2,
                          "high": 5,
                          "low": 4,
                          "medium": 11,
                          "none": 0
                        },
                        "shape": "empty",
                        "slug": "payments",
                        "updated_at": "2026-10-01T09:34:12Z",
                        "workspace_id": 7
                      }
                    },
                    "event": "cve.affects_project",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "properties": {
                          "asset_ids": {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          "cve": {
                            "$ref": "#/components/schemas/V1Cve"
                          },
                          "project": {
                            "$ref": "#/components/schemas/Project"
                          }
                        },
                        "required": [
                          "cve",
                          "project",
                          "asset_ids"
                        ],
                        "type": "object"
                      },
                      "event": {
                        "const": "cve.affects_project"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "cve.affects_project event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "finding.new": {
      "post": {
        "description": "A finding appeared that the previous scan of the same target did not have. `data` is the open finding (`listFindings` shape).\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onFindingNew",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A finding.new delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "finding": {
                        "confidence": "confidence",
                        "cve_id": "CVE-2024-3094",
                        "ecosystem": "npm",
                        "package": "openssl",
                        "score": 9.8,
                        "severity": "HIGH",
                        "source": "manual",
                        "title": "Payments API",
                        "version": "3.0.13"
                      },
                      "kind": "git",
                      "scan_id": 4812,
                      "scanned_at": "2026-10-01T09:30:00Z",
                      "target": "https://github.com/acme/payments-api"
                    },
                    "event": "finding.new",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1OpenFinding"
                      },
                      "event": {
                        "const": "finding.new"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "finding.new event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "finding.resolved": {
      "post": {
        "description": "A finding of the previous scan of a target is gone (upgraded, removed or fixed). `data` is the finding as it was.\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onFindingResolved",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A finding.resolved delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "finding": {
                        "confidence": "confidence",
                        "cve_id": "CVE-2024-3094",
                        "ecosystem": "npm",
                        "package": "openssl",
                        "score": 9.8,
                        "severity": "HIGH",
                        "source": "manual",
                        "title": "Payments API",
                        "version": "3.0.13"
                      },
                      "kind": "git",
                      "scan_id": 4812,
                      "scanned_at": "2026-10-01T09:30:00Z",
                      "target": "https://github.com/acme/payments-api"
                    },
                    "event": "finding.resolved",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1OpenFinding"
                      },
                      "event": {
                        "const": "finding.resolved"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "finding.resolved event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "fix_run.finished": {
      "post": {
        "description": "A fix run finished: `done` (with `pr_url` when it opened a pull request), `no_changes` or `failed`. `data` is the run (`getFix` shape).\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onFixRunFinished",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A fix_run.finished delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "ai": false,
                      "branch": "mycve/fix-4812",
                      "changes": null,
                      "created_at": "2026-10-01T09:30:00Z",
                      "error": "connection refused",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 77,
                      "kind": "git",
                      "mode": "pr",
                      "pr_url": "https://github.com/acme/payments-api/pull/318",
                      "scan_id": 4812,
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api",
                      "trigger": "manual"
                    },
                    "event": "fix_run.finished",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1FixRun"
                      },
                      "event": {
                        "const": "fix_run.finished"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "fix_run.finished event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "ping": {
      "post": {
        "description": "A test delivery sent by `pingWebhook`, to this endpoint only. Never subscribed to; answer 2xx.\n\n**Status**: delivered by this deployment.\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onPing",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A ping delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "events": [
                        "scan.completed",
                        "scan.failed"
                      ],
                      "url": "https://hooks.acme.dev/mycve",
                      "webhook_id": 5
                    },
                    "event": "ping",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "properties": {
                          "events": {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          "url": {
                            "format": "uri",
                            "type": "string"
                          },
                          "webhook_id": {
                            "format": "int64",
                            "type": "integer"
                          }
                        },
                        "required": [
                          "webhook_id",
                          "url",
                          "events"
                        ],
                        "type": "object"
                      },
                      "event": {
                        "const": "ping"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "ping event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "pipeline_run.finished": {
      "post": {
        "description": "A pipeline run finished (`success`, `failed`, `canceled` or `skipped`). `data` is the run with its jobs (`getPipelineRun` shape).\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onPipelineRunFinished",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A pipeline_run.finished delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "created_at": "2026-10-01T09:30:00Z",
                      "created_by": "ana@acme.dev",
                      "error": "connection refused",
                      "file": "cve.yml",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 905,
                      "jobs": [
                        {
                          "allow_failure": false,
                          "attempt": 3,
                          "duration_ms": 1840,
                          "error": "connection refused",
                          "exit_code": 3,
                          "findings_summary": {
                            "critical": 2,
                            "high": 5,
                            "low": 4,
                            "medium": 11,
                            "none": 0
                          },
                          "finished_at": "2026-10-01T09:34:12Z",
                          "id": 101,
                          "name": "Payments",
                          "needs": [
                            "need"
                          ],
                          "stage": "stage",
                          "started_at": "2026-10-01T09:30:00Z",
                          "status": "done",
                          "uses": "uses"
                        }
                      ],
                      "name": "Payments",
                      "ref": "main",
                      "sha": "9fceb02d0ae598e95dc970b74767f19372d61af8",
                      "source": "manual",
                      "started_at": "2026-10-01T09:30:00Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api"
                    },
                    "event": "pipeline_run.finished",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1PipelineRun"
                      },
                      "event": {
                        "const": "pipeline_run.finished"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "pipeline_run.finished event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "report.ready": {
      "post": {
        "description": "A report finished building (from `createReport`, a schedule or the monthly run). `data` is the report as listed by `listReports`; download it with `downloadReport`.\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onReportReady",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A report.ready delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "created_at": "2026-10-01T09:30:00Z",
                      "id": 214,
                      "job_id": 3120,
                      "new_findings": 3,
                      "open": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "period_end": "2026-10-01T00:00:00Z",
                      "period_start": "2026-09-01T00:00:00Z",
                      "project_ids": [
                        12
                      ],
                      "resolved": 3,
                      "scope": "workspace",
                      "title": "Payments API",
                      "trigger": "manual"
                    },
                    "event": "report.ready",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/ReportListItem"
                      },
                      "event": {
                        "const": "report.ready"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "report.ready event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "scan.completed": {
      "post": {
        "description": "A scan finished successfully. `data` is the scan (`getScan` shape) with its finding counts; fetch `listScanFindings` for the findings.\n\n**Status**: delivered by this deployment.\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onScanCompleted",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A scan.completed delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "counts": {
                        "critical": 2,
                        "high": 5,
                        "low": 4,
                        "medium": 11,
                        "none": 0
                      },
                      "created_at": "2026-10-01T09:30:00Z",
                      "error": null,
                      "finding_count": 22,
                      "findings_url": "/v1/scans/4812/findings",
                      "finished_at": "2026-10-01T09:34:12Z",
                      "id": 4812,
                      "kind": "git",
                      "package_count": 412,
                      "profile": null,
                      "started_at": "2026-10-01T09:30:04Z",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api",
                      "tasks_url": "/v1/scans/4812/tasks",
                      "watch_id": null
                    },
                    "event": "scan.completed",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1Scan"
                      },
                      "event": {
                        "const": "scan.completed"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "scan.completed event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "scan.failed": {
      "post": {
        "description": "A scan failed (the repository or image could not be fetched, the scanner crashed, it timed out). `data` is the scan with `error`.\n\n**Status**: delivered by this deployment.\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onScanFailed",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A scan.failed delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "counts": {
                        "critical": 0,
                        "high": 0,
                        "low": 0,
                        "medium": 0,
                        "none": 0
                      },
                      "created_at": "2026-10-01T09:30:00Z",
                      "error": "manifest unknown: ghcr.io/acme/payments-api:1.4.3",
                      "finding_count": 0,
                      "findings_url": "/v1/scans/4813/findings",
                      "finished_at": "2026-10-01T09:30:21Z",
                      "id": 4813,
                      "kind": "image",
                      "package_count": 0,
                      "profile": null,
                      "started_at": "2026-10-01T09:30:04Z",
                      "status": "failed",
                      "target": "ghcr.io/acme/payments-api:1.4.3",
                      "tasks_url": "/v1/scans/4813/tasks",
                      "watch_id": null
                    },
                    "event": "scan.failed",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1Scan"
                      },
                      "event": {
                        "const": "scan.failed"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "scan.failed event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    },
    "secret.incident": {
      "post": {
        "description": "GitGuardian reported a new leaked-secret incident in one of the workspace's repositories, or a known one became valid again. `data` is the incident (`listSecretIncidents` shape).\n\n**Status**: subscribable now; delivered once the module that owns it emits it (subscribe early, deliveries start without a change on your side).\n\n**Verifying a delivery**\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Split `Mycve-Signature` on `,`: `t=<unix seconds>` and one or more `v1=<hex>` (two during the 24 hours after a secret rotation).\n3. Reject the delivery if `t` is more than 300 seconds from your clock (replay protection).\n4. Compute `hex(HMAC-SHA256(secret, \"<t>.\" + raw body))` with the endpoint's `whsec_…` secret.\n5. Accept if it equals any `v1` value, compared in constant time.\n6. Dedupe on `Mycve-Delivery` (`whd_…`): a retry carries the same id and body (with a new `t` and signature).\n\nAnswer 2xx within 10 seconds and do the work afterwards.",
        "operationId": "onSecretIncident",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "A secret.incident delivery",
                  "value": {
                    "created_at": "2026-10-01T09:34:12Z",
                    "data": {
                      "connector_id": 3,
                      "detector": "AWS Keys",
                      "first_seen": "2026-10-01T09:30:00Z",
                      "gitguardian_url": "https://dashboard.gitguardian.com/workspace/1/incidents/6021",
                      "id": 6021,
                      "last_seen": "2026-10-01T09:30:00Z",
                      "occurrences": 3,
                      "severity": "high",
                      "source": "manual",
                      "status": "done",
                      "target": "https://github.com/acme/payments-api",
                      "updated_at": "2026-10-01T09:34:12Z",
                      "validity": "validity"
                    },
                    "event": "secret.incident",
                    "id": "whd_5e0c2f9a1b3d4c6e7f809a1b",
                    "workspace_id": 7
                  }
                }
              },
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  {
                    "properties": {
                      "data": {
                        "$ref": "#/components/schemas/V1SecretIncident"
                      },
                      "event": {
                        "const": "secret.incident"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "2XX": {
            "description": "Any 2xx within 10 seconds acknowledges the delivery. Answer fast and do the work afterwards."
          },
          "default": {
            "description": "Anything else (3xx included: redirects are not followed, timeouts too) is a failure: retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; 50 failures in a row disable the endpoint."
          }
        },
        "security": [],
        "summary": "secret.incident event",
        "tags": [
          "Webhook events"
        ],
        "x-apidog-folder": "Tokens & webhooks/Webhook events",
        "x-codeSamples": [
          {
            "label": "Verify (TypeScript, Node)",
            "lang": "TypeScript",
            "source": "import crypto from \"node:crypto\";\n\n// rawBody: the exact bytes received (verify before parsing the JSON)\nexport function verifyMycveSignature(secret: string, header: string, rawBody: Buffer, toleranceSecs = 300): boolean {\n  let t = 0;\n  const sigs: string[] = [];\n  for (const part of header.split(\",\")) {\n    const [k, v] = part.trim().split(\"=\", 2);\n    if (k === \"t\") t = Number(v);\n    if (k === \"v1\" && v) sigs.push(v);\n  }\n  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false;\n  const want = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return sigs.some((s) => {\n    const got = Buffer.from(s, \"hex\");\n    return got.length === want.length && crypto.timingSafeEqual(got, want);\n  });\n}"
          },
          {
            "label": "Verify (Python)",
            "lang": "Python",
            "source": "import hashlib\nimport hmac\nimport time\n\n\ndef verify_mycve_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:\n    t, sigs = None, []\n    for part in header.split(\",\"):\n        k, _, v = part.strip().partition(\"=\")\n        if k == \"t\" and v.isdigit():\n            t = int(v)\n        elif k == \"v1\" and v:\n            sigs.append(v)\n    if t is None or abs(time.time() - t) > tolerance:\n        return False\n    want = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(want, s) for s in sigs)"
          },
          {
            "label": "Verify (Go)",
            "lang": "Go",
            "source": "package webhooks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// VerifyMycveSignature checks a Mycve-Signature header against the raw body.\nfunc VerifyMycveSignature(secret, header string, rawBody []byte, now time.Time) bool {\n\tvar t int64\n\tvar sigs []string\n\tfor _, part := range strings.Split(header, \",\") {\n\t\tk, v, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n\t\tswitch k {\n\t\tcase \"t\":\n\t\t\tt, _ = strconv.ParseInt(v, 10, 64)\n\t\tcase \"v1\":\n\t\t\tsigs = append(sigs, v)\n\t\t}\n\t}\n\tif d := now.Unix() - t; t == 0 || d > 300 || d < -300 {\n\t\treturn false\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(strconv.FormatInt(t, 10) + \".\"))\n\tmac.Write(rawBody)\n\twant := hex.EncodeToString(mac.Sum(nil))\n\tfor _, s := range sigs {\n\t\tif hmac.Equal([]byte(s), []byte(want)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}"
          },
          {
            "label": "Verify (Rust)",
            "lang": "Rust",
            "source": "// Cargo.toml: hmac = \"0.12\", sha2 = \"0.10\", hex = \"0.4\"\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\npub fn verify_mycve_signature(secret: &str, header: &str, raw_body: &[u8], now_unix: i64) -> bool {\n    let (mut t, mut sigs) = (None, Vec::new());\n    for part in header.split(',') {\n        match part.trim().split_once('=') {\n            Some((\"t\", v)) => t = v.parse::<i64>().ok(),\n            Some((\"v1\", v)) => sigs.push(v.to_owned()),\n            _ => {}\n        }\n    }\n    let Some(t) = t else { return false };\n    if (now_unix - t).abs() > 300 {\n        return false;\n    }\n    sigs.iter().any(|s| {\n        let Ok(got) = hex::decode(s) else { return false };\n        let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).expect(\"any key length\");\n        mac.update(format!(\"{t}.\").as_bytes());\n        mac.update(raw_body);\n        mac.verify_slice(&got).is_ok() // constant time\n    })\n}"
          },
          {
            "label": "Verify (openssl)",
            "lang": "Shell",
            "source": "# body.json: the raw body as received; SIG: the Mycve-Signature header\nt=$(printf '%s' \"$SIG\" | tr ',' '\\n' | sed -n 's/^t=//p')\nwant=$( (printf '%s.' \"$t\"; cat body.json) | openssl dgst -sha256 -hmac \"$MYCVE_WEBHOOK_SECRET\" -hex | sed 's/^.* //')\nprintf '%s' \"$SIG\" | tr ',' '\\n' | grep -qx \"v1=$want\" && echo valid || echo INVALID"
          }
        ]
      }
    }
  },
  "x-tagGroups": [
    {
      "name": "Account & workspaces",
      "tags": [
        "Workspaces",
        "Notifications"
      ]
    },
    {
      "name": "Projects",
      "tags": [
        "Projects"
      ]
    },
    {
      "name": "Assets & infrastructure",
      "tags": [
        "Assets"
      ]
    },
    {
      "name": "Scans & findings",
      "tags": [
        "Scans",
        "Findings"
      ]
    },
    {
      "name": "CVEs",
      "tags": [
        "CVEs"
      ]
    },
    {
      "name": "Pipelines",
      "tags": [
        "Pipelines"
      ]
    },
    {
      "name": "Fixes",
      "tags": [
        "Fixes"
      ]
    },
    {
      "name": "Integrations",
      "tags": [
        "Connectors"
      ]
    },
    {
      "name": "Secrets",
      "tags": [
        "Secrets"
      ]
    },
    {
      "name": "Reports & analytics",
      "tags": [
        "Reports",
        "Analytics"
      ]
    },
    {
      "name": "Templates",
      "tags": [
        "Templates"
      ]
    },
    {
      "name": "Tokens & webhooks",
      "tags": [
        "Tokens",
        "Webhooks",
        "Webhook events"
      ]
    },
    {
      "name": "Meta",
      "tags": [
        "Meta"
      ]
    }
  ]
}
