From e4d350db948c5491186ef563774b8b38cdc94d72 Mon Sep 17 00:00:00 2001 From: Gabriel Lopes <1308394+gaprl@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:14:39 -0700 Subject: [PATCH 01/27] feat(api-reference): add auto-generated API changelog pages Add a per-version API changelog (api-reference/v1/Changelog, api-reference/v2/Changelog) generated from the OpenAPI specs' git history with oasdiff. One block per day the spec changed, rendered as Stripe-style tables: a colored change-verb chip, the oasdiff description, and the endpoint as a method Badge linking to its generated reference page. Repetitive changes (enum values, format flips) are coalesced into single rows, and rss: true gives subscribers a feed per API version. The page fully regenerates on every run, so the backfill is retroactive (v1 to 2026-05-28, v2 to 2026-07-14) and self-correcting. Endpoint links are built only from the current spec, so removed endpoints render unlinked instead of 404ing. The update-openapi-specs cron now installs a checksum-pinned oasdiff, regenerates both pages after the mirror and sort steps, and ships them in the same signed-commit PR as the spec change. --- .github/workflows/update-openapi-specs.yml | 46 +- docs/api-reference/v1/Changelog.mdx | 113 +++ docs/api-reference/v2/Changelog.mdx | 125 +++ docs/docs.json | 6 +- scripts/generate_api_changelog.py | 601 +++++++++++++++ scripts/tests/fixtures/changelog_empty.json | 1 + .../tests/fixtures/changelog_info_only.json | 12 + .../fixtures/changelog_mixed_levels.json | 66 ++ scripts/tests/test_generate_api_changelog.py | 711 ++++++++++++++++++ 9 files changed, 1674 insertions(+), 7 deletions(-) create mode 100644 docs/api-reference/v1/Changelog.mdx create mode 100644 docs/api-reference/v2/Changelog.mdx create mode 100644 scripts/generate_api_changelog.py create mode 100644 scripts/tests/fixtures/changelog_empty.json create mode 100644 scripts/tests/fixtures/changelog_info_only.json create mode 100644 scripts/tests/fixtures/changelog_mixed_levels.json create mode 100644 scripts/tests/test_generate_api_changelog.py diff --git a/.github/workflows/update-openapi-specs.yml b/.github/workflows/update-openapi-specs.yml index 097bbeb62e..45010cf2de 100644 --- a/.github/workflows/update-openapi-specs.yml +++ b/.github/workflows/update-openapi-specs.yml @@ -22,7 +22,7 @@ jobs: update-specs: name: Fetch specs and open PR if changed runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: # Runs as the semgrep-docs-release App rather than GITHUB_TOKEN for two # reasons: App-authored PRs trigger Docs CI (GITHUB_TOKEN-authored ones do @@ -39,6 +39,9 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: token: ${{ steps.generate-token.outputs.token }} + # Full history: the changelog generator diffs the specs across git + # revisions, which oasdiff reads with `git show` under the hood. + fetch-depth: 0 - name: Fetch latest specs from semgrep.dev run: | @@ -67,13 +70,40 @@ jobs: # Upstream emits no `servers:` block, and Mintlify has no docs.json # override for OpenAPI-generated pages, so without this the playground - # renders https://api.example.com and cannot send requests. Runs last: - # the two steps above parse `paths:` and `tags:`, and neither needs to - # see the block this adds. Safe to drop once semgrep-app emits `servers` - # natively -- this step no-ops rather than overwriting it. + # renders https://api.example.com and cannot send requests. Runs after + # the two steps above, which parse `paths:` and `tags:` and neither of + # which needs to see the block this adds. Safe to drop once semgrep-app + # emits `servers` natively -- this step no-ops rather than overwriting it. - name: Add the API base URL Mintlify's playground needs run: uv run scripts/add_openapi_servers.py docs/public_v1.openapi.yaml docs/public_v2.openapi.yaml + # Checksummed binary download rather than a third-party action, per the + # supply-chain pinning policy. Keep the version pinned: bumping oasdiff + # regenerates every changelog entry, which belongs in its own PR. + - name: Install oasdiff (pinned) + run: | + curl --fail --silent --show-error --location --retry 3 \ + --output /tmp/oasdiff.tar.gz \ + https://github.com/oasdiff/oasdiff/releases/download/v1.28.0/oasdiff_1.28.0_linux_amd64.tar.gz + echo "e0ef076f2cf953d922addc04be9c3851cf3ec18f7678d2b94d44cea23dca51b5 /tmp/oasdiff.tar.gz" | sha256sum --check + tar -xzf /tmp/oasdiff.tar.gz -C /tmp oasdiff + sudo install /tmp/oasdiff /usr/local/bin/oasdiff + + # Must run after the mirror, sort, and servers steps so the working-tree + # specs being diffed are in their final committed form. Runs before + # validate so the generated MDX is validated too. + - name: Regenerate API changelogs + run: | + uv run scripts/generate_api_changelog.py docs/public_v1.openapi.yaml \ + docs/api-reference/v1/Changelog.mdx \ + --api-name "Semgrep API v1" --api-href /api-reference/v1/Introduction \ + --link-base /api-reference/v1 + uv run scripts/generate_api_changelog.py docs/public_v2.openapi.yaml \ + docs/api-reference/v2/Changelog.mdx \ + --api-name "Semgrep API v2" --api-href /api-reference/v2/Introduction \ + --link-base /api-reference/v2 \ + --note "The v2 API is experimental; breaking changes are expected while it is in alpha." + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 22 @@ -92,6 +122,8 @@ jobs: add-paths: | docs/public_v1.openapi.yaml docs/public_v2.openapi.yaml + docs/api-reference/v1/Changelog.mdx + docs/api-reference/v2/Changelog.mdx branch: chore/update-openapi-specs delete-branch: true # Re-issued on every run, not just when the PR is first opened. That @@ -113,3 +145,7 @@ jobs: - `docs/public_v1.openapi.yaml` ← https://semgrep.dev/api/v1/public_v1.openapi.yaml - `docs/public_v2.openapi.yaml` ← https://semgrep.dev/api/v2/openapi.yaml + + The API changelog pages under `docs/api-reference/*/Changelog.mdx` + are regenerated from the spec's git history by + `scripts/generate_api_changelog.py`. diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx new file mode 100644 index 0000000000..9116aced58 --- /dev/null +++ b/docs/api-reference/v1/Changelog.mdx @@ -0,0 +1,113 @@ +--- +title: "Changelog" +description: "Changes to the Semgrep API v1, generated from its OpenAPI specification." +rss: true +--- + +{/* Auto-generated by scripts/generate_api_changelog.py -- do not edit by hand. */} + +Changes to the [Semgrep API v1](/api-reference/v1/Introduction), detected by +comparing successive versions of its OpenAPI specification. Breaking changes +are labeled; documentation-only edits are not listed. + + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | +| Deprecated | endpoint deprecated | GET [`/api/v1/deployments/{deploymentId}/policies`](/api-reference/v1/policiesservice/list-policies) | +| Deprecated | endpoint deprecated | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | +| Deprecated | endpoint deprecated | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | + + + +## Breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Changed | the `deployments/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments`](/api-reference/v1/deploymentsservice/list-deployments) | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Changed | the `dependencyFilter/repositoryId/items/` request property `format` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Changed | the `format` of the request properties `cursor`, `deploymentId` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Changed | the `cursor` response's property `format` changed from `uint64` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | +| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | +| Changed | the `format` of the request properties `cursor`, `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | +| Changed | the `format` of the response properties `cursor`, `repositorySummaries/items/id`, `repositorySummaries/items/numDependencies` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | +| Changed | for the `path` request parameters `deploymentId`, `repositoryId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | +| Changed | the `format` of the request properties `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | +| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | +| Changed | the `lockfileSummaries/items/numDependencies` response's property `format` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/policies`](/api-reference/v1/policiesservice/list-policies) | +| Changed | the `policies/items/id` response's property `format` changed from `uint64` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentId}/policies`](/api-reference/v1/policiesservice/list-policies) | +| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | +| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | +| Changed | the `format` of the response properties `policy/id`, `rules/items/id` changed from `uint64` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | +| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | +| Changed | the `format` of the request properties `deploymentId`, `policyId` changed from `uint64` to `int64` | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | +| Changed | the `format` of the response properties `policyId`, `updatedRule/id` changed from `uint64` to `int64` for status `200` | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | +| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | +| Changed | for the `path` request parameters `deploymentId`, `scanId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/scan/{scanId}`](/api-reference/v1/scansservice/get-scan-details) | +| Changed | the `format` of the response properties `deployment_id`, `exit_code`, `id`, `repository_id` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentId}/scan/{scanId}`](/api-reference/v1/scansservice/get-scan-details) | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | +| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | +| Changed | the `format` of the response properties `scans/items/deployment_id`, `scans/items/findings_counts/code`, `scans/items/findings_counts/secrets`, `scans/items/findings_counts/supply_chain`, `scans/items/findings_counts/total`, `scans/items/id`, `scans/items/repository_id` changed from `uint64` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/secrets`](/api-reference/v1/secretsservice/list-secrets) | +| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentId}/secrets`](/api-reference/v1/secretsservice/list-secrets) | +| Changed | for the `path` request parameter `externalTicketId`, the `format` was changed from `uint32` to `int64` | DELETE [`/api/v1/deployments/{deploymentId}/ticketing/v2/tickets/{externalTicketId}`](/api-reference/v1/ticketingservice/unlink-a-jira-ticket) | +| Changed | for the `query` request parameter `repository_ids`, the `format` of property `items/` was narrowed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentSlug}/projects`](/api-reference/v1/projectsservice/list-all-projects) | +| Changed | the `projects/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentSlug}/projects`](/api-reference/v1/projectsservice/list-all-projects) | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}`](/api-reference/v1/projectsservice/get-project-details) | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}`](/api-reference/v1/projectsservice/update-project-details) | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}/managed-scan`](/api-reference/v1/projectsservice/toggle-managed-scans-for-a-project) | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | DELETE [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags`](/api-reference/v1/projectsservice/remove-tags-from-project) | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PUT [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags`](/api-reference/v1/projectsservice/add-tags-to-project) | +| Changed | the `limit` request property `format` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | +| Changed | the `format` of the response properties `failed/items/issue_ids/items/`, `skipped/items/issue_ids/items/`, `succeeded/items/issue_ids/items/`, `succeeded/items/ticket_id` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | +| Changed | the `format` of the request properties `issue_ids/items/`, `limit` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | +| Changed | the `format` of the response properties `num_triaged`, `triaged_issues/items/` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | + +## Potentially breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the `dependencies/items/ecosystem` response property for the response status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Removed | removed the request property `formatVersion/version` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the `scans/items/status` response property for the response status `200` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | +| Added | added the new `SCM_TYPE_HARNESS` enum value to the `findings/items/repository/allOf[#/components/schemas/protos.secrets.v1.SecretsFinding_Repository]/scmType` response property for the response status `200` | GET [`/api/v1/deployments/{deploymentId}/secrets`](/api-reference/v1/secretsservice/list-secrets) | +| Removed | removed the optional property `sastFindings` from the response with the `200` status | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Removed | removed the optional property `scaFindings` from the response with the `200` status | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Removed | removed the request property `deploymentSlug` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | +| Added | added the new optional request property `formatVersion/cyclonedxVersion` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses/items/` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | +| Added | endpoint added | POST [`/api/v1/deployments/{deploymentId}/tickets/link`](/api-reference/v1/ticketingservice/link-an-existing-ticket-to-findings) | +| Added | endpoint added | POST [`/api/v1/deployments/{deploymentId}/tickets/unlink`](/api-reference/v1/ticketingservice/unlink-a-ticket-from-findings) | +| Added | added the new enum value `ai_sast` to the `query` request parameter `issue_type` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Added | added the new enum value `duplicate` to the `query` request parameter `triage_reasons` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Added | added the new enum value `provisionally_ignored` to the `query` request parameter `status` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Added | added the optional property `findings` to the response with the `200` status | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | +| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | +| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | +| Added | added the new `duplicate` enum value to the request property `new_triage_reason` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | +| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | +| Added | added the new `provisionally_ignored` enum value to the request property `new_triage_state` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | +| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | + diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx new file mode 100644 index 0000000000..4f8c72e52f --- /dev/null +++ b/docs/api-reference/v2/Changelog.mdx @@ -0,0 +1,125 @@ +--- +title: "Changelog" +description: "Changes to the Semgrep API v2, generated from its OpenAPI specification." +rss: true +--- + +{/* Auto-generated by scripts/generate_api_changelog.py -- do not edit by hand. */} + +Changes to the [Semgrep API v2](/api-reference/v2/Introduction), detected by +comparing successive versions of its OpenAPI specification. Breaking changes +are labeled; documentation-only edits are not listed. The v2 API is experimental; breaking changes are expected while it is in alpha. + + +## Potentially breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `FIX_JOB_ERROR_TYPE_AI_PROVIDER_NOT_ALLOWED`, `FIX_JOB_ERROR_TYPE_ARCHIVED_REPOSITORY`, `FIX_JOB_ERROR_TYPE_AUTOFIX_DISABLED`, `FIX_JOB_ERROR_TYPE_GROUPING_DISABLED`, `FIX_JOB_ERROR_TYPE_INVALID_GROUP`, `FIX_JOB_ERROR_TYPE_NON_PRIMARY_BRANCH_DISABLED`, `FIX_JOB_ERROR_TYPE_UNSUPPORTED_ISSUE_TYPE` enum values to the `errors/items/errorType` response property for the response status `200` | POST [`/api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs`](/api-reference/v2/aifixjobsservice/create-fix-job) | +| Removed | removed the optional property `instances/items/transitionStates` from the response with the `200` status | GET [`/api/notifications/deployments/{deploymentId}/ticketing/v2`](/api-reference/v2/externalticketingservice/get-external-ticketing-instances) | + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the optional property `errorDetail` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test`](/api-reference/v2/notificationrulesservice/test-notification-rule) | +| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/products`](/api-reference/v2/deploymentproductsservice/get-deployment-product-config) | +| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | PUT [`/api/agent/deployments/{deploymentId}/products`](/api-reference/v2/deploymentproductsservice/upsert-deployment-product-config) | +| Added | added the optional property `errorDetail` to the response with the `200` status | POST [`/api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test`](/api-reference/v2/notificationwebhooksservice/test-notification-webhook) | +| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new optional request property `automation/actions/items/id` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new optional request property `automation/actions/items/id` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the optional property `instances/items/fixedTransitionState` to the response with the `200` status | GET [`/api/notifications/deployments/{deploymentId}/ticketing/v2`](/api-reference/v2/externalticketingservice/get-external-ticketing-instances) | +| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | + + + +## Potentially breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the `settings/tourStates/items/id` response property for the response status `200` | GET [`/api/auth/users/current/settings`](/api-reference/v2/usersservice/get-user-settings) | + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the request property `tourStates/items/id` | PATCH [`/api/auth/users/current/settings`](/api-reference/v2/usersservice/update-user-settings) | +| Added | added the new `WorkflowsActivationBanner` enum value to the request property `eventSource` | PATCH [`/api/auth/users/current/settings`](/api-reference/v2/usersservice/update-user-settings) | + + + +## Potentially breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | + + + +## Potentially breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new optional request property `includeFirstDetectedAt` | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | +| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | +| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | +| Added | added the optional property `issues/items/issue/firstDetectedAt` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | +| Added | added the new optional request property `includeFirstDetectedAt` | POST [`/api/agent/deployments/{deploymentId}/issues/export`](/api-reference/v2/issuesservice/export-issues) | +| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/issues/v2/{issueId}`](/api-reference/v2/issuesservice/get-issue) | +| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/issues/v2/{issueId}`](/api-reference/v2/issuesservice/get-issue) | +| Added | added the optional property `firstDetectedAt` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/issues/v2/{issueId}`](/api-reference/v2/issuesservice/get-issue) | +| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/users`](/api-reference/v2/deploymentservice/get-deployment-users) | +| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/users/list`](/api-reference/v2/deploymentservice/list-deployment-users) | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new optional request property `issueTypes` | POST [`/api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets`](/api-reference/v2/externalticketingservice/create-external-tickets) | +| Deprecated | endpoint deprecated | GET [`/api/policies/v1/deployments/{deploymentId}`](/api-reference/v2/policiesservice/list-policies) | +| Deprecated | endpoint deprecated | GET [`/api/policies/v1/deployments/{deploymentId}/{policyId}/rules`](/api-reference/v2/policiesservice/list-policy-rules) | +| Deprecated | endpoint deprecated | PUT [`/api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath}`](/api-reference/v2/policiesservice/update-policy-rule) | +| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST [`/api/reporting/{deploymentId}/findings`](/api-reference/v2/reportsservice/get-report-findings) | +| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST [`/api/reporting/{deploymentId}/findings`](/api-reference/v2/reportsservice/get-report-findings) | +| Added | added the optional property `findings/items/firstDetectedAt` to the response with the `200` status | POST [`/api/reporting/{deploymentId}/findings`](/api-reference/v2/reportsservice/get-report-findings) | +| Added | added the new optional request property `filters/timeBucketSize` | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory`](/api-reference/v2/reportsservice/get-malware-firewall-blocked-malicious-dependencies-by-advisory-report) | +| Added | added the new optional request property `filters/timeBucketSize` | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem`](/api-reference/v2/reportsservice/get-malware-firewall-blocked-malicious-dependencies-by-dependency-version-and-ecosystem-report) | +| Added | added the new optional request property `filters/timeBucketSize` | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem`](/api-reference/v2/reportsservice/get-malware-firewall-blocked-malicious-dependencies-by-ecosystem-report) | +| Added | endpoint added | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/scan-activity`](/api-reference/v2/reportsservice/get-malware-firewall-scan-activity-report) | +| Added | endpoint added | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/summary`](/api-reference/v2/reportsservice/get-malware-firewall-summary-report) | +| Added | added the new optional `query` request parameter `dependencyFilter.includeArchived` | GET [`/api/sca/deployments/{deploymentId}/dependencies`](/api-reference/v2/supplychain2service/list-dependencies) | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/sca/deployments/{deploymentId}/dependencies`](/api-reference/v2/supplychain2service/list-dependencies) | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/sca/deployments/{deploymentId}/repositories`](/api-reference/v2/supplychain2service/list-repositories-with-dependencies) | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles`](/api-reference/v2/supplychain2service/list-lockfiles-with-dependencies-in-a-given-repository) | + diff --git a/docs/docs.json b/docs/docs.json index 3b6e1e8f9d..72163dc9c3 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -618,7 +618,8 @@ "pages": [ "api-reference/v1/Introduction", "api-reference/v1/Authentication", - "api-reference/v1/Terms-of-Use" + "api-reference/v1/Terms-of-Use", + "api-reference/v1/Changelog" ] }, { @@ -639,7 +640,8 @@ "pages": [ "api-reference/v2/Introduction", "api-reference/v2/Authentication", - "api-reference/v2/Terms-of-Use" + "api-reference/v2/Terms-of-Use", + "api-reference/v2/Changelog" ] }, { diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py new file mode 100644 index 0000000000..23ed3eb536 --- /dev/null +++ b/scripts/generate_api_changelog.py @@ -0,0 +1,601 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.9" +# dependencies = ["pyyaml"] +# +# [tool.uv] +# exclude-newer = "1 week" +# /// +# +# Generate a Mintlify changelog page (docs/api-reference/*/Changelog.mdx) from +# an OpenAPI spec's git history. +# +# Why this exists: the API reference is rendered straight from the checked-in +# specs, so the spec's git history *is* the API's change history — but nothing +# surfaced it to readers. This script walks that history with oasdiff +# (https://github.com/oasdiff/oasdiff), which diffs the specs semantically: +# line reorders from sort_openapi_nav.py and description edits from +# mirror_openapi.py produce no entries, only consumer-affecting changes do. +# +# How dates work: commits touching the spec are collapsed to one snapshot per +# committer-date (the day's final state — intra-day flip-flops intentionally +# vanish), and consecutive snapshots are diffed to make one block per +# day. If the working tree differs from HEAD (the update-openapi-specs.yml +# cron fetches fresh specs before committing), a snapshot dated "today" is +# added so the changelog entry lands in the same PR as the spec change. Should +# that PR merge on a later day, the next full regeneration re-dates the entry +# to the merge commit's date — a small, self-correcting relabel diff. +# +# The page is fully regenerated on every run (idempotent, and retroactively +# consistent if tooling changes). Output is a pure function of git history and +# the oasdiff version, so keep oasdiff pinned in CI — bumping it may reword +# every entry at once, which is expected and fine in a deliberate bump PR. +# +# Requires the oasdiff binary (brew install oasdiff, or see the pinned install +# in .github/workflows/update-openapi-specs.yml) and full git history (not a +# shallow clone). + +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Callable, NamedTuple, Optional + +import yaml + +# Spec-hygiene checks aimed at API maintainers, not consumers. The `info` +# section only ever changes when the spec's own version/title metadata does +# (e.g. "major version did not increase"), which is meaningless noise on a +# reader-facing changelog. +EXCLUDED_SECTIONS = frozenset(("info",)) + +MONTHS = ( + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", +) + +BREAKING = 3 +POTENTIALLY_BREAKING = 2 + +SECTION_TITLES = { + BREAKING: "Breaking changes", + POTENTIALLY_BREAKING: "Potentially breaking changes", + 1: "Changes", +} + +# One upstream sync often lands the same kind of change many times over — six +# enum values added to one property, a dozen properties switching `uint32` to +# `int64`. Rendering each as its own bullet buries the day's real news, so +# changes whose oasdiff text differs only in one identifier are folded into a +# single bullet. Keyed by oasdiff check id; the regex names the varying token +# `value` and any other groups become part of the merge key (changes merge only +# when everything but `value` matches). Texts that don't match the expected +# wording (e.g. after an oasdiff upgrade) are left verbatim. +COALESCIBLE = { + "request-property-enum-value-added": ( + re.compile(r"^added the new `(?P[^`]+)` enum value to the request property `(?P[^`]+)`$"), + "added the new {values} enum values to the request property `{prop}`", + ), + "response-property-enum-value-added": ( + re.compile( + r"^added the new `(?P[^`]+)` enum value to the `(?P[^`]+)` response property" + r" for the response status `(?P[^`]+)`$" + ), + "added the new {values} enum values to the `{prop}` response property" + " for the response status `{status}`", + ), + "request-parameter-enum-value-added": ( + re.compile( + r"^added the new enum value `(?P[^`]+)` to the `(?P[^`]+)`" + r" request parameter `(?P[^`]+)`$" + ), + "added the new enum values {values} to the `{loc}` request parameter `{param}`", + ), + "request-property-type-changed": ( + re.compile( + r"^the `(?P[^`]+)` request property `(?P[^`]+)` changed" + r" from `(?P[^`]+)` to `(?P[^`]+)`$" + ), + "the `{attr}` of the request properties {values} changed from `{old}` to `{new}`", + ), + "response-property-type-changed": ( + re.compile( + r"^the `(?P[^`]+)` response's property `(?P[^`]+)` changed" + r" from `(?P[^`]+)` to `(?P[^`]+)` for status `(?P[^`]+)`$" + ), + "the `{attr}` of the response properties {values} changed" + " from `{old}` to `{new}` for status `{status}`", + ), + "request-parameter-type-changed": ( + re.compile( + r"^for the `(?P[^`]+)` request parameter `(?P[^`]+)`, the `(?P[^`]+)`" + r" was changed from `(?P[^`]+)` to `(?P[^`]+)`$" + ), + "for the `{loc}` request parameters {values}, the `{attr}`" + " was changed from `{old}` to `{new}`", + ), +} + + +class Snapshot(NamedTuple): + ref: Optional[str] # commit sha, or None for the working tree + date: str # ISO YYYY-MM-DD + + +class Entry(NamedTuple): + date: str # ISO YYYY-MM-DD + changes: list + + +def repo_root() -> Path: + out = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, text=True, check=True, + ) + return Path(out.stdout.strip()) + + +def list_snapshots(spec_rel: str, cwd: Path) -> list[Snapshot]: + """One snapshot per committer-date the spec changed on, newest first. + + --first-parent keeps dates monotonic along the main branch; without it, + commits authored days earlier on a side branch would interleave. + """ + out = subprocess.run( + ["git", "log", "--first-parent", "--format=%H %cs", "--", spec_rel], + capture_output=True, text=True, check=True, cwd=cwd, + ) + snapshots: list[Snapshot] = [] + seen_dates = set() + for line in out.stdout.splitlines(): + sha, _, date = line.strip().partition(" ") + if not sha or date in seen_dates: + continue # git log is newest-first: first hash = day's final state + seen_dates.add(date) + snapshots.append(Snapshot(sha, date)) + return snapshots + + +def working_tree_differs(spec_rel: str, cwd: Path) -> bool: + return ( + subprocess.run( + ["git", "diff", "--quiet", "HEAD", "--", spec_rel], cwd=cwd + ).returncode + != 0 + ) + + +def run_oasdiff(base: str, rev: str, oasdiff_bin: str, cwd: Path) -> Optional[list]: + """Changes between two spec revisions, or None if oasdiff failed. + + Exit code 0 covers both "changes found" and "no changes" (we don't pass + --fail-on), so a non-zero exit always means a real error such as an + unparseable revision. + """ + result = subprocess.run( + [oasdiff_bin, "changelog", "--format", "json", "--flatten-allof", base, rev], + capture_output=True, text=True, cwd=cwd, + ) + if result.returncode != 0: + print( + f"warning: oasdiff failed for {base} -> {rev}: {result.stderr.strip()}", + file=sys.stderr, + ) + return None + return json.loads(result.stdout) + + +def _revision_arg(snapshot: Snapshot, spec_rel: str) -> str: + return f"{snapshot.ref}:{spec_rel}" if snapshot.ref else spec_rel + + +def build_entries( + snapshots: list[Snapshot], + spec_rel: str, + diff: Callable[[str, str], Optional[list]], +) -> list[Entry]: + """Diff consecutive snapshots into per-date entries, newest first. + + The oldest snapshot is the baseline and never gets an entry — diffing it + against nothing would report every endpoint as "added". + """ + ordered = list(reversed(snapshots)) # oldest -> newest + if not ordered: + return [] + + entries: list[Entry] = [] + last_good = ordered[0] + for snapshot in ordered[1:]: + rev_arg = _revision_arg(snapshot, spec_rel) + changes = diff(_revision_arg(last_good, spec_rel), rev_arg) + if changes is None: + # Decide which side is unparseable: if the newer side self-diffs + # cleanly, the base was bad — drop it and let its successor's diff + # pick up any changes; otherwise skip the newer snapshot. + if diff(rev_arg, rev_arg) is not None: + last_good = snapshot + continue + changes = [c for c in changes if c.get("section") not in EXCLUDED_SECTIONS] + if changes: + entries.append(Entry(snapshot.date, changes)) + last_good = snapshot + return list(reversed(entries)) + + +def format_date(iso: str) -> str: + """"2026-08-07" -> "August 7, 2026" (no strftime: %-d is platform-bound).""" + year, month, day = iso.split("-") + return f"{MONTHS[int(month) - 1]} {int(day)}, {int(year)}" + + +def _escape_segment(text: str) -> str: + text = text.replace("&", "&") + text = text.replace("<", "<").replace(">", ">") + text = text.replace("{", "{").replace("}", "}") + for char in "*_[]": + text = text.replace(char, "\\" + char) + return text + + +def escape_mdx(text: str) -> str: + """Escape MDX-hazardous characters, preserving `code spans`. + + oasdiff wraps identifiers in backticks; inside a code span MDX treats + braces and angle brackets literally, so only text outside spans needs + entity-escaping. Unbalanced backticks can't delimit spans — escape them. + """ + parts = text.split("`") + if len(parts) % 2 == 0: # odd backtick count: not span-delimited + return _escape_segment(text).replace("`", "\\`") + for i in range(0, len(parts), 2): # even indices sit outside code spans + parts[i] = _escape_segment(parts[i]) + return "`".join(parts) + + +def coalesce_changes(changes: list) -> list: + """Fold same-kind changes that differ only in one identifier into one. + + See COALESCIBLE. Order follows first appearance of each merge bucket, and + a bucket with a single change keeps its original dict untouched. + """ + buckets: dict = {} + order: list = [] + for change in changes: + pattern, template = COALESCIBLE.get(change.get("id"), (None, None)) + match = pattern.match(change["text"]) if pattern else None + if match: + fixed = {k: v for k, v in match.groupdict().items() if k != "value"} + key = ( + change["id"], change.get("operation"), change.get("path"), + tuple(sorted(fixed.items())), + ) + bucket = buckets.setdefault(key, {"changes": [], "values": [], "fixed": fixed, "template": template}) + bucket["changes"].append(change) + bucket["values"].append(match.group("value")) + else: + key = ("verbatim", len(order)) + buckets[key] = {"changes": [change]} + if key not in order: + order.append(key) + + merged = [] + for key in order: + bucket = buckets[key] + if len(bucket["changes"]) == 1: + merged.append(bucket["changes"][0]) + continue + values = ", ".join(f"`{v}`" for v in sorted(bucket["values"])) + merged.append( + {**bucket["changes"][0], "text": bucket["template"].format(values=values, **bucket["fixed"])} + ) + return merged + + +HTTP_METHODS = frozenset( + ("get", "post", "put", "patch", "delete", "options", "head", "trace") +) + +# Mintlify Badge colors per HTTP method, matching the API playground's visual +# language (green reads, blue creates, red deletes). +METHOD_BADGE_COLORS = { + "GET": "green", + "POST": "blue", + "PUT": "orange", + "PATCH": "yellow", + "DELETE": "red", +} + +# Verb chip for the table style's Change column, keyed on tokens of the +# oasdiff check id. Order matters: "api-path-removed-without-deprecation" +# contains both removal and deprecation words, and it is a removal. +CHANGE_VERBS = ( + (frozenset(("removed", "deleted")), ("Removed", "red")), + (frozenset(("added", "new")), ("Added", "green")), + (frozenset(("deprecated",)), ("Deprecated", "yellow")), +) +CHANGE_VERB_FALLBACKS = ( + frozenset( + ("changed", "specialized", "narrowed", "generalized", + "increased", "decreased", "restricted") + ), + ("Changed", "orange"), +) + + +def mintlify_slug(text: str) -> str: + """Slugify the way Mintlify names its generated endpoint pages. + + Reverse-engineered and verified against the rendered site (every + operation in both specs resolved with HTTP 200): lowercase; apostrophes + stripped outright; underscores and square brackets preserved; every other + run of non-alphanumerics becomes a single hyphen. + """ + text = text.lower().replace("'", "") + return re.sub(r"[^a-z0-9_\[\]]+", "-", text).strip("-") + + +def _default_page_title(method: str, path: str) -> str: + """Mintlify's page title for operations without a summary. + + Path parameter segments become word breaks; adjacent literal segments + run together: GET /api/agent/deployments/{id}/ignores -> + "Get apiagentdeployments ignores". + """ + text = "".join( + " " if seg.startswith("{") else seg for seg in path.strip("/").split("/") + ) + return f"{method.capitalize()} {' '.join(text.split())}" + + +def endpoint_urls(spec: dict, link_base: str) -> dict: + """(METHOD, path) -> docs URL for every operation in the spec. + + Built from the *current* spec on purpose: pages only exist for endpoints + that are still in it, so removed endpoints simply drop out of the map and + render unlinked instead of pointing at a 404. + """ + urls = {} + for path, item in (spec.get("paths") or {}).items(): + if not isinstance(item, dict): + continue + for method, op in item.items(): + if method not in HTTP_METHODS or not isinstance(op, dict): + continue + tags = op.get("tags") or [] + if not tags: + continue + title = op.get("summary") or _default_page_title(method, path) + # brackets are legal in Mintlify slugs but break markdown link + # destinations, so percent-encode them + slug = mintlify_slug(title).replace("[", "%5B").replace("]", "%5D") + urls[(method.upper(), path)] = f"{link_base}/{mintlify_slug(tags[0])}/{slug}" + return urls + + +def _endpoint_key(change: dict) -> tuple: + path = change.get("path") or "" + if not path: + return (1, "", "") # General bucket sorts after all endpoints + return (0, path, change.get("operation") or "") + + +def _summary(changes: list) -> str: + """Raw change counts, e.g. "2 breaking · 1 potentially breaking · 3 other changes".""" + breaking = sum(1 for c in changes if c["level"] == BREAKING) + potential = sum(1 for c in changes if c["level"] == POTENTIALLY_BREAKING) + other = len(changes) - breaking - potential + parts = [] + if breaking: + parts.append(f"{breaking} breaking") + if potential: + parts.append(f"{potential} potentially breaking") + if other: + label = "other change" if parts else "change" + parts.append(f"{other} {label}{'s' if other != 1 else ''}") + return " · ".join(parts) + + +def _render_section(changes: list, links: dict) -> list: + """Bullets for one severity section, grouped by endpoint.""" + groups: dict[tuple, list] = {} + for change in changes: + groups.setdefault(_endpoint_key(change), []).append(change) + + lines = [] + for key in sorted(groups): + texts = [ + escape_mdx(c["text"]) + for c in sorted(groups[key], key=lambda c: (c["id"], c["text"])) + ] + if key[0] == 1: # General bucket: schema/security changes, no endpoint + lines.extend(f"- {text}" for text in texts) + continue + lead = _endpoint_cell(key, links) + if len(texts) == 1: + lines.append(f"- {lead}: {texts[0]}") + else: + lines.append(f"- {lead}:") + lines.extend(f" - {text}" for text in texts) + return lines + + +def _change_verb(change: dict) -> tuple: + """(label, badge color) for the table's Change column, from the check id.""" + tokens = set(change.get("id", "").split("-")) + for words, verb in CHANGE_VERBS: + if tokens & words: + return verb + if tokens & CHANGE_VERB_FALLBACKS[0]: + return CHANGE_VERB_FALLBACKS[1] + return ("Changed", "gray") + + +def _endpoint_cell(key: tuple, links: dict) -> str: + if key[0] == 1: # General bucket: schema/security changes, no endpoint + return "—" + operation, path = key[2], key[1] + color = METHOD_BADGE_COLORS.get(operation, "gray") + url = links.get((operation, path)) + target = f"[`{path}`]({url})" if url else f"`{path}`" + return f'{operation} {target}' + + +def _render_section_table(changes: list, links: dict) -> list: + """One table for a severity section: Change | Description | Endpoint.""" + groups: dict[tuple, list] = {} + for change in changes: + groups.setdefault(_endpoint_key(change), []).append(change) + + lines = ["| Change | Description | Endpoint |", "|---|---|---|"] + for key in sorted(groups): + cell = _endpoint_cell(key, links) + for change in sorted(groups[key], key=lambda c: (c["id"], c["text"])): + verb, color = _change_verb(change) + description = escape_mdx(change["text"]).replace("|", "\\|") + lines.append( + f'| {verb}' + f" | {description} | {cell} |" + ) + return lines + + +def _render_update(entry: Entry, links: dict, style: str = "table") -> str: + label = format_date(entry.date) + attrs = [f'label="{label}"', f'description="{_summary(entry.changes)}"'] + if any(c["level"] == BREAKING for c in entry.changes): + attrs.append('tags={["Breaking"]}') + + render_section = _render_section_table if style == "table" else _render_section + changes = coalesce_changes(entry.changes) + lines = [f""] + first = True + for level in (BREAKING, POTENTIALLY_BREAKING, 1): + section = [c for c in changes if (c["level"] if c["level"] in SECTION_TITLES else 1) == level] + if not section: + continue + if not first: + lines.append("") + first = False + lines.append(f"## {SECTION_TITLES[level]}") + lines.append("") + lines.extend(render_section(section, links)) + lines.append("") + return "\n".join(lines) + + +def render_page( + api_name: str, + api_href: Optional[str], + note: Optional[str], + entries: list[Entry], + links: Optional[dict] = None, + style: str = "table", +) -> str: + name = f"[{api_name}]({api_href})" if api_href else api_name + intro = ( + f"Changes to the {name}, detected by\n" + "comparing successive versions of its OpenAPI specification. Breaking changes\n" + "are labeled; documentation-only edits are not listed." + ) + if note: + intro += f" {note}" + + blocks = [ + "---", + 'title: "Changelog"', + f'description: "Changes to the {api_name}, generated from its OpenAPI specification."', + "rss: true", + "---", + "", + "{/* Auto-generated by scripts/generate_api_changelog.py -- do not edit by hand. */}", + "", + intro, + "", + ] + if entries: + blocks.append( + "\n\n".join(_render_update(entry, links or {}, style) for entry in entries) + ) + else: + blocks.append("No API changes recorded yet.") + return "\n".join(blocks) + "\n" + + +def main(argv: Optional[list[str]] = None) -> int: + parser = argparse.ArgumentParser( + description="Generate a Mintlify changelog page from an OpenAPI spec's git history." + ) + parser.add_argument("spec", help="path to the OpenAPI spec (tracked in git)") + parser.add_argument("output", help="path of the Changelog.mdx to write") + parser.add_argument("--api-name", required=True, help='e.g. "Semgrep API v1"') + parser.add_argument("--api-href", help="docs link for the API name in the intro") + parser.add_argument("--note", help="extra sentence appended to the intro") + parser.add_argument( + "--link-base", + help="URL prefix of the generated endpoint pages (e.g. /api-reference/v1);" + " when set, endpoint mentions link to their reference page", + ) + parser.add_argument( + "--style", + choices=("table", "list"), + default="table", + help="table: Stripe-style Change/Description/Endpoint tables (default);" + " list: endpoint-grouped bullets", + ) + parser.add_argument( + "--no-working-tree", action="store_true", + help="ignore uncommitted spec changes (only diff committed history)", + ) + parser.add_argument("--today", help="date label for working-tree changes (YYYY-MM-DD)") + parser.add_argument("--oasdiff-bin", default="oasdiff") + parser.add_argument( + "--max-entries", type=int, default=0, + help="cap the page at the newest N entries (0 = unlimited)", + ) + args = parser.parse_args(argv) + + cwd = repo_root() + spec_rel = Path(args.spec).resolve().relative_to(cwd).as_posix() + + snapshots = list_snapshots(spec_rel, cwd) + if not snapshots: + print(f"error: no git history for {spec_rel} (shallow clone?)", file=sys.stderr) + return 1 + + if not args.no_working_tree and working_tree_differs(spec_rel, cwd): + today = args.today or datetime.now(timezone.utc).strftime("%Y-%m-%d") + worktree = Snapshot(None, today) + if snapshots[0].date == today: + snapshots[0] = worktree # merge into one Update for the day + else: + snapshots.insert(0, worktree) + + entries = build_entries( + snapshots, + spec_rel, + lambda base, rev: run_oasdiff(base, rev, oasdiff_bin=args.oasdiff_bin, cwd=cwd), + ) + if args.max_entries: + entries = entries[: args.max_entries] + + links = {} + if args.link_base: + links = endpoint_urls(yaml.safe_load(Path(args.spec).read_text()), args.link_base) + + content = render_page(args.api_name, args.api_href, args.note, entries, links, args.style) + output = Path(args.output) + if output.exists() and output.read_text() == content: + print(f"{output}: unchanged") + return 0 + output.write_text(content) + print(f"{output}: regenerated with {len(entries)} entries") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/tests/fixtures/changelog_empty.json b/scripts/tests/fixtures/changelog_empty.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/scripts/tests/fixtures/changelog_empty.json @@ -0,0 +1 @@ +[] diff --git a/scripts/tests/fixtures/changelog_info_only.json b/scripts/tests/fixtures/changelog_info_only.json new file mode 100644 index 0000000000..4c54b5f051 --- /dev/null +++ b/scripts/tests/fixtures/changelog_info_only.json @@ -0,0 +1,12 @@ +[ + { + "id": "endpoint-added", + "text": "endpoint added", + "level": 1, + "operation": "POST", + "operationId": "createPolicy", + "path": "/api/v1/policies", + "section": "paths", + "fingerprint": "285df1289de3" + } +] diff --git a/scripts/tests/fixtures/changelog_mixed_levels.json b/scripts/tests/fixtures/changelog_mixed_levels.json new file mode 100644 index 0000000000..17c6295970 --- /dev/null +++ b/scripts/tests/fixtures/changelog_mixed_levels.json @@ -0,0 +1,66 @@ +[ + { + "id": "api-path-removed-without-deprecation", + "text": "api path removed without deprecation", + "level": 3, + "operation": "GET", + "operationId": "listAgents", + "path": "/api/v1/agents", + "section": "paths", + "fingerprint": "437410792ed8" + }, + { + "id": "response-required-property-removed", + "text": "removed the required property `tags` from the response with the `200` status", + "level": 3, + "operation": "GET", + "operationId": "listProjects", + "path": "/api/v1/deployments/{deployment_id}/projects", + "section": "paths", + "fingerprint": "5011e5fc6ce1" + }, + { + "id": "request-parameter-removed", + "text": "deleted the `query` request parameter `page_token`", + "level": 2, + "operation": "GET", + "operationId": "listProjects", + "path": "/api/v1/deployments/{deployment_id}/projects", + "section": "paths", + "fingerprint": "9f2b3c4d5e6a" + }, + { + "id": "api-major-version-not-bumped", + "text": "a breaking change was detected but the major version did not increase, from `1.0.0` to `1.1.0`", + "level": 1, + "section": "info", + "fingerprint": "12dd67d7a24c" + }, + { + "id": "new-optional-request-parameter", + "text": "added the new optional `query` request parameter `page_size`", + "level": 1, + "operation": "GET", + "operationId": "listProjects", + "path": "/api/v1/deployments/{deployment_id}/projects", + "section": "paths", + "fingerprint": "0b0c82b8f358" + }, + { + "id": "endpoint-added", + "text": "endpoint added", + "level": 1, + "operation": "POST", + "operationId": "createPolicy", + "path": "/api/v1/policies", + "section": "paths", + "fingerprint": "285df1289de3" + }, + { + "id": "api-schema-removed", + "text": "removed the schema `ProjectFilter`", + "level": 1, + "section": "components", + "fingerprint": "aa11bb22cc33" + } +] diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py new file mode 100644 index 0000000000..06572c3185 --- /dev/null +++ b/scripts/tests/test_generate_api_changelog.py @@ -0,0 +1,711 @@ +import json +import shutil +import subprocess +import textwrap +from pathlib import Path + +import pytest + +import generate_api_changelog as gac + +FIXTURES = Path(__file__).parent / "fixtures" + + +def load_fixture(name): + return json.loads((FIXTURES / name).read_text()) + + +# --- snapshot listing ------------------------------------------------------- + + +def test_list_snapshots_groups_by_date_keeping_newest_commit(monkeypatch): + calls = [] + + def fake_run(cmd, **kwargs): + calls.append(cmd) + stdout = ( + "aaa111 2026-08-13\n" + "bbb222 2026-08-13\n" + "ccc333 2026-08-07\n" + "ddd444 2026-07-14\n" + ) + return subprocess.CompletedProcess(cmd, 0, stdout=stdout, stderr="") + + monkeypatch.setattr(gac.subprocess, "run", fake_run) + snapshots = gac.list_snapshots("docs/spec.yaml", Path("/repo")) + + # git log is newest-first; the first hash seen for a date is that day's + # final state. + assert snapshots == [ + gac.Snapshot("aaa111", "2026-08-13"), + gac.Snapshot("ccc333", "2026-08-07"), + gac.Snapshot("ddd444", "2026-07-14"), + ] + assert "--first-parent" in calls[0] + + +# --- date formatting -------------------------------------------------------- + + +def test_format_date_is_locale_independent(): + assert gac.format_date("2026-08-07") == "August 7, 2026" + assert gac.format_date("2026-12-25") == "December 25, 2026" + + +# --- MDX escaping ----------------------------------------------------------- + + +def test_escape_mdx_preserves_code_spans_and_escapes_outside(): + text = "removed the required property `tags` from the response with the `200` status" + assert gac.escape_mdx(text) == text + + assert ( + gac.escape_mdx("added {x} and around `{'keep'}`") + == "added {x} and <T> around `{'keep'}`" + ) + + +def test_escape_mdx_ampersand_first(): + assert gac.escape_mdx("a & b < c") == "a & b < c" + + +def test_escape_mdx_markdown_chars_outside_code_spans(): + assert gac.escape_mdx("a *b* [c] _d_") == r"a \*b\* \[c\] \_d\_" + + +def test_escape_mdx_unbalanced_backticks_escaped_literally(): + assert gac.escape_mdx("odd ` tick {x}") == "odd \\` tick {x}" + + +# --- endpoint page URLs ------------------------------------------------------ +# +# Slug rules verified empirically against Mintlify-generated pages (all 243 +# operations of both specs resolved with HTTP 200 on 2026-08-13). + + +def test_mintlify_slug_rules(): + assert gac.mintlify_slug("Create Fix Job") == "create-fix-job" + assert gac.mintlify_slug("AiFixJobsService") == "aifixjobsservice" + # apostrophes are stripped, not hyphenated + assert gac.mintlify_slug("Get a Project's Managed Scan Settings") == "get-a-projects-managed-scan-settings" + # commas collapse into the space hyphen; existing hyphens survive + assert ( + gac.mintlify_slug("List Code, Supply Chain, or AI-powered Scan findings") + == "list-code-supply-chain-or-ai-powered-scan-findings" + ) + # underscores and square brackets are preserved + assert gac.mintlify_slug("Get x managed_scan_settings") == "get-x-managed_scan_settings" + assert ( + gac.mintlify_slug("[Beta] Get SMS VPC Bootstrap CloudFormation Template") + == "[beta]-get-sms-vpc-bootstrap-cloudformation-template" + ) + + +LINKS_SPEC = { + "paths": { + "/api/v1/policies": { + "post": {"tags": ["PoliciesService"], "summary": "Create Policy"}, + "parameters": [{"name": "x", "in": "query"}], # non-method key ignored + }, + "/api/agent/deployments/{deploymentId}/ignores": { + # no summary: page title is derived from method + path, with + # parameter segments becoming word breaks + "get": {"tags": ["IgnoresService"]}, + }, + "/api/v1/untagged": {"get": {"summary": "No Tag"}}, + } +} + + +def test_endpoint_urls_from_spec(): + urls = gac.endpoint_urls(LINKS_SPEC, "/api-reference/v2") + assert urls[("POST", "/api/v1/policies")] == "/api-reference/v2/policiesservice/create-policy" + assert urls[("GET", "/api/agent/deployments/{deploymentId}/ignores")] == ( + "/api-reference/v2/ignoresservice/get-apiagentdeployments-ignores" + ) + assert ("GET", "/api/v1/untagged") not in urls # no tag -> no page URL + + +def test_endpoint_urls_percent_encode_brackets(): + spec = {"paths": {"/api/v1/bootstrap-sms-vpc": {"get": { + "tags": ["MiscService"], + "summary": "[Beta] Get SMS VPC Bootstrap CloudFormation Template", + }}}} + urls = gac.endpoint_urls(spec, "/api-reference/v1") + assert urls[("GET", "/api/v1/bootstrap-sms-vpc")] == ( + "/api-reference/v1/miscservice/%5Bbeta%5D-get-sms-vpc-bootstrap-cloudformation-template" + ) + + +# --- coalescing repetitive changes ------------------------------------------ + + +def enum_change(value, prop="errors/items/errorType", status="200", **overrides): + change = { + "id": "response-property-enum-value-added", + "text": ( + f"added the new `{value}` enum value to the `{prop}` response " + f"property for the response status `{status}`" + ), + "level": 2, + "operation": "POST", + "path": "/api/v1/jobs", + "section": "paths", + } + change.update(overrides) + return change + + +def test_coalesce_merges_enum_values_for_same_property(): + merged = gac.coalesce_changes( + [enum_change("TYPE_B"), enum_change("TYPE_A"), enum_change("TYPE_C")] + ) + assert len(merged) == 1 + assert merged[0]["text"] == ( + "added the new `TYPE_A`, `TYPE_B`, `TYPE_C` enum values to the " + "`errors/items/errorType` response property for the response status `200`" + ) + assert merged[0]["level"] == 2 + + +def test_coalesce_keeps_distinct_properties_apart(): + merged = gac.coalesce_changes( + [enum_change("A"), enum_change("A", prop="other/prop")] + ) + assert len(merged) == 2 + + +def test_coalesce_keeps_distinct_endpoints_apart(): + merged = gac.coalesce_changes( + [enum_change("A"), enum_change("A", path="/api/v1/other")] + ) + assert len(merged) == 2 + + +def test_coalesce_singletons_and_unknown_ids_stay_verbatim(): + single = enum_change("ONLY_ONE") + unknown = { + "id": "endpoint-added", "text": "endpoint added", "level": 1, + "operation": "POST", "path": "/api/v1/policies", "section": "paths", + } + merged = gac.coalesce_changes([single, unknown]) + assert merged == [single, unknown] + + +def test_coalesce_format_changes_by_old_new_pair(): + def fmt(prop, old="uint64"): + return { + "id": "request-property-type-changed", + "text": f"the `{prop}` request property `format` changed from `{old}` to `int64`", + "level": 3, + "operation": "POST", + "path": "/api/v1/deps", + "section": "paths", + } + + merged = gac.coalesce_changes([fmt("cursor"), fmt("deploymentId"), fmt("pageSize", old="uint32")]) + texts = sorted(c["text"] for c in merged) + assert texts == [ + "the `format` of the request properties `cursor`, `deploymentId` changed from `uint64` to `int64`", + "the `pageSize` request property `format` changed from `uint32` to `int64`", + ] + + +def test_coalesce_unmatched_text_stays_verbatim(): + odd = enum_change("X") + odd["text"] = "some future oasdiff wording we do not recognize" + assert gac.coalesce_changes([odd, enum_change("Y")]) == [odd, enum_change("Y")] + + +# --- entry building --------------------------------------------------------- + + +def snapshots_for(*pairs): + """pairs of (ref, date), newest first.""" + return [gac.Snapshot(ref, date) for ref, date in pairs] + + +def test_build_entries_diffs_consecutive_snapshots_newest_first(): + snaps = snapshots_for( + ("ccc", "2026-08-07"), ("bbb", "2026-07-23"), ("aaa", "2026-07-14") + ) + diffs = { + ("aaa:spec.yaml", "bbb:spec.yaml"): load_fixture("changelog_info_only.json"), + ("bbb:spec.yaml", "ccc:spec.yaml"): load_fixture("changelog_mixed_levels.json"), + } + entries = gac.build_entries(snaps, "spec.yaml", lambda b, r: diffs[(b, r)]) + + assert [e.date for e in entries] == ["2026-08-07", "2026-07-23"] + # oldest snapshot is the baseline: no entry for 2026-07-14 + + +def test_build_entries_skips_empty_diffs_without_breaking_chain(): + snaps = snapshots_for( + ("ccc", "2026-08-07"), ("bbb", "2026-07-23"), ("aaa", "2026-07-14") + ) + seen = [] + + def diff(base, rev): + seen.append((base, rev)) + if rev == "bbb:spec.yaml": + return [] # e.g. the nav-sort line permutation + return load_fixture("changelog_info_only.json") + + entries = gac.build_entries(snaps, "spec.yaml", diff) + + assert [e.date for e in entries] == ["2026-08-07"] + # bbb produced no entry but still becomes the next comparison base + assert seen == [("aaa:spec.yaml", "bbb:spec.yaml"), ("bbb:spec.yaml", "ccc:spec.yaml")] + + +def test_build_entries_filters_excluded_sections(): + snaps = snapshots_for(("bbb", "2026-08-07"), ("aaa", "2026-07-14")) + version_noise = [c for c in load_fixture("changelog_mixed_levels.json") if c["section"] == "info"] + assert version_noise, "fixture must contain an info-section change" + + entries = gac.build_entries(snaps, "spec.yaml", lambda b, r: version_noise) + + assert entries == [] # a day with only excluded changes gets no entry + + +def test_build_entries_drops_unparseable_base_and_promotes_revision(): + snaps = snapshots_for( + ("ccc", "2026-08-07"), ("bbb", "2026-07-23"), ("aaa", "2026-07-14") + ) + + def diff(base, rev): + if base == "aaa:spec.yaml" and rev == "bbb:spec.yaml": + return None # pair fails + if base == rev: # self-diff: bbb parses fine -> aaa was the bad side + return [] + assert (base, rev) == ("bbb:spec.yaml", "ccc:spec.yaml") + return load_fixture("changelog_info_only.json") + + entries = gac.build_entries(snaps, "spec.yaml", diff) + assert [e.date for e in entries] == ["2026-08-07"] + + +def test_build_entries_skips_unparseable_revision_keeping_base(): + snaps = snapshots_for( + ("ccc", "2026-08-07"), ("bbb", "2026-07-23"), ("aaa", "2026-07-14") + ) + + def diff(base, rev): + if rev == "bbb:spec.yaml": + return None # both the pair and the self-diff fail: bbb is bad + assert (base, rev) == ("aaa:spec.yaml", "ccc:spec.yaml") + return load_fixture("changelog_info_only.json") + + entries = gac.build_entries(snaps, "spec.yaml", diff) + assert [e.date for e in entries] == ["2026-08-07"] + + +def test_build_entries_working_tree_snapshot_uses_plain_path(): + snaps = snapshots_for((None, "2026-08-13"), ("aaa", "2026-07-14")) + seen = [] + + def diff(base, rev): + seen.append((base, rev)) + return load_fixture("changelog_info_only.json") + + gac.build_entries(snaps, "spec.yaml", diff) + assert seen == [("aaa:spec.yaml", "spec.yaml")] + + +# --- rendering -------------------------------------------------------------- + +GOLDEN_PAGE = """\ +--- +title: "Changelog" +description: "Changes to the Semgrep API v1, generated from its OpenAPI specification." +rss: true +--- + +{/* Auto-generated by scripts/generate_api_changelog.py -- do not edit by hand. */} + +Changes to the [Semgrep API v1](/api-reference/v1/Introduction), detected by +comparing successive versions of its OpenAPI specification. Breaking changes +are labeled; documentation-only edits are not listed. + + +## Breaking changes + +- GET `/api/v1/agents`: api path removed without deprecation +- GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects): removed the required property `tags` from the response with the `200` status + +## Potentially breaking changes + +- GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects): deleted the `query` request parameter `page_token` + +## Changes + +- GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects): added the new optional `query` request parameter `page_size` +- POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy): endpoint added +- removed the schema `ProjectFilter` + + + +## Changes + +- POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy): endpoint added + +""" + +GOLDEN_LINKS = { + # /api/v1/agents deliberately absent: a removed endpoint has no page, + # so its lead-in must stay a plain code span + ("GET", "/api/v1/deployments/{deployment_id}/projects"): "/api-reference/v1/projectsservice/list-projects", + ("POST", "/api/v1/policies"): "/api-reference/v1/policiesservice/create-policy", +} + + +def golden_entries(): + mixed = [c for c in load_fixture("changelog_mixed_levels.json") if c["section"] != "info"] + return [ + gac.Entry("2026-08-07", mixed), + gac.Entry("2026-07-23", load_fixture("changelog_info_only.json")), + ] + + +def test_render_page_golden_list_style(): + page = gac.render_page( + api_name="Semgrep API v1", + api_href="/api-reference/v1/Introduction", + note=None, + entries=golden_entries(), + links=GOLDEN_LINKS, + style="list", + ) + assert page == GOLDEN_PAGE + + +GOLDEN_TABLE_BODY = """\ + +## Breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Removed | api path removed without deprecation | GET `/api/v1/agents` | +| Removed | removed the required property `tags` from the response with the `200` status | GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects) | + +## Potentially breaking changes + +| Change | Description | Endpoint | +|---|---|---| +| Removed | deleted the `query` request parameter `page_token` | GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects) | + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new optional `query` request parameter `page_size` | GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects) | +| Added | endpoint added | POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy) | +| Removed | removed the schema `ProjectFilter` | — | + + + +## Changes + +| Change | Description | Endpoint | +|---|---|---| +| Added | endpoint added | POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy) | + +""" + + +def test_render_page_golden_table_style(): + page = gac.render_page( + api_name="Semgrep API v1", + api_href="/api-reference/v1/Introduction", + note=None, + entries=golden_entries(), + links=GOLDEN_LINKS, + style="table", + ) + assert page.endswith(GOLDEN_TABLE_BODY) + assert page.startswith("---\ntitle: \"Changelog\"") + + +def test_change_verb_mapping(): + assert gac._change_verb({"id": "endpoint-added"}) == ("Added", "green") + assert gac._change_verb({"id": "new-optional-request-parameter"}) == ("Added", "green") + # "removed" wins over the "deprecation" in the same id + assert gac._change_verb({"id": "api-path-removed-without-deprecation"}) == ("Removed", "red") + assert gac._change_verb({"id": "endpoint-deprecated"}) == ("Deprecated", "yellow") + assert gac._change_verb({"id": "request-property-type-changed"}) == ("Changed", "orange") + assert gac._change_verb({"id": "request-parameter-property-type-specialized"}) == ("Changed", "orange") + assert gac._change_verb({"id": "something-unrecognized"}) == ("Changed", "gray") + + +def test_table_cells_escape_pipes(): + changes = [{ + "id": "endpoint-added", "level": 1, "section": "paths", + "operation": "GET", "path": "/api/v1/a", + "text": "added enum `A|B` to x", + }] + rendered = gac._render_update(gac.Entry("2026-08-07", changes), links={}, style="table") + assert r"`A\|B`" in rendered + assert " | added enum " in rendered # column separators intact + + +def test_render_page_breaking_tag_and_summary(): + page = gac.render_page( + api_name="Semgrep API v1", + api_href="/api-reference/v1/Introduction", + note=None, + entries=golden_entries(), + ) + assert ( + '' + ) in page + assert '' in page + + +def test_render_update_nests_multiple_changes_per_endpoint(): + changes = [ + { + "id": "request-property-removed", "level": 3, "section": "paths", + "operation": "POST", "path": "/api/v1/deps", + "text": "removed the request property `a`", + }, + { + "id": "request-property-removed", "level": 3, "section": "paths", + "operation": "POST", "path": "/api/v1/deps", + "text": "removed the request property `b`", + }, + ] + rendered = gac._render_update(gac.Entry("2026-08-07", changes), links={}, style="list") + assert ( + '- POST `/api/v1/deps`:\n' + " - removed the request property `a`\n" + " - removed the request property `b`" + ) in rendered + + +def test_render_update_method_badge_colors(): + def change(operation, path): + return { + "id": "endpoint-added", "text": "endpoint added", "level": 1, + "section": "paths", "operation": operation, "path": path, + } + + rendered = gac._render_update( + gac.Entry("2026-08-07", [change("DELETE", "/api/v1/a"), change("BREW", "/api/v1/b")]), + links={}, + style="list", + ) + assert 'DELETE' in rendered + assert 'BREW' in rendered # unknown method + + +def test_render_update_links_nested_endpoint_lead_in(): + changes = [ + { + "id": "request-property-removed", "level": 3, "section": "paths", + "operation": "POST", "path": "/api/v1/deps", + "text": "removed the request property `a`", + }, + { + "id": "request-property-removed", "level": 3, "section": "paths", + "operation": "POST", "path": "/api/v1/deps", + "text": "removed the request property `b`", + }, + ] + links = {("POST", "/api/v1/deps"): "/api-reference/v1/depsservice/create-dep"} + rendered = gac._render_update(gac.Entry("2026-08-07", changes), links=links, style="list") + assert ( + '- POST' + " [`/api/v1/deps`](/api-reference/v1/depsservice/create-dep):\n" + ) in rendered + + +def test_render_page_without_entries_still_renders_shell(): + page = gac.render_page( + api_name="Semgrep API v2", + api_href=None, + note="The v2 API is experimental; breaking changes are expected.", + entries=[], + ) + assert page.startswith("---\ntitle: \"Changelog\"") + assert "rss: true" in page + assert "Semgrep API v2" in page + assert "The v2 API is experimental; breaking changes are expected." in page + assert "POST' + " [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy)" + in out.read_text() + ) + + +def test_main_single_snapshot_renders_empty_shell(wired_main): + run, out, state = wired_main + state["snapshots"] = snapshots_for(("aaa", "2026-07-14")) + assert run() == 0 + content = out.read_text() + assert " Date: Tue, 18 Aug 2026 11:35:37 -0700 Subject: [PATCH 02/27] feat(api-reference): clickable endpoint pills, chip-width Change column Replace the split method-badge + underlined path link with a single Badge pill containing method and path, wrapped in a raw anchor (a markdown link would keep Mintlify's bottom-border underline). elements after each path segment let long paths wrap inside the pill instead of overflowing the table, without adding characters that survive copy-paste; path braces are entity-escaped for JSX. Tables gain an api-changelog-table wrapper div styled in styles.css: Mintlify puts min-width: 150px on every td, which held the Change column open well past its chip. --- docs/api-reference/v1/Changelog.mdx | 172 ++++++++++--------- docs/api-reference/v2/Changelog.mdx | 164 +++++++++++------- docs/styles.css | 11 ++ scripts/generate_api_changelog.py | 48 +++++- scripts/tests/test_generate_api_changelog.py | 54 +++++- 5 files changed, 294 insertions(+), 155 deletions(-) diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx index 9116aced58..a5ade4db96 100644 --- a/docs/api-reference/v1/Changelog.mdx +++ b/docs/api-reference/v1/Changelog.mdx @@ -13,101 +13,117 @@ are labeled; documentation-only edits are not listed. ## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | -| Deprecated | endpoint deprecated | GET [`/api/v1/deployments/{deploymentId}/policies`](/api-reference/v1/policiesservice/list-policies) | -| Deprecated | endpoint deprecated | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | -| Deprecated | endpoint deprecated | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies | +| Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Deprecated | endpoint deprecated | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | + +
## Breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Changed | the `deployments/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments`](/api-reference/v1/deploymentsservice/list-deployments) | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Changed | the `dependencyFilter/repositoryId/items/` request property `format` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Changed | the `format` of the request properties `cursor`, `deploymentId` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Changed | the `cursor` response's property `format` changed from `uint64` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | -| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | -| Changed | the `format` of the request properties `cursor`, `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | -| Changed | the `format` of the response properties `cursor`, `repositorySummaries/items/id`, `repositorySummaries/items/numDependencies` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | -| Changed | for the `path` request parameters `deploymentId`, `repositoryId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | -| Changed | the `format` of the request properties `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | -| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | -| Changed | the `lockfileSummaries/items/numDependencies` response's property `format` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/policies`](/api-reference/v1/policiesservice/list-policies) | -| Changed | the `policies/items/id` response's property `format` changed from `uint64` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentId}/policies`](/api-reference/v1/policiesservice/list-policies) | -| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | -| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | -| Changed | the `format` of the response properties `policy/id`, `rules/items/id` changed from `uint64` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/list-policy-rules) | -| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | -| Changed | the `format` of the request properties `deploymentId`, `policyId` changed from `uint64` to `int64` | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | -| Changed | the `format` of the response properties `policyId`, `updatedRule/id` changed from `uint64` to `int64` for status `200` | PUT [`/api/v1/deployments/{deploymentId}/policies/{policyId}`](/api-reference/v1/policiesservice/update-policy) | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | -| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | -| Changed | for the `path` request parameters `deploymentId`, `scanId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/scan/{scanId}`](/api-reference/v1/scansservice/get-scan-details) | -| Changed | the `format` of the response properties `deployment_id`, `exit_code`, `id`, `repository_id` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentId}/scan/{scanId}`](/api-reference/v1/scansservice/get-scan-details) | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | -| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | -| Changed | the `format` of the response properties `scans/items/deployment_id`, `scans/items/findings_counts/code`, `scans/items/findings_counts/secrets`, `scans/items/findings_counts/supply_chain`, `scans/items/findings_counts/total`, `scans/items/id`, `scans/items/repository_id` changed from `uint64` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET [`/api/v1/deployments/{deploymentId}/secrets`](/api-reference/v1/secretsservice/list-secrets) | -| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentId}/secrets`](/api-reference/v1/secretsservice/list-secrets) | -| Changed | for the `path` request parameter `externalTicketId`, the `format` was changed from `uint32` to `int64` | DELETE [`/api/v1/deployments/{deploymentId}/ticketing/v2/tickets/{externalTicketId}`](/api-reference/v1/ticketingservice/unlink-a-jira-ticket) | -| Changed | for the `query` request parameter `repository_ids`, the `format` of property `items/` was narrowed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET [`/api/v1/deployments/{deploymentSlug}/projects`](/api-reference/v1/projectsservice/list-all-projects) | -| Changed | the `projects/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentSlug}/projects`](/api-reference/v1/projectsservice/list-all-projects) | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}`](/api-reference/v1/projectsservice/get-project-details) | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}`](/api-reference/v1/projectsservice/update-project-details) | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}/managed-scan`](/api-reference/v1/projectsservice/toggle-managed-scans-for-a-project) | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | DELETE [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags`](/api-reference/v1/projectsservice/remove-tags-from-project) | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PUT [`/api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags`](/api-reference/v1/projectsservice/add-tags-to-project) | -| Changed | the `limit` request property `format` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | -| Changed | the `format` of the response properties `failed/items/issue_ids/items/`, `skipped/items/issue_ids/items/`, `succeeded/items/issue_ids/items/`, `succeeded/items/ticket_id` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | -| Changed | the `format` of the request properties `issue_ids/items/`, `limit` changed from `uint32` to `int64` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | -| Changed | the `format` of the response properties `num_triaged`, `triaged_issues/items/` changed from `uint32` to `int64` for status `200` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | +| Changed | the `deployments/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Changed | the `dependencyFilter/repositoryId/items/` request property `format` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Changed | the `format` of the request properties `cursor`, `deploymentId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Changed | the `cursor` response's property `format` changed from `uint64` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Changed | the `format` of the request properties `cursor`, `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Changed | the `format` of the response properties `cursor`, `repositorySummaries/items/id`, `repositorySummaries/items/numDependencies` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Changed | for the `path` request parameters `deploymentId`, `repositoryId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Changed | the `format` of the request properties `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Changed | the `lockfileSummaries/items/numDependencies` response's property `format` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies | +| Changed | the `policies/items/id` response's property `format` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies | +| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | the `format` of the response properties `policy/id`, `rules/items/id` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | the `format` of the request properties `deploymentId`, `policyId` changed from `uint64` to `int64` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | the `format` of the response properties `policyId`, `updatedRule/id` changed from `uint64` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/sbom/export | +| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/sbom/export | +| Changed | for the `path` request parameters `deploymentId`, `scanId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/scan/{scanId} | +| Changed | the `format` of the response properties `deployment_id`, `exit_code`, `id`, `repository_id` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/scan/{scanId} | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Changed | the `format` of the response properties `scans/items/deployment_id`, `scans/items/findings_counts/code`, `scans/items/findings_counts/secrets`, `scans/items/findings_counts/supply_chain`, `scans/items/findings_counts/total`, `scans/items/id`, `scans/items/repository_id` changed from `uint64` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/secrets | +| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentId}/secrets | +| Changed | for the `path` request parameter `externalTicketId`, the `format` was changed from `uint32` to `int64` | DELETE /api/v1/deployments/{deploymentId}/ticketing/v2/tickets/{externalTicketId} | +| Changed | for the `query` request parameter `repository_ids`, the `format` of property `items/` was narrowed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/projects | +| Changed | the `projects/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects/{projectName} | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName} | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName}/managed-scan | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | DELETE /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | +| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | +| Changed | the `limit` request property `format` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentSlug}/tickets | +| Changed | the `format` of the response properties `failed/items/issue_ids/items/`, `skipped/items/issue_ids/items/`, `succeeded/items/issue_ids/items/`, `succeeded/items/ticket_id` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentSlug}/tickets | +| Changed | the `format` of the request properties `issue_ids/items/`, `limit` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentSlug}/triage | +| Changed | the `format` of the response properties `num_triaged`, `triaged_issues/items/` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentSlug}/triage | + +
## Potentially breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the `dependencies/items/ecosystem` response property for the response status `200` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Removed | removed the request property `formatVersion/version` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | -| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the `scans/items/status` response property for the response status `200` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | -| Added | added the new `SCM_TYPE_HARNESS` enum value to the `findings/items/repository/allOf[#/components/schemas/protos.secrets.v1.SecretsFinding_Repository]/scmType` response property for the response status `200` | GET [`/api/v1/deployments/{deploymentId}/secrets`](/api-reference/v1/secretsservice/list-secrets) | -| Removed | removed the optional property `sastFindings` from the response with the `200` status | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Removed | removed the optional property `scaFindings` from the response with the `200` status | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Removed | removed the request property `deploymentSlug` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the `dependencies/items/ecosystem` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Removed | removed the request property `formatVersion/version` | POST /api/v1/deployments/{deploymentId}/sbom/export | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the `scans/items/status` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | added the new `SCM_TYPE_HARNESS` enum value to the `findings/items/repository/allOf[#/components/schemas/protos.secrets.v1.SecretsFinding_Repository]/scmType` response property for the response status `200` | GET /api/v1/deployments/{deploymentId}/secrets | +| Removed | removed the optional property `sastFindings` from the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | +| Removed | removed the optional property `scaFindings` from the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | +| Removed | removed the request property `deploymentSlug` | POST /api/v1/deployments/{deploymentSlug}/triage | + +
## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST [`/api/v1/deployments/{deploymentId}/dependencies`](/api-reference/v1/supplychainservice/list-dependencies) | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories`](/api-reference/v1/supplychainservice/list-repositories-with-dependencies) | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST [`/api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles`](/api-reference/v1/supplychainservice/list-lockfiles-in-a-given-repository-with-dependencies) | -| Added | added the new optional request property `formatVersion/cyclonedxVersion` | POST [`/api/v1/deployments/{deploymentId}/sbom/export`](/api-reference/v1/supplychainservice/create-a-new-sbom-export-job) | -| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses/items/` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | -| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses` | POST [`/api/v1/deployments/{deploymentId}/scans/search`](/api-reference/v1/scansservice/list-scans-beta) | -| Added | endpoint added | POST [`/api/v1/deployments/{deploymentId}/tickets/link`](/api-reference/v1/ticketingservice/link-an-existing-ticket-to-findings) | -| Added | endpoint added | POST [`/api/v1/deployments/{deploymentId}/tickets/unlink`](/api-reference/v1/ticketingservice/unlink-a-ticket-from-findings) | -| Added | added the new enum value `ai_sast` to the `query` request parameter `issue_type` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Added | added the new enum value `duplicate` to the `query` request parameter `triage_reasons` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Added | added the new enum value `provisionally_ignored` to the `query` request parameter `status` | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Added | added the optional property `findings` to the response with the `200` status | GET [`/api/v1/deployments/{deploymentSlug}/findings`](/api-reference/v1/findingsservice/list-code-supply-chain-or-ai-powered-scan-findings) | -| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | -| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST [`/api/v1/deployments/{deploymentSlug}/tickets`](/api-reference/v1/ticketingservice/create-jira-tickets) | -| Added | added the new `duplicate` enum value to the request property `new_triage_reason` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | -| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | -| Added | added the new `provisionally_ignored` enum value to the request property `new_triage_state` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | -| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST [`/api/v1/deployments/{deploymentSlug}/triage`](/api-reference/v1/triageservice/bulk-triage) | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Added | added the new optional request property `formatVersion/cyclonedxVersion` | POST /api/v1/deployments/{deploymentId}/sbom/export | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses/items/` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | endpoint added | POST /api/v1/deployments/{deploymentId}/tickets/link | +| Added | endpoint added | POST /api/v1/deployments/{deploymentId}/tickets/unlink | +| Added | added the new enum value `ai_sast` to the `query` request parameter `issue_type` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the new enum value `duplicate` to the `query` request parameter `triage_reasons` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the new enum value `provisionally_ignored` to the `query` request parameter `status` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the optional property `findings` to the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST /api/v1/deployments/{deploymentSlug}/tickets | +| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST /api/v1/deployments/{deploymentSlug}/tickets | +| Added | added the new `duplicate` enum value to the request property `new_triage_reason` | POST /api/v1/deployments/{deploymentSlug}/triage | +| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST /api/v1/deployments/{deploymentSlug}/triage | +| Added | added the new `provisionally_ignored` enum value to the request property `new_triage_state` | POST /api/v1/deployments/{deploymentSlug}/triage | +| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST /api/v1/deployments/{deploymentSlug}/triage | + +
diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index 4f8c72e52f..fb2ff74b6f 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -13,113 +13,145 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental ## Potentially breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `FIX_JOB_ERROR_TYPE_AI_PROVIDER_NOT_ALLOWED`, `FIX_JOB_ERROR_TYPE_ARCHIVED_REPOSITORY`, `FIX_JOB_ERROR_TYPE_AUTOFIX_DISABLED`, `FIX_JOB_ERROR_TYPE_GROUPING_DISABLED`, `FIX_JOB_ERROR_TYPE_INVALID_GROUP`, `FIX_JOB_ERROR_TYPE_NON_PRIMARY_BRANCH_DISABLED`, `FIX_JOB_ERROR_TYPE_UNSUPPORTED_ISSUE_TYPE` enum values to the `errors/items/errorType` response property for the response status `200` | POST [`/api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs`](/api-reference/v2/aifixjobsservice/create-fix-job) | -| Removed | removed the optional property `instances/items/transitionStates` from the response with the `200` status | GET [`/api/notifications/deployments/{deploymentId}/ticketing/v2`](/api-reference/v2/externalticketingservice/get-external-ticketing-instances) | +| Added | added the new `FIX_JOB_ERROR_TYPE_AI_PROVIDER_NOT_ALLOWED`, `FIX_JOB_ERROR_TYPE_ARCHIVED_REPOSITORY`, `FIX_JOB_ERROR_TYPE_AUTOFIX_DISABLED`, `FIX_JOB_ERROR_TYPE_GROUPING_DISABLED`, `FIX_JOB_ERROR_TYPE_INVALID_GROUP`, `FIX_JOB_ERROR_TYPE_NON_PRIMARY_BRANCH_DISABLED`, `FIX_JOB_ERROR_TYPE_UNSUPPORTED_ISSUE_TYPE` enum values to the `errors/items/errorType` response property for the response status `200` | POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs | +| Removed | removed the optional property `instances/items/transitionStates` from the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/ticketing/v2 | + +
## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the optional property `errorDetail` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test`](/api-reference/v2/notificationrulesservice/test-notification-rule) | -| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/products`](/api-reference/v2/deploymentproductsservice/get-deployment-product-config) | -| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | PUT [`/api/agent/deployments/{deploymentId}/products`](/api-reference/v2/deploymentproductsservice/upsert-deployment-product-config) | -| Added | added the optional property `errorDetail` to the response with the `200` status | POST [`/api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test`](/api-reference/v2/notificationwebhooksservice/test-notification-webhook) | -| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | -| Added | added the new optional request property `automation/actions/items/id` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new optional request property `automation/actions/items/id` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the optional property `instances/items/fixedTransitionState` to the response with the `200` status | GET [`/api/notifications/deployments/{deploymentId}/ticketing/v2`](/api-reference/v2/externalticketingservice/get-external-ticketing-instances) | -| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test | +| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products | +| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | PUT /api/agent/deployments/{deploymentId}/products | +| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test | +| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new optional request property `automation/actions/items/id` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new optional request property `automation/actions/items/id` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `instances/items/fixedTransitionState` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/ticketing/v2 | +| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET /api/v2/deployments/{deploymentId}/automations | + +
## Potentially breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the `settings/tourStates/items/id` response property for the response status `200` | GET [`/api/auth/users/current/settings`](/api-reference/v2/usersservice/get-user-settings) | +| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the `settings/tourStates/items/id` response property for the response status `200` | GET /api/auth/users/current/settings | + +
## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the request property `tourStates/items/id` | PATCH [`/api/auth/users/current/settings`](/api-reference/v2/usersservice/update-user-settings) | -| Added | added the new `WorkflowsActivationBanner` enum value to the request property `eventSource` | PATCH [`/api/auth/users/current/settings`](/api-reference/v2/usersservice/update-user-settings) | +| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the request property `tourStates/items/id` | PATCH /api/auth/users/current/settings | +| Added | added the new `WorkflowsActivationBanner` enum value to the request property `eventSource` | PATCH /api/auth/users/current/settings | + +
## Potentially breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | + +
## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | + +
## Potentially breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET [`/api/v2/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/list-automations) | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | + +
## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `includeFirstDetectedAt` | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | -| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | -| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | -| Added | added the optional property `issues/items/issue/firstDetectedAt` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/issues`](/api-reference/v2/issuesservice/list-issues) | -| Added | added the new optional request property `includeFirstDetectedAt` | POST [`/api/agent/deployments/{deploymentId}/issues/export`](/api-reference/v2/issuesservice/export-issues) | -| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/issues/v2/{issueId}`](/api-reference/v2/issuesservice/get-issue) | -| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/issues/v2/{issueId}`](/api-reference/v2/issuesservice/get-issue) | -| Added | added the optional property `firstDetectedAt` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/issues/v2/{issueId}`](/api-reference/v2/issuesservice/get-issue) | -| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | GET [`/api/agent/deployments/{deploymentId}/users`](/api-reference/v2/deploymentservice/get-deployment-users) | -| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | POST [`/api/agent/deployments/{deploymentId}/users/list`](/api-reference/v2/deploymentservice/list-deployment-users) | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST [`/api/notifications/deployments/{deploymentId}/automations`](/api-reference/v2/automationsservice/create-automation) | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT [`/api/notifications/deployments/{deploymentId}/automations/{automationId}`](/api-reference/v2/automationsservice/update-automation) | -| Added | added the new optional request property `issueTypes` | POST [`/api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets`](/api-reference/v2/externalticketingservice/create-external-tickets) | -| Deprecated | endpoint deprecated | GET [`/api/policies/v1/deployments/{deploymentId}`](/api-reference/v2/policiesservice/list-policies) | -| Deprecated | endpoint deprecated | GET [`/api/policies/v1/deployments/{deploymentId}/{policyId}/rules`](/api-reference/v2/policiesservice/list-policy-rules) | -| Deprecated | endpoint deprecated | PUT [`/api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath}`](/api-reference/v2/policiesservice/update-policy-rule) | -| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST [`/api/reporting/{deploymentId}/findings`](/api-reference/v2/reportsservice/get-report-findings) | -| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST [`/api/reporting/{deploymentId}/findings`](/api-reference/v2/reportsservice/get-report-findings) | -| Added | added the optional property `findings/items/firstDetectedAt` to the response with the `200` status | POST [`/api/reporting/{deploymentId}/findings`](/api-reference/v2/reportsservice/get-report-findings) | -| Added | added the new optional request property `filters/timeBucketSize` | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory`](/api-reference/v2/reportsservice/get-malware-firewall-blocked-malicious-dependencies-by-advisory-report) | -| Added | added the new optional request property `filters/timeBucketSize` | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem`](/api-reference/v2/reportsservice/get-malware-firewall-blocked-malicious-dependencies-by-dependency-version-and-ecosystem-report) | -| Added | added the new optional request property `filters/timeBucketSize` | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem`](/api-reference/v2/reportsservice/get-malware-firewall-blocked-malicious-dependencies-by-ecosystem-report) | -| Added | endpoint added | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/scan-activity`](/api-reference/v2/reportsservice/get-malware-firewall-scan-activity-report) | -| Added | endpoint added | POST [`/api/reporting/{deploymentId}/reports/malware-firewall/summary`](/api-reference/v2/reportsservice/get-malware-firewall-summary-report) | -| Added | added the new optional `query` request parameter `dependencyFilter.includeArchived` | GET [`/api/sca/deployments/{deploymentId}/dependencies`](/api-reference/v2/supplychain2service/list-dependencies) | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/sca/deployments/{deploymentId}/dependencies`](/api-reference/v2/supplychain2service/list-dependencies) | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/sca/deployments/{deploymentId}/repositories`](/api-reference/v2/supplychain2service/list-repositories-with-dependencies) | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST [`/api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles`](/api-reference/v2/supplychain2service/list-lockfiles-with-dependencies-in-a-given-repository) | +| Added | added the new optional request property `includeFirstDetectedAt` | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the optional property `issues/items/issue/firstDetectedAt` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the new optional request property `includeFirstDetectedAt` | POST /api/agent/deployments/{deploymentId}/issues/export | +| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | +| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | +| Added | added the optional property `firstDetectedAt` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | +| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/users | +| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/users/list | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new optional request property `issueTypes` | POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets | +| Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId} | +| Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules | +| Deprecated | endpoint deprecated | PUT /api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath} | +| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | +| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | +| Added | added the optional property `findings/items/firstDetectedAt` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | +| Added | added the new optional request property `filters/timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory | +| Added | added the new optional request property `filters/timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem | +| Added | added the new optional request property `filters/timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem | +| Added | endpoint added | POST /api/reporting/{deploymentId}/reports/malware-firewall/scan-activity | +| Added | endpoint added | POST /api/reporting/{deploymentId}/reports/malware-firewall/summary | +| Added | added the new optional `query` request parameter `dependencyFilter.includeArchived` | GET /api/sca/deployments/{deploymentId}/dependencies | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/sca/deployments/{deploymentId}/dependencies | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/sca/deployments/{deploymentId}/repositories | +| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles | + +
diff --git a/docs/styles.css b/docs/styles.css index c468805cc4..98dc71fd0e 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -13,3 +13,14 @@ h1 { .dark [data-component-part="accordion-button"]:hover { background-color: #1f2d44 !important; } + +/* API changelog tables (generated by scripts/generate_api_changelog.py, + which emits the wrapper class): hug the Change column to its chip. + Mintlify's table styling puts min-width: 150px on every td, which is + what holds the column open -- hence the !important override. */ +.api-changelog-table th:first-child, +.api-changelog-table td:first-child { + min-width: 0 !important; + width: 1%; + white-space: nowrap; +} diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index 23ed3eb536..237e0c0997 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -445,15 +445,58 @@ def _endpoint_cell(key: tuple, links: dict) -> str: return f'{operation} {target}' +def _escape_jsx_text(text: str) -> str: + """Escape text placed as JSX element children (Badge content). + + Braces open JSX expressions and angle brackets open tags; HTML entities + are decoded by the JSX runtime, so they render as the literal characters. + """ + text = text.replace("&", "&") + text = text.replace("<", "<").replace(">", ">") + return text.replace("{", "{").replace("}", "}") + + +def _endpoint_pill(key: tuple, links: dict) -> str: + """Table style's endpoint cell: one clickable pill with method + path. + + A raw rather than a markdown link because Mintlify underlines links + with a bottom border, which the inline style suppresses. after + each path segment lets the pill wrap instead of overflowing the table + (paths are single unbreakable tokens otherwise) without inserting + characters that would survive copy-paste. + """ + if key[0] == 1: # General bucket: schema/security changes, no endpoint + return "—" + operation, path = key[2], key[1] + color = METHOD_BADGE_COLORS.get(operation, "gray") + escaped = _escape_jsx_text(path) + wrappable = escaped[0] + escaped[1:].replace("/", "/") + badge = f'{operation} {wrappable}' + url = links.get((operation, path)) + if not url: + return badge + return ( + f'' + f"{badge}" + ) + + def _render_section_table(changes: list, links: dict) -> list: """One table for a severity section: Change | Description | Endpoint.""" groups: dict[tuple, list] = {} for change in changes: groups.setdefault(_endpoint_key(change), []).append(change) - lines = ["| Change | Description | Endpoint |", "|---|---|---|"] + # The div carries a hook for docs/styles.css, which hugs the Change + # column to its chip; plain markdown tables offer no width control. + lines = [ + '
', + "", + "| Change | Description | Endpoint |", + "|---|---|---|", + ] for key in sorted(groups): - cell = _endpoint_cell(key, links) + cell = _endpoint_pill(key, links) for change in sorted(groups[key], key=lambda c: (c["id"], c["text"])): verb, color = _change_verb(change) description = escape_mdx(change["text"]).replace("|", "\\|") @@ -461,6 +504,7 @@ def _render_section_table(changes: list, links: dict) -> list: f'| {verb}' f" | {description} | {cell} |" ) + lines.extend(["", "
"]) return lines diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index 06572c3185..6a29c1011c 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -383,32 +383,48 @@ def test_render_page_golden_list_style(): ## Breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Removed | api path removed without deprecation | GET `/api/v1/agents` | -| Removed | removed the required property `tags` from the response with the `200` status | GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects) | +| Removed | api path removed without deprecation | GET /api/v1/agents | +| Removed | removed the required property `tags` from the response with the `200` status | GET /api/v1/deployments/{deployment_id}/projects | + +
## Potentially breaking changes +
+ | Change | Description | Endpoint | |---|---|---| -| Removed | deleted the `query` request parameter `page_token` | GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects) | +| Removed | deleted the `query` request parameter `page_token` | GET /api/v1/deployments/{deployment_id}/projects | + +
## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional `query` request parameter `page_size` | GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects) | -| Added | endpoint added | POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy) | +| Added | added the new optional `query` request parameter `page_size` | GET /api/v1/deployments/{deployment_id}/projects | +| Added | endpoint added | POST /api/v1/policies | | Removed | removed the schema `ProjectFilter` | — | + +
## Changes +
+ | Change | Description | Endpoint | |---|---|---| -| Added | endpoint added | POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy) | +| Added | endpoint added | POST /api/v1/policies | + +
""" @@ -484,6 +500,25 @@ def test_render_update_nests_multiple_changes_per_endpoint(): ) in rendered +def test_endpoint_pill_escapes_braces_and_marks_wrap_points(): + key = (0, "/api/v1/deps/{depId}/files", "GET") + links = {("GET", "/api/v1/deps/{depId}/files"): "/api-reference/v1/depsservice/list-files"} + pill = gac._endpoint_pill(key, links) + # entire pill is the link: raw anchor (markdown links keep Mintlify's + # underline border), underline suppressed, JSX-hazardous braces as + # entities, wrap opportunities after each path segment + assert pill == ( + '' + 'GET /api/v1/deps' + "/{depId}/files" + ) + assert gac._endpoint_pill(key, {}) == ( + 'GET /api/v1/deps' + "/{depId}/files" + ) + + def test_render_update_method_badge_colors(): def change(operation, path): return { @@ -496,7 +531,7 @@ def change(operation, path): links={}, style="list", ) - assert 'DELETE' in rendered + assert 'DELETE' in rendered # list style keeps split pill assert 'BREW' in rendered # unknown method @@ -647,8 +682,9 @@ def test_main_link_base_links_endpoints(wired_main, tmp_path): ) assert run("--link-base", "/api-reference/v1") == 0 assert ( - 'POST' - " [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy)" + '' + 'POST /api/v1/policies' in out.read_text() ) From ce52680ad735ad92d79766a4ae4d6d46d4ea46ad Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Wed, 2 Sep 2026 23:22:04 -0700 Subject: [PATCH 03/27] fix(api-reference): make changelog property paths readable Rewrite oasdiff's JSON Schema property paths into the field reference a consumer actually writes, and stop filing routine enum additions under a breaking-change heading. - Drop `allOf[#/components/schemas/protos.*]` segments. These specs are protobuf-generated and wrap every described $ref in a single-element allOf (the OpenAPI 3.0 workaround for $ref siblings being ignored), so the segment is composition bookkeeping that the wire payload has no level for. Removing it is more accurate as well as shorter, and it was the only place an internal proto package name reached the page. - Fold an `items` segment into its parent as `[]` and join with `.`, so `automations/items/filters/allOf[...]/conditions/items/type` renders as `automations[].filters.conditions[].type`. - Demote `response-property-enum-value-added` to INFO. oasdiff rates it WARN on the theory that a consumer may switch exhaustively over the enum, but these enums come from protobuf, where they are open by construction; 46 routine additions were burying the actual removals. Field-name case is deliberately untouched: the specs emit both `deployment_id` and `deploymentId` depending on the service, and the changelog has to name the key the endpoint actually accepts. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v1/Changelog.mdx | 110 ++++++---- docs/api-reference/v2/Changelog.mdx | 217 +++++++++++++------ scripts/generate_api_changelog.py | 79 +++++++ scripts/tests/test_generate_api_changelog.py | 113 ++++++++++ 4 files changed, 414 insertions(+), 105 deletions(-) diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx index a5ade4db96..07a562f8a3 100644 --- a/docs/api-reference/v1/Changelog.mdx +++ b/docs/api-reference/v1/Changelog.mdx @@ -10,6 +10,30 @@ Changes to the [Semgrep API v1](/api-reference/v1/Introduction), detected by comparing successive versions of its OpenAPI specification. Breaking changes are labeled; documentation-only edits are not listed. + +## Breaking changes + +
+ +| Change | Description | Endpoint | +|---|---|---| +| Removed | removed the success response with the status `200` | POST /api/v1/deployments/{deploymentId}/sbom/export | + +
+ +## Changes + +
+ +| Change | Description | Endpoint | +|---|---|---| +| Added | added the success response with the status `202` | POST /api/v1/deployments/{deploymentId}/sbom/export | +| Added | added the new optional request property `is_partial_scan` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | added the optional property `scans[].is_partial_scan` to the response with the `200` status | POST /api/v1/deployments/{deploymentId}/scans/search | + +
+
+ ## Changes @@ -17,9 +41,9 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | | Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies | | Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | | Deprecated | endpoint deprecated | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | @@ -27,57 +51,57 @@ are labeled; documentation-only edits are not listed. - + ## Breaking changes
| Change | Description | Endpoint | |---|---|---| -| Changed | the `deployments/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments | +| Changed | the `deployments[].id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments | | Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Changed | the `dependencyFilter/repositoryId/items/` request property `format` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Changed | the `dependencyFilter.repositoryId[]` request property `format` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | | Changed | the `format` of the request properties `cursor`, `deploymentId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | | Changed | the `cursor` response's property `format` changed from `uint64` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies | | Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | | Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Changed | the `format` of the request properties `cursor`, `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Changed | the `format` of the response properties `cursor`, `repositorySummaries/items/id`, `repositorySummaries/items/numDependencies` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Changed | the `format` of the request properties `cursor`, `dependencyFilter.repositoryId[]`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Changed | the `format` of the response properties `cursor`, `repositorySummaries[].id`, `repositorySummaries[].numDependencies` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | | Changed | for the `path` request parameters `deploymentId`, `repositoryId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | -| Changed | the `format` of the request properties `dependencyFilter/repositoryId/items/`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Changed | the `format` of the request properties `dependencyFilter.repositoryId[]`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | | Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | -| Changed | the `lockfileSummaries/items/numDependencies` response's property `format` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Changed | the `lockfileSummaries[].numDependencies` response's property `format` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | | Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies | -| Changed | the `policies/items/id` response's property `format` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies | +| Changed | the `policies[].id` response's property `format` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies | | Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | | Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | -| Changed | the `format` of the response properties `policy/id`, `rules/items/id` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | the `format` of the response properties `policy.id`, `rules[].id` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | | Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | | Changed | the `format` of the request properties `deploymentId`, `policyId` changed from `uint64` to `int64` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | -| Changed | the `format` of the response properties `policyId`, `updatedRule/id` changed from `uint64` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | the `format` of the response properties `policyId`, `updatedRule.id` changed from `uint64` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | | Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/sbom/export | | Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/sbom/export | | Changed | for the `path` request parameters `deploymentId`, `scanId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/scan/{scanId} | | Changed | the `format` of the response properties `deployment_id`, `exit_code`, `id`, `repository_id` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/scan/{scanId} | | Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/scans/search | | Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/scans/search | -| Changed | the `format` of the response properties `scans/items/deployment_id`, `scans/items/findings_counts/code`, `scans/items/findings_counts/secrets`, `scans/items/findings_counts/supply_chain`, `scans/items/findings_counts/total`, `scans/items/id`, `scans/items/repository_id` changed from `uint64` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Changed | the `format` of the response properties `scans[].deployment_id`, `scans[].findings_counts.code`, `scans[].findings_counts.secrets`, `scans[].findings_counts.supply_chain`, `scans[].findings_counts.total`, `scans[].id`, `scans[].repository_id` changed from `uint64` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | | Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/secrets | | Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentId}/secrets | | Changed | for the `path` request parameter `externalTicketId`, the `format` was changed from `uint32` to `int64` | DELETE /api/v1/deployments/{deploymentId}/ticketing/v2/tickets/{externalTicketId} | -| Changed | for the `query` request parameter `repository_ids`, the `format` of property `items/` was narrowed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Changed | for the `query` request parameter `repository_ids`, the `format` of property `items` was narrowed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | | Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/projects | -| Changed | the `projects/items/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects/{projectName} | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName} | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName}/managed-scan | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | DELETE /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | -| Changed | the `project/id` response's property `format` changed from `uint32` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | +| Changed | the `projects[].id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects | +| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects/{projectName} | +| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName} | +| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName}/managed-scan | +| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | DELETE /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | +| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | | Changed | the `limit` request property `format` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentSlug}/tickets | -| Changed | the `format` of the response properties `failed/items/issue_ids/items/`, `skipped/items/issue_ids/items/`, `succeeded/items/issue_ids/items/`, `succeeded/items/ticket_id` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentSlug}/tickets | -| Changed | the `format` of the request properties `issue_ids/items/`, `limit` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentSlug}/triage | -| Changed | the `format` of the response properties `num_triaged`, `triaged_issues/items/` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentSlug}/triage | +| Changed | the `format` of the response properties `failed[].issue_ids[]`, `skipped[].issue_ids[]`, `succeeded[].issue_ids[]`, `succeeded[].ticket_id` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentSlug}/tickets | +| Changed | the `format` of the request properties `issue_ids[]`, `limit` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentSlug}/triage | +| Changed | the `format` of the response properties `num_triaged`, `triaged_issues[]` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentSlug}/triage |
@@ -87,12 +111,9 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the `dependencies/items/ecosystem` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Removed | removed the request property `formatVersion/version` | POST /api/v1/deployments/{deploymentId}/sbom/export | -| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the `scans/items/status` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | -| Added | added the new `SCM_TYPE_HARNESS` enum value to the `findings/items/repository/allOf[#/components/schemas/protos.secrets.v1.SecretsFinding_Repository]/scmType` response property for the response status `200` | GET /api/v1/deployments/{deploymentId}/secrets | -| Removed | removed the optional property `sastFindings` from the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | -| Removed | removed the optional property `scaFindings` from the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | +| Removed | removed the request property `formatVersion.version` | POST /api/v1/deployments/{deploymentId}/sbom/export | +| Removed | removed the optional property `sastFindings` from the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | +| Removed | removed the optional property `scaFindings` from the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | | Removed | removed the request property `deploymentSlug` | POST /api/v1/deployments/{deploymentSlug}/triage | @@ -103,21 +124,24 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem/items/` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter/ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | -| Added | added the new optional request property `formatVersion/cyclonedxVersion` | POST /api/v1/deployments/{deploymentId}/sbom/export | -| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses/items/` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem[]` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the `dependencies[].ecosystem` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/dependencies | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem[]` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem[]` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Added | added the new optional request property `formatVersion.cyclonedxVersion` | POST /api/v1/deployments/{deploymentId}/sbom/export | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses[]` | POST /api/v1/deployments/{deploymentId}/scans/search | | Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the `scans[].status` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | added the new `SCM_TYPE_HARNESS` enum value to the `findings[].repository.scmType` response property for the response status `200` | GET /api/v1/deployments/{deploymentId}/secrets | | Added | endpoint added | POST /api/v1/deployments/{deploymentId}/tickets/link | | Added | endpoint added | POST /api/v1/deployments/{deploymentId}/tickets/unlink | -| Added | added the new enum value `ai_sast` to the `query` request parameter `issue_type` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Added | added the new enum value `duplicate` to the `query` request parameter `triage_reasons` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Added | added the new enum value `provisionally_ignored` to the `query` request parameter `status` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Added | added the optional property `findings` to the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the new enum value `ai_sast` to the `query` request parameter `issue_type` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the new enum value `duplicate` to the `query` request parameter `triage_reasons` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the new enum value `provisionally_ignored` to the `query` request parameter `status` | GET /api/v1/deployments/{deploymentSlug}/findings | +| Added | added the optional property `findings` to the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | | Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST /api/v1/deployments/{deploymentSlug}/tickets | | Added | added the new `provisionally_ignored` enum value to the request property `status` | POST /api/v1/deployments/{deploymentSlug}/tickets | | Added | added the new `duplicate` enum value to the request property `new_triage_reason` | POST /api/v1/deployments/{deploymentSlug}/triage | diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index fb2ff74b6f..a6ed5f9234 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -10,109 +10,134 @@ Changes to the [Semgrep API v2](/api-reference/v2/Introduction), detected by comparing successive versions of its OpenAPI specification. Breaking changes are labeled; documentation-only edits are not listed. The v2 API is experimental; breaking changes are expected while it is in alpha. - -## Potentially breaking changes + +## Breaking changes
| Change | Description | Endpoint | |---|---|---| -| Added | added the new `FIX_JOB_ERROR_TYPE_AI_PROVIDER_NOT_ALLOWED`, `FIX_JOB_ERROR_TYPE_ARCHIVED_REPOSITORY`, `FIX_JOB_ERROR_TYPE_AUTOFIX_DISABLED`, `FIX_JOB_ERROR_TYPE_GROUPING_DISABLED`, `FIX_JOB_ERROR_TYPE_INVALID_GROUP`, `FIX_JOB_ERROR_TYPE_NON_PRIMARY_BRANCH_DISABLED`, `FIX_JOB_ERROR_TYPE_UNSUPPORTED_ISSUE_TYPE` enum values to the `errors/items/errorType` response property for the response status `200` | POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs | -| Removed | removed the optional property `instances/items/transitionStates` from the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/ticketing/v2 | +| Added | added the new required `header` request parameter `if-match` | PUT /api/policies/v2/deployments/{deploymentId}/remediation-policies |
+
+ ## Changes
| Change | Description | Endpoint | |---|---|---| -| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test | -| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products | -| Added | added the optional property `products/ai/mdr_enabled` to the response with the `200` status | PUT /api/agent/deployments/{deploymentId}/products | -| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test | -| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new optional request property `automation/actions/items/id` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new optional request property `automation/actions/items/id` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the optional property `automation/actions/items/id` to the response with the `200` status | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the optional property `instances/items/fixedTransitionState` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/ticketing/v2 | -| Added | added the optional property `automations/items/actions/items/id` to the response with the `200` status | GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the optional property `gitSha` to the response with the `200` status | GET /api/health-check | +| Added | endpoint added | GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/findings | +| Added | endpoint added | GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/overview | +| Added | endpoint added | POST /api/workflows/v1/deployments/{deploymentId}/jobs/list | +| Added | endpoint added | GET /api/workflows/v1/deployments/{deploymentId}/registry | +| Added | endpoint added | POST /api/workflows/v1/deployments/{deploymentId}/trigger |
- -## Potentially breaking changes + +## Changes
| Change | Description | Endpoint | |---|---|---| -| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the `settings/tourStates/items/id` response property for the response status `200` | GET /api/auth/users/current/settings | +| Added | added the optional property `byProject.additionalProperties.projectId` to the response with the `200` status | POST /api/reporting/{deploymentId}/reports/by-project/by-product | +| Added | added the optional property `byProject.additionalProperties.projectId` to the response with the `200` status | POST /api/reporting/{deploymentId}/reports/by-project/by-severity |
+
+ ## Changes
| Change | Description | Endpoint | |---|---|---| -| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the request property `tourStates/items/id` | PATCH /api/auth/users/current/settings | -| Added | added the new `WorkflowsActivationBanner` enum value to the request property `eventSource` | PATCH /api/auth/users/current/settings | +| Added | added the new optional request property `filters.latestScanStatus` | POST /api/agent/deployments/{deploymentId}/repos/_provision | +| Added | added the new optional request property `filters.latestScanStatus` | POST /api/agent/deployments/{deploymentId}/repos/_secret | +| Added | added the new optional request property `filters.latestScanStatus` | POST /api/agent/deployments/{deploymentId}/repos/count | +| Added | added the new optional request property `filters.latestScanStatus` | PATCH /api/agent/deployments/{deploymentId}/repos/filtered |
- + ## Potentially breaking changes
| Change | Description | Endpoint | |---|---|---| -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation/triggerSource` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations/items/triggerSource` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Removed | removed the optional property `products.sast.autofix_pr_enabled` from the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products | +| Removed | removed the request property `sast.autofix_pr_enabled` | PUT /api/agent/deployments/{deploymentId}/products | +| Removed | removed the optional property `products.sast.autofix_pr_enabled` from the response with the `200` status | PUT /api/agent/deployments/{deploymentId}/products | + +
+ +## Changes + +
+ +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the request property `automation.triggerSource` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automation.triggerSource` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the request property `automation.triggerSource` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automation.triggerSource` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new enum value `TOKEN_ROLE_READ_ONLY` to the `query` request parameter `roleFilter` | DELETE /api/tokens/v1/deployments/{deploymentId}/tokens | +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations |
+
+ ## Changes
| Change | Description | Endpoint | |---|---|---| -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation/triggerSource` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | PATCH /api/agent/deployments/{deploymentId}/findings/v2 | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issue_counts | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issue_filters | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issues/export | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issues/search | +| Added | added the optional property `packageManagerConfigsWithCredentials[].config.registryType` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | +| Added | added the new optional request property `packageManagerConfigForInsert.config.registryType` | POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | +| Added | added the optional property `packageManagerConfig.registryType` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | +| Added | added the new optional request property `packageManagerConfigForInsert.config.registryType` | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId} | +| Added | added the optional property `packageManagerConfig.registryType` to the response with the `200` status | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId} | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/ai/deployments/{deploymentId}/memories/triage | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/ai/{deploymentId}/combined_task | +| Added | endpoint added | POST /api/issues/{deploymentId}/counts | +| Added | endpoint added | GET /api/issues/{deploymentId}/issue/{issueId} | +| Added | endpoint added | POST /api/issues/{deploymentId}/issue/{issueId} | +| Added | endpoint added | GET /api/issues/{deploymentId}/issue/{issueId}/activity | +| Added | endpoint added | POST /api/issues/{deploymentId}/search | +| Added | added the new optional request property `issueFilters.hasInFlightAutofixPr` | POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets | +| Added | added the optional property `projects[].scmType` to the response with the `200` status | POST /api/v2/deployments/{deploymentId}/projects/list | +| Added | added the optional property `project.scmType` to the response with the `200` status | DELETE /api/v2/deployments/{deploymentId}/projects/{projectId} | +| Added | added the optional property `project.scmType` to the response with the `200` status | GET /api/v2/deployments/{deploymentId}/projects/{projectId} |
- + ## Potentially breaking changes
| Change | Description | Endpoint | |---|---|---| -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation/actions/items/type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations/items/actions/items/type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Removed | removed the optional property `instances[].transitionStates` from the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/ticketing/v2 |
@@ -120,38 +145,106 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
+| Change | Description | Endpoint | +|---|---|---| +| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test | +| Added | added the new `FIX_JOB_ERROR_TYPE_AI_PROVIDER_NOT_ALLOWED`, `FIX_JOB_ERROR_TYPE_ARCHIVED_REPOSITORY`, `FIX_JOB_ERROR_TYPE_AUTOFIX_DISABLED`, `FIX_JOB_ERROR_TYPE_GROUPING_DISABLED`, `FIX_JOB_ERROR_TYPE_INVALID_GROUP`, `FIX_JOB_ERROR_TYPE_NON_PRIMARY_BRANCH_DISABLED`, `FIX_JOB_ERROR_TYPE_UNSUPPORTED_ISSUE_TYPE` enum values to the `errors[].errorType` response property for the response status `200` | POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs | +| Added | added the optional property `products.ai.mdr_enabled` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products | +| Added | added the optional property `products.ai.mdr_enabled` to the response with the `200` status | PUT /api/agent/deployments/{deploymentId}/products | +| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test | +| Added | added the optional property `automations[].actions[].id` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new optional request property `automation.actions[].id` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the optional property `automation.actions[].id` to the response with the `200` status | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new optional request property `automation.actions[].id` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `automation.actions[].id` to the response with the `200` status | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `instances[].fixedTransitionState` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/ticketing/v2 | +| Added | added the optional property `automations[].actions[].id` to the response with the `200` status | GET /api/v2/deployments/{deploymentId}/automations | + +
+
+ + +## Changes + +
+ +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the `settings.tourStates[].id` response property for the response status `200` | GET /api/auth/users/current/settings | +| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the request property `tourStates[].id` | PATCH /api/auth/users/current/settings | +| Added | added the new `WorkflowsActivationBanner` enum value to the request property `eventSource` | PATCH /api/auth/users/current/settings | + +
+
+ + +## Changes + +
+ +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation.filters.conditions[].type` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation.triggerSource` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation.triggerSource` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation.filters.conditions[].type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation.triggerSource` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation.triggerSource` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | + +
+
+ + +## Changes + +
+ | Change | Description | Endpoint | |---|---|---| | Added | added the new optional request property `includeFirstDetectedAt` | POST /api/agent/deployments/{deploymentId}/issues | -| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | -| Added | added the optional property `issues/items/issue/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | -| Added | added the optional property `issues/items/issue/firstDetectedAt` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the optional property `issues[].issue.exceptionRequest.requesterName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the optional property `issues[].issue.exceptionRequest.reviewerName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the optional property `issues[].issue.firstDetectedAt` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | | Added | added the new optional request property `includeFirstDetectedAt` | POST /api/agent/deployments/{deploymentId}/issues/export | -| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | -| Added | added the optional property `exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | +| Added | added the optional property `exceptionRequest.requesterName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | +| Added | added the optional property `exceptionRequest.reviewerName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | | Added | added the optional property `firstDetectedAt` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | -| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/users | -| Added | added the optional property `users/items/scimManaged` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/users/list | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation/actions/items/type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation/filters/allOf[#/components/schemas/protos.automations.v1.Filters]/conditions/items/type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `users[].scimManaged` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/users | +| Added | added the optional property `users[].scimManaged` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/users/list | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations[].actions[].type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation.actions[].type` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation.filters.conditions[].type` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation.actions[].type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation.actions[].type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation.filters.conditions[].type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation.actions[].type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | | Added | added the new optional request property `issueTypes` | POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets | | Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId} | | Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules | | Deprecated | endpoint deprecated | PUT /api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath} | -| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/requesterName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | -| Added | added the optional property `findings/items/exceptionRequest/allOf[#/components/schemas/protos.issues.v1.IssueExceptionRequestSummary]/reviewerName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | -| Added | added the optional property `findings/items/firstDetectedAt` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | -| Added | added the new optional request property `filters/timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory | -| Added | added the new optional request property `filters/timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem | -| Added | added the new optional request property `filters/timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem | +| Added | added the optional property `findings[].exceptionRequest.requesterName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | +| Added | added the optional property `findings[].exceptionRequest.reviewerName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | +| Added | added the optional property `findings[].firstDetectedAt` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | +| Added | added the new optional request property `filters.timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory | +| Added | added the new optional request property `filters.timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem | +| Added | added the new optional request property `filters.timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem | | Added | endpoint added | POST /api/reporting/{deploymentId}/reports/malware-firewall/scan-activity | | Added | endpoint added | POST /api/reporting/{deploymentId}/reports/malware-firewall/summary | | Added | added the new optional `query` request parameter `dependencyFilter.includeArchived` | GET /api/sca/deployments/{deploymentId}/dependencies | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/sca/deployments/{deploymentId}/dependencies | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/sca/deployments/{deploymentId}/repositories | -| Added | added the new optional request property `dependencyFilter/includeArchived` | POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/sca/deployments/{deploymentId}/dependencies | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/sca/deployments/{deploymentId}/repositories | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations[].actions[].type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations |
diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index 237e0c0997..a93778be25 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -54,6 +54,76 @@ # reader-facing changelog. EXCLUDED_SECTIONS = frozenset(("info",)) +# oasdiff levels we deliberately disagree with, keyed by check id. +# +# oasdiff rates a new *response* enum value WARN (potentially breaking) on the +# theory that a consumer exhaustively switching on the enum, or validating +# responses against a closed set, breaks when an unseen value arrives. That is +# reasonable for a hand-written spec, but ours are generated from protobuf, +# where enums are open by construction and gaining values is routine +# evolution -- so every sync filed a pile of `SCAN_STATUS_*`-style additions +# under a "Potentially breaking changes" heading and buried the actual +# removals. Request-side enum additions are already INFO upstream (the server +# accepting strictly more input cannot break a caller), so only the response +# side needs correcting. +LEVEL_OVERRIDES = { + "response-property-enum-value-added": 1, +} + +# oasdiff names properties with JSON Schema's internal vocabulary, which leaks +# spec plumbing into reader-facing text. Three rewrites turn a path into the +# field reference a consumer would actually write: +# +# 1. `allOf[#/components/schemas/protos.projects.v1.RepoFilters]` segments are +# dropped. These specs are protobuf-generated and wrap every described +# $ref in a single-element allOf (the OpenAPI 3.0 workaround for $ref +# siblings being ignored), so the segment is pure composition bookkeeping +# -- the wire payload has no such level, and dropping it makes the path +# *more* accurate as well as shorter. It also happens to be the only place +# an internal proto package name appears. +# 2. An `items` segment means "element of the preceding array", so it folds +# into the parent as `[]`. A trailing one (an array of scalars, e.g. +# `repositoryId/items/`) would otherwise dangle. +# 3. `/` becomes `.`, matching how the field is addressed in JSON. +# +# Field-name *case* is deliberately never touched: the specs inconsistently +# emit both `deployment_id` and `deploymentId` depending on the service, and +# the changelog has to name the key the endpoint actually accepts. +# Stripped whole, before splitting on "/": the JSON pointer inside the brackets +# contains slashes of its own, so a naive split would shred it. +ALLOF_SEGMENT = re.compile(r"/allOf\[[^\]]*\]") + + +def humanize_property_path(path: str) -> str: + """`a/items/b/allOf[#/...]/c` -> `a[].b.c`. Non-paths pass through.""" + out: list[str] = [] + for segment in ALLOF_SEGMENT.sub("", path).split("/"): + if not segment: + continue + if segment == "items" and out: + out[-1] += "[]" + continue + out.append(segment) + return ".".join(out) + + +def humanize_change_text(change: dict) -> dict: + """Rewrite property paths inside the change's code spans.""" + text = change.get("text") or "" + if "`" not in text: + return change + parts = text.split("`") + if len(parts) % 2 == 0: # unbalanced backticks: leave well alone + return change + for i in range(1, len(parts), 2): # odd indices are code spans + span = parts[i] + # a leading "/" marks a URL path, not a property path + if "/" in span and not span.startswith("/"): + parts[i] = humanize_property_path(span) + rewritten = "`".join(parts) + return change if rewritten == text else {**change, "text": rewritten} + + MONTHS = ( "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", @@ -221,12 +291,21 @@ def build_entries( last_good = snapshot continue changes = [c for c in changes if c.get("section") not in EXCLUDED_SECTIONS] + changes = [humanize_change_text(apply_level_override(c)) for c in changes] if changes: entries.append(Entry(snapshot.date, changes)) last_good = snapshot return list(reversed(entries)) +def apply_level_override(change: dict) -> dict: + """Re-level a change per LEVEL_OVERRIDES, leaving others untouched.""" + override = LEVEL_OVERRIDES.get(change.get("id")) + if override is None or change.get("level") == override: + return change + return {**change, "level": override} + + def format_date(iso: str) -> str: """"2026-08-07" -> "August 7, 2026" (no strftime: %-d is platform-bound).""" year, month, day = iso.split("-") diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index 6a29c1011c..bcb40d446e 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -268,6 +268,119 @@ def test_build_entries_filters_excluded_sections(): assert entries == [] # a day with only excluded changes gets no entry +def test_apply_level_override_demotes_response_enum_additions(): + change = {"id": "response-property-enum-value-added", "level": 2, "text": "added"} + + assert gac.apply_level_override(change)["level"] == 1 + assert change["level"] == 2 # override does not mutate its input + + +def test_apply_level_override_leaves_other_checks_alone(): + for check_id, level in ( + ("api-path-removed-without-deprecation", 3), + ("request-parameter-removed", 2), + ("endpoint-added", 1), + ): + change = {"id": check_id, "level": level} + assert gac.apply_level_override(change) is change + + +def test_build_entries_demotes_response_enum_additions_out_of_breaking_sections(): + snaps = snapshots_for(("bbb", "2026-08-07"), ("aaa", "2026-07-14")) + enum_addition = [ + { + "id": "response-property-enum-value-added", + "level": 2, + "section": "paths", + "operation": "GET", + "path": "/api/v1/deployments", + "text": "added the new `X` enum value to the `status` response property", + } + ] + + entries = gac.build_entries(snaps, "spec.yaml", lambda b, r: enum_addition) + + assert [c["level"] for c in entries[0].changes] == [1] + + +@pytest.mark.parametrize( + "raw,expected", + [ + # the reported case: a described $ref wrapped in a single-element allOf + ( + "filters/allOf[#/components/schemas/protos.projects.v1.RepoFilters]/latestScanStatus", + "filters.latestScanStatus", + ), + # array elements fold into the parent, allOf drops out mid-path + ( + "automations/items/filters/allOf[#/components/schemas/protos.automations.v1.Filters]" + "/conditions/items/type", + "automations[].filters.conditions[].type", + ), + # an array of scalars leaves a trailing `items` that would dangle + ("dependencyFilter/repositoryId/items/", "dependencyFilter.repositoryId[]"), + ("errors/items/errorType", "errors[].errorType"), + # snake_case fields keep their spelling -- the endpoint accepts that key + ("scans/items/is_partial_scan", "scans[].is_partial_scan"), + # already clean, and non-paths + ("filters/latestScanStatus", "filters.latestScanStatus"), + ("latestScanStatus", "latestScanStatus"), + ], +) +def test_humanize_property_path(raw, expected): + assert gac.humanize_property_path(raw) == expected + + +def test_humanize_change_text_rewrites_only_code_spans(): + change = { + "text": "added the new optional request property " + "`filters/allOf[#/components/schemas/protos.projects.v1.RepoFilters]/latestScanStatus`", + } + + assert gac.humanize_change_text(change)["text"] == ( + "added the new optional request property `filters.latestScanStatus`" + ) + assert "allOf" in change["text"] # input not mutated + + +def test_humanize_change_text_leaves_url_paths_and_plain_spans_alone(): + for text in ( + "removed the endpoint `/api/v1/deployments`", # leading slash: a URL + "the response status `200` was added", + "added the new `SCAN_STATUS_CANCELLED` enum value", + "unbalanced ` backtick with a/b path", + ): + assert gac.humanize_change_text({"text": text})["text"] == text + + +def test_humanize_runs_before_coalescing_so_paths_merge(): + changes = [ + { + "id": "response-property-enum-value-added", + "level": 1, + "operation": "GET", + "path": "/x", + "text": "added the new `A` enum value to the " + "`f/allOf[#/components/schemas/protos.a.v1.T]/g` response property" + " for the response status `200`", + }, + { + "id": "response-property-enum-value-added", + "level": 1, + "operation": "GET", + "path": "/x", + "text": "added the new `B` enum value to the `f/g` response property" + " for the response status `200`", + }, + ] + + merged = gac.coalesce_changes([gac.humanize_change_text(c) for c in changes]) + + assert len(merged) == 1 # identical paths after humanizing, so they fold + assert "`A`, `B`" in merged[0]["text"] + assert "allOf" not in merged[0]["text"] + + def test_build_entries_drops_unparseable_base_and_promotes_revision(): snaps = snapshots_for( ("ccc", "2026-08-07"), ("bbb", "2026-07-23"), ("aaa", "2026-07-14") From bd056f0505a03f9ee212e117cb782ec646f72065 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 10:01:08 -0700 Subject: [PATCH 04/27] feat(api-reference): make the changelog RSS feed usable The pages already carried `rss: true`, but Mintlify would have produced a poor feed from them: - It emits one entry per Markdown heading inside an ``, so our severity sections turned 3 dated entries into 6 for v1 and 10 into 12 for v2 -- titled "Changes" (9x), "Breaking changes", "Potentially breaking changes", undated and repeated across every day. - Feed entries "contain pure Markdown only ... exclude components, code, and HTML elements", and our rows are a JSX table of Badges and links, so subscribers would have received near-empty items. Emit an explicit `rss={{ title, description }}` per ``, which is what Mintlify recommends for updates containing excluded content. That pins the feed to one dated item per day with a prose summary, and decouples it from the heading structure -- otherwise a re-level that adds or drops a section heading counts as "modifying headings inside an existing Update" and republishes the entry to subscribers. Also add `--rss-url`, which renders subscribe instructions on the page. Mintlify only emits a `` autodiscovery tag for `rss: true` pages and no visible affordance, so the page has to say it. Fixes a pre-existing summary bug along the way: the noun was attached only via the "other" bucket, so a breaking-only day read "1 breaking" instead of "1 breaking change". The count logic is now shared between the on-page summary and the RSS description so the two cannot drift. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/update-openapi-specs.yml | 4 +- docs/api-reference/v1/Changelog.mdx | 13 +- docs/api-reference/v2/Changelog.mdx | 27 +++-- scripts/generate_api_changelog.py | 121 ++++++++++++++++--- scripts/tests/test_generate_api_changelog.py | 113 ++++++++++++++++- 5 files changed, 244 insertions(+), 34 deletions(-) diff --git a/.github/workflows/update-openapi-specs.yml b/.github/workflows/update-openapi-specs.yml index 45010cf2de..cfdca5b833 100644 --- a/.github/workflows/update-openapi-specs.yml +++ b/.github/workflows/update-openapi-specs.yml @@ -97,11 +97,13 @@ jobs: uv run scripts/generate_api_changelog.py docs/public_v1.openapi.yaml \ docs/api-reference/v1/Changelog.mdx \ --api-name "Semgrep API v1" --api-href /api-reference/v1/Introduction \ - --link-base /api-reference/v1 + --link-base /api-reference/v1 \ + --rss-url https://docs.semgrep.dev/api-reference/v1/Changelog/rss.xml uv run scripts/generate_api_changelog.py docs/public_v2.openapi.yaml \ docs/api-reference/v2/Changelog.mdx \ --api-name "Semgrep API v2" --api-href /api-reference/v2/Introduction \ --link-base /api-reference/v2 \ + --rss-url https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml \ --note "The v2 API is experimental; breaking changes are expected while it is in alpha." - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx index 07a562f8a3..7d7c70d3c9 100644 --- a/docs/api-reference/v1/Changelog.mdx +++ b/docs/api-reference/v1/Changelog.mdx @@ -10,7 +10,14 @@ Changes to the [Semgrep API v1](/api-reference/v1/Introduction), detected by comparing successive versions of its OpenAPI specification. Breaking changes are labeled; documentation-only edits are not listed. - + + **Subscribe to this changelog:** add [`https://docs.semgrep.dev/api-reference/v1/Changelog/rss.xml`](https://docs.semgrep.dev/api-reference/v1/Changelog/rss.xml) to any + feed reader, or run `/feed subscribe https://docs.semgrep.dev/api-reference/v1/Changelog/rss.xml` in Slack to post new + entries to a channel. Most readers also accept this page's own URL and + find the feed automatically. + + + ## Breaking changes
@@ -34,7 +41,7 @@ are labeled; documentation-only edits are not listed.
- + ## Changes
@@ -51,7 +58,7 @@ are labeled; documentation-only edits are not listed.
- + ## Breaking changes
diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index a6ed5f9234..0af966d3b9 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -10,7 +10,14 @@ Changes to the [Semgrep API v2](/api-reference/v2/Introduction), detected by comparing successive versions of its OpenAPI specification. Breaking changes are labeled; documentation-only edits are not listed. The v2 API is experimental; breaking changes are expected while it is in alpha. - + + **Subscribe to this changelog:** add [`https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml`](https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml) to any + feed reader, or run `/feed subscribe https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml` in Slack to post new + entries to a channel. Most readers also accept this page's own URL and + find the feed automatically. + + + ## Breaking changes
@@ -22,7 +29,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Changes
@@ -39,7 +46,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Changes
@@ -52,7 +59,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Changes
@@ -67,7 +74,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Potentially breaking changes
@@ -97,7 +104,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Changes
@@ -130,7 +137,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Potentially breaking changes
@@ -163,7 +170,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Changes
@@ -177,7 +184,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Changes
@@ -200,7 +207,7 @@ are labeled; documentation-only edits are not listed. The v2 API is experimental
- + ## Changes
diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index a93778be25..58ef0e511a 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -463,20 +463,70 @@ def _endpoint_key(change: dict) -> tuple: return (0, path, change.get("operation") or "") -def _summary(changes: list) -> str: - """Raw change counts, e.g. "2 breaking · 1 potentially breaking · 3 other changes".""" +def _count_parts(changes: list) -> list[str]: + """Non-zero severity counts, least-qualified last: ["2 breaking", "3 other"]. + + Shared by the on-page summary and the RSS description so the two cannot + disagree. The trailing noun is left to the caller, which appends it to the + final part -- "2 breaking · 3 other changes" reads as one phrase, so a + breaking-only day must still come out as "1 breaking change" rather than + the bare "1 breaking". "other" is dropped when it stands alone, since + there is then nothing for it to be other than. + """ breaking = sum(1 for c in changes if c["level"] == BREAKING) potential = sum(1 for c in changes if c["level"] == POTENTIALLY_BREAKING) other = len(changes) - breaking - potential - parts = [] - if breaking: - parts.append(f"{breaking} breaking") - if potential: - parts.append(f"{potential} potentially breaking") - if other: - label = "other change" if parts else "change" - parts.append(f"{other} {label}{'s' if other != 1 else ''}") - return " · ".join(parts) + counts = ((breaking, "breaking"), (potential, "potentially breaking"), (other, "other")) + present = [(n, word) for n, word in counts if n] + if len(present) == 1 and present[0][1] == "other": + return [str(present[0][0])] + return [f"{n} {word}" for n, word in present] + + +def _with_noun(parts: list[str], count: int) -> list[str]: + """Append "change"/"changes" to the final part, agreeing with its count.""" + return parts[:-1] + [f"{parts[-1]} change{'s' if count != 1 else ''}"] + + +def _summary(changes: list) -> str: + """Raw change counts, e.g. "2 breaking · 1 potentially breaking · 3 other changes".""" + parts = _count_parts(changes) + if not parts: + return "no changes" + last = int(parts[-1].split()[0]) + return " · ".join(_with_noun(parts, last)) + + +def _jsx_string(text: str) -> str: + """Escape text for a double-quoted JSX string literal.""" + return text.replace("\\", "\\\\").replace('"', '\\"') + + +def _rss_description(changes: list) -> str: + """Plain-prose summary for RSS subscribers. + + Mintlify strips components, code and HTML from feed entries, and our + entries are almost entirely a JSX table of Badges and links -- so without + an explicit `rss` description a subscriber would receive an empty item. + Prose rather than the interpunct-separated on-page summary, since this is + read in a feed reader with no surrounding context. + """ + parts = _count_parts(changes) + if not parts: + return "No changes." + last = int(parts[-1].split()[0]) + parts = _with_noun(parts, last) + if len(parts) == 1: + listed = parts[0] + elif len(parts) == 2: + listed = f"{parts[0]} and {parts[1]}" # no serial comma for a pair + else: + listed = ", ".join(parts[:-1]) + f", and {parts[-1]}" + endpoints = {(c.get("operation"), c.get("path")) for c in changes if c.get("path")} + if not endpoints: + return f"{listed}." + scope = "endpoint" if len(endpoints) == 1 else "endpoints" + return f"{listed} across {len(endpoints)} {scope}." def _render_section(changes: list, links: dict) -> list: @@ -587,11 +637,25 @@ def _render_section_table(changes: list, links: dict) -> list: return lines -def _render_update(entry: Entry, links: dict, style: str = "table") -> str: +def _render_update( + entry: Entry, links: dict, style: str = "table", api_name: Optional[str] = None +) -> str: label = format_date(entry.date) attrs = [f'label="{label}"', f'description="{_summary(entry.changes)}"'] if any(c["level"] == BREAKING for c in entry.changes): attrs.append('tags={["Breaking"]}') + # Pins the feed to one item per . Without it Mintlify emits one + # entry per Markdown heading inside the block, so a day with three + # severity sections becomes three items titled "Breaking changes", + # "Potentially breaking changes" and "Changes" -- undated, and repeated + # across every day. It also decouples the feed from the heading structure: + # a re-level that adds or drops a section heading would otherwise count as + # "modifying headings inside an existing Update" and republish the entry. + rss_title = f"{api_name} — {label}" if api_name else label + attrs.append( + 'rss={{ title: "%s", description: "%s" }}' + % (_jsx_string(rss_title), _jsx_string(_rss_description(entry.changes))) + ) render_section = _render_section_table if style == "table" else _render_section changes = coalesce_changes(entry.changes) @@ -611,6 +675,23 @@ def _render_update(entry: Entry, links: dict, style: str = "table") -> str: return "\n".join(lines) +def _rss_callout(rss_url: str) -> str: + """Subscribe instructions. + + Mintlify emits a `` tag + for `rss: true` pages, which is autodiscovery only -- it renders no + visible affordance -- so the page has to say this itself. + """ + return ( + "\n" + f" **Subscribe to this changelog:** add [`{rss_url}`]({rss_url}) to any\n" + f" feed reader, or run `/feed subscribe {rss_url}` in Slack to post new\n" + " entries to a channel. Most readers also accept this page's own URL and\n" + " find the feed automatically.\n" + "" + ) + + def render_page( api_name: str, api_href: Optional[str], @@ -618,6 +699,7 @@ def render_page( entries: list[Entry], links: Optional[dict] = None, style: str = "table", + rss_url: Optional[str] = None, ) -> str: name = f"[{api_name}]({api_href})" if api_href else api_name intro = ( @@ -640,9 +722,13 @@ def render_page( intro, "", ] + if rss_url: + blocks.extend([_rss_callout(rss_url), ""]) if entries: blocks.append( - "\n\n".join(_render_update(entry, links or {}, style) for entry in entries) + "\n\n".join( + _render_update(entry, links or {}, style, api_name) for entry in entries + ) ) else: blocks.append("No API changes recorded yet.") @@ -658,6 +744,11 @@ def main(argv: Optional[list[str]] = None) -> int: parser.add_argument("--api-name", required=True, help='e.g. "Semgrep API v1"') parser.add_argument("--api-href", help="docs link for the API name in the intro") parser.add_argument("--note", help="extra sentence appended to the intro") + parser.add_argument( + "--rss-url", + help="absolute URL of the page's Mintlify RSS feed (the page path with" + " /rss.xml appended); when set, the page carries subscribe instructions", + ) parser.add_argument( "--link-base", help="URL prefix of the generated endpoint pages (e.g. /api-reference/v1);" @@ -710,7 +801,9 @@ def main(argv: Optional[list[str]] = None) -> int: if args.link_base: links = endpoint_urls(yaml.safe_load(Path(args.spec).read_text()), args.link_base) - content = render_page(args.api_name, args.api_href, args.note, entries, links, args.style) + content = render_page( + args.api_name, args.api_href, args.note, entries, links, args.style, args.rss_url + ) output = Path(args.output) if output.exists() and output.read_text() == content: print(f"{output}: unchanged") diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index bcb40d446e..b3b317ef84 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -440,7 +440,7 @@ def diff(base, rev): comparing successive versions of its OpenAPI specification. Breaking changes are labeled; documentation-only edits are not listed. - + ## Breaking changes - GET `/api/v1/agents`: api path removed without deprecation @@ -457,7 +457,7 @@ def diff(base, rev): - removed the schema `ProjectFilter` - + ## Changes - POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy): endpoint added @@ -493,7 +493,7 @@ def test_render_page_golden_list_style(): GOLDEN_TABLE_BODY = """\ - + ## Breaking changes
@@ -528,7 +528,7 @@ def test_render_page_golden_list_style():
- + ## Changes
@@ -587,9 +587,110 @@ def test_render_page_breaking_tag_and_summary(): assert ( '' + 'tags={["Breaking"]} rss=' ) in page - assert '' in page + assert '") < page.index("" not in page + assert "rss.xml" not in page + + +@pytest.mark.parametrize( + "levels,summary,rss", + [ + # a breaking-only day still gets its noun + ([3], "1 breaking change", "1 breaking change across 1 endpoint."), + ([3, 3], "2 breaking changes", "2 breaking changes across 1 endpoint."), + # "other" is not qualified when it stands alone + ([1], "1 change", "1 change across 1 endpoint."), + ([1, 1, 1], "3 changes", "3 changes across 1 endpoint."), + # mixed: noun lands on the final part only + ([3, 2, 1], "1 breaking · 1 potentially breaking · 1 other change", + "1 breaking, 1 potentially breaking, and 1 other change across 1 endpoint."), + ([3, 3, 1, 1], "2 breaking · 2 other changes", + "2 breaking and 2 other changes across 1 endpoint."), + ], +) +def test_summary_and_rss_description_agree_and_read_as_phrases(levels, summary, rss): + changes = [ + {"id": "x", "level": lv, "section": "paths", + "operation": "GET", "path": "/api/v1/a", "text": f"change {i}"} + for i, lv in enumerate(levels) + ] + + assert gac._summary(changes) == summary + assert gac._rss_description(changes) == rss + + +def test_rss_description_omits_endpoint_scope_when_no_endpoint(): + changes = [{"id": "api-schema-removed", "level": 1, "section": "components", + "text": "removed the schema `X`"}] + + assert gac._rss_description(changes) == "1 change." def test_render_update_nests_multiple_changes_per_endpoint(): From 6b31d5a7d7b2e5782e5d51f1689a43c165458f8f Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 10:43:29 -0700 Subject: [PATCH 05/27] docs(api-reference): drop the v2 alpha caveat from the changelog intro The caveat duplicates what the v2 Introduction page already states, and on a page whose whole purpose is to label breaking changes individually it read as a blanket disclaimer over entries that are mostly additive. The generator keeps its --note flag; the v2 invocation just stops passing one. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/update-openapi-specs.yml | 3 +-- docs/api-reference/v2/Changelog.mdx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-openapi-specs.yml b/.github/workflows/update-openapi-specs.yml index cfdca5b833..2ca12efb1a 100644 --- a/.github/workflows/update-openapi-specs.yml +++ b/.github/workflows/update-openapi-specs.yml @@ -103,8 +103,7 @@ jobs: docs/api-reference/v2/Changelog.mdx \ --api-name "Semgrep API v2" --api-href /api-reference/v2/Introduction \ --link-base /api-reference/v2 \ - --rss-url https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml \ - --note "The v2 API is experimental; breaking changes are expected while it is in alpha." + --rss-url https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index 0af966d3b9..f926621d9f 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -8,7 +8,7 @@ rss: true Changes to the [Semgrep API v2](/api-reference/v2/Introduction), detected by comparing successive versions of its OpenAPI specification. Breaking changes -are labeled; documentation-only edits are not listed. The v2 API is experimental; breaking changes are expected while it is in alpha. +are labeled; documentation-only edits are not listed. **Subscribe to this changelog:** add [`https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml`](https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml) to any From 3402fad7ab459328361a8009c558c570c55c9cc1 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 11:04:37 -0700 Subject: [PATCH 06/27] fix(api-reference): stop generator changes republishing old feed entries Mintlify "publishes entries when you add new Update components and when you modify headings inside of existing Update components". Our severity sections were Markdown headings, and which of them a given day has depends on the level mapping -- so demoting a check (LEVEL_OVERRIDES) or bumping oasdiff removes or adds a heading inside months-old entries and re-notifies every subscriber about them. Today's enum demotion would have done exactly that. Render the section labels in bold instead. With no headings inside an , the only remaining publish trigger is adding a new one. The rss= prop already supplies the feed title, so nothing depended on these being headings, and their anchors were colliding anyway -- every day emitted the same #changes. Verified on the PR preview deployment beforehand: an edit outside the Update blocks left all 10 v2 items' guid and pubDate untouched, confirming both are per-block rather than per-file. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v1/Changelog.mdx | 12 +++---- docs/api-reference/v2/Changelog.mdx | 24 ++++++------- scripts/generate_api_changelog.py | 10 +++++- scripts/tests/test_generate_api_changelog.py | 36 +++++++++++++++----- 4 files changed, 55 insertions(+), 27 deletions(-) diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx index 7d7c70d3c9..60b76bdb48 100644 --- a/docs/api-reference/v1/Changelog.mdx +++ b/docs/api-reference/v1/Changelog.mdx @@ -18,7 +18,7 @@ are labeled; documentation-only edits are not listed. -## Breaking changes +**Breaking changes**
@@ -28,7 +28,7 @@ are labeled; documentation-only edits are not listed.
-## Changes +**Changes**
@@ -42,7 +42,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
@@ -59,7 +59,7 @@ are labeled; documentation-only edits are not listed. -## Breaking changes +**Breaking changes**
@@ -112,7 +112,7 @@ are labeled; documentation-only edits are not listed.
-## Potentially breaking changes +**Potentially breaking changes**
@@ -125,7 +125,7 @@ are labeled; documentation-only edits are not listed.
-## Changes +**Changes**
diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index f926621d9f..ef71bf2da6 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -18,7 +18,7 @@ are labeled; documentation-only edits are not listed. -## Breaking changes +**Breaking changes**
@@ -30,7 +30,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
@@ -47,7 +47,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
@@ -60,7 +60,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
@@ -75,7 +75,7 @@ are labeled; documentation-only edits are not listed. -## Potentially breaking changes +**Potentially breaking changes**
@@ -87,7 +87,7 @@ are labeled; documentation-only edits are not listed.
-## Changes +**Changes**
@@ -105,7 +105,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
@@ -138,7 +138,7 @@ are labeled; documentation-only edits are not listed. -## Potentially breaking changes +**Potentially breaking changes**
@@ -148,7 +148,7 @@ are labeled; documentation-only edits are not listed.
-## Changes +**Changes**
@@ -171,7 +171,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
@@ -185,7 +185,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
@@ -208,7 +208,7 @@ are labeled; documentation-only edits are not listed. -## Changes +**Changes**
diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index 58ef0e511a..99b3ccfda3 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -668,7 +668,15 @@ def _render_update( if not first: lines.append("") first = False - lines.append(f"## {SECTION_TITLES[level]}") + # Bold rather than a Markdown heading, deliberately. Mintlify + # republishes a feed entry when a heading inside an existing + # is modified, and which severity sections a day has depends on the + # level mapping -- so a re-level (see LEVEL_OVERRIDES) or an oasdiff + # bump would re-notify every subscriber about months-old entries. + # With no headings in the block, the only publish trigger left is + # adding a new . The rss= prop supplies the feed title, so + # nothing depends on these being headings. + lines.append(f"**{SECTION_TITLES[level]}**") lines.append("") lines.extend(render_section(section, links)) lines.append("") diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index b3b317ef84..8072725871 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -1,4 +1,5 @@ import json +import re import shutil import subprocess import textwrap @@ -441,16 +442,16 @@ def diff(base, rev): are labeled; documentation-only edits are not listed. -## Breaking changes +**Breaking changes** - GET `/api/v1/agents`: api path removed without deprecation - GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects): removed the required property `tags` from the response with the `200` status -## Potentially breaking changes +**Potentially breaking changes** - GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects): deleted the `query` request parameter `page_token` -## Changes +**Changes** - GET [`/api/v1/deployments/{deployment_id}/projects`](/api-reference/v1/projectsservice/list-projects): added the new optional `query` request parameter `page_size` - POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy): endpoint added @@ -458,7 +459,7 @@ def diff(base, rev): -## Changes +**Changes** - POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy): endpoint added @@ -494,7 +495,7 @@ def test_render_page_golden_list_style(): GOLDEN_TABLE_BODY = """\ -## Breaking changes +**Breaking changes**
@@ -505,7 +506,7 @@ def test_render_page_golden_list_style():
-## Potentially breaking changes +**Potentially breaking changes**
@@ -515,7 +516,7 @@ def test_render_page_golden_list_style():
-## Changes +**Changes**
@@ -529,7 +530,7 @@ def test_render_page_golden_list_style(): -## Changes +**Changes**
@@ -693,6 +694,25 @@ def test_rss_description_omits_endpoint_scope_when_no_endpoint(): assert gac._rss_description(changes) == "1 change." +def test_update_blocks_contain_no_markdown_headings(): + """Mintlify republishes an entry when a heading inside it is modified. + + Which severity sections a day has depends on the level mapping, so a + heading here would re-notify subscribers about old entries every time + LEVEL_OVERRIDES or oasdiff changes. Section labels must stay non-headings. + """ + page = gac.render_page( + api_name="Semgrep API v1", + api_href="/api-reference/v1/Introduction", + note=None, + entries=golden_entries(), + ) + + for block in re.findall(r"]*>(.*?)", page, re.S): + assert not re.search(r"^#{1,6} ", block, re.M), block + assert "**Breaking changes**" in page # still labelled, just not a heading + + def test_render_update_nests_multiple_changes_per_endpoint(): changes = [ { From a72d74de21def717d8e657bdc8466d81952740ff Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 22:20:55 -0700 Subject: [PATCH 07/27] fix(api-reference): keep endpoint re-tagging out of the changelog Regrouping the public API into its new services re-tags roughly 200 endpoints without touching a path, a request, or a response. oasdiff reports each one twice -- `api-tag-added` and `api-tag-removed` -- so the reorganisation would land as ~400 rows on a single date. Measured, not guessed: re-tagging the 6 MiscService operations produced exactly 12 changes. A day of nothing but re-tagging would therefore create a new , and a new is the one thing that still publishes to RSS subscribers. The feed's largest entry ever would be an internal filing change, and any real change shipped the same day would be buried under it. Drop both checks. A tag decides which nav group an endpoint renders under; oasdiff itself rates both INFO. Not entirely free -- some SDK generators namespace by tag, so a consumer generating a client from the published spec could see a method move class -- but it is not a change to the HTTP contract, and that caveat is recorded next to the exclusion. Real changes shipped alongside a re-tag still appear; only the tag rows are dropped. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/generate_api_changelog.py | 24 ++++++++++++- scripts/tests/test_generate_api_changelog.py | 38 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index 99b3ccfda3..efd6e60426 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -54,6 +54,23 @@ # reader-facing changelog. EXCLUDED_SECTIONS = frozenset(("info",)) +# Checks dropped outright, by id. +# +# A tag is documentation grouping: it decides which nav group an endpoint +# renders under, not what the endpoint accepts or returns. Regrouping the +# public API into its new services re-tags roughly 200 endpoints, and oasdiff +# emits an `added` and a `removed` for each, so the whole reorganisation would +# land as ~400 rows on one date -- a new , which is the one thing that +# still publishes to RSS subscribers. That would announce an internal filing +# change as the largest entry in the feed's history while burying the real +# changes shipped that day. +# +# Not entirely free: some SDK generators namespace by tag, so a consumer +# generating a client from the published spec could see a method move class. +# It is still not a change to the HTTP contract -- same path, same request, +# same response -- and oasdiff itself rates both checks INFO. +EXCLUDED_CHECKS = frozenset(("api-tag-added", "api-tag-removed")) + # oasdiff levels we deliberately disagree with, keyed by check id. # # oasdiff rates a new *response* enum value WARN (potentially breaking) on the @@ -290,7 +307,12 @@ def build_entries( if diff(rev_arg, rev_arg) is not None: last_good = snapshot continue - changes = [c for c in changes if c.get("section") not in EXCLUDED_SECTIONS] + changes = [ + c + for c in changes + if c.get("section") not in EXCLUDED_SECTIONS + and c.get("id") not in EXCLUDED_CHECKS + ] changes = [humanize_change_text(apply_level_override(c)) for c in changes] if changes: entries.append(Entry(snapshot.date, changes)) diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index 8072725871..63be580643 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -382,6 +382,44 @@ def test_humanize_runs_before_coalescing_so_paths_merge(): assert "allOf" not in merged[0]["text"] +def test_build_entries_drops_tag_only_changes(): + """Regrouping endpoints into new services must not reach subscribers. + + oasdiff emits api-tag-added + api-tag-removed per re-tagged operation, so + the reorganisation would otherwise land as hundreds of rows on one date -- + a new , which publishes to RSS. + """ + snaps = snapshots_for(("bbb", "2026-08-07"), ("aaa", "2026-07-14")) + retag = [ + {"id": "api-tag-removed", "level": 1, "section": "paths", + "operation": "GET", "path": "/api/agent/identity", + "text": "api tag `MiscService` removed"}, + {"id": "api-tag-added", "level": 1, "section": "paths", + "operation": "GET", "path": "/api/agent/identity", + "text": "api tag `Deployments` added"}, + ] + + entries = gac.build_entries(snaps, "spec.yaml", lambda b, r: retag) + + assert entries == [] # a day of nothing but re-tagging gets no entry at all + + +def test_build_entries_keeps_real_changes_shipped_alongside_a_retag(): + snaps = snapshots_for(("bbb", "2026-08-07"), ("aaa", "2026-07-14")) + mixed = [ + {"id": "api-tag-added", "level": 1, "section": "paths", + "operation": "GET", "path": "/api/agent/identity", + "text": "api tag `Deployments` added"}, + {"id": "endpoint-added", "level": 1, "section": "paths", + "operation": "POST", "path": "/api/v2/things", + "text": "endpoint added"}, + ] + + entries = gac.build_entries(snaps, "spec.yaml", lambda b, r: mixed) + + assert [c["id"] for c in entries[0].changes] == ["endpoint-added"] + + def test_build_entries_drops_unparseable_base_and_promotes_revision(): snaps = snapshots_for( ("ccc", "2026-08-07"), ("bbb", "2026-07-23"), ("aaa", "2026-07-14") From 0482a4b409d79e9cf3c743327884398150e0d04a Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Fri, 4 Sep 2026 17:21:30 -0700 Subject: [PATCH 08/27] fix(api-reference): make the changelog's "Breaking" filter actually filter Selecting "Breaking" in the side panel did nothing on either changelog page: the chip lit up and the URL gained ?tags=Breaking, but all 10 v2 (and all 3 v1) updates stayed on screen. The cause is Mintlify's visibility predicate, which reduces to "hide me only if I have tags and none of them is selected": if (!filters.length) return true; const active = filters.filter(f => f.active); return !(active.length !== 0 && tags?.length) || tags.some(t => active.some(f => f.tag === t)); For an with no tags= at all, `tags` is undefined, `tags?.length` is falsy, and the first clause short-circuits to true -- it is visible no matter what is selected. We tagged only the days containing a level-3 change, so the other days had no tags to fail to match and nothing could ever be hidden. Confirmed with a two- scratch page: one tagged + one untagged filters nothing, while tagging both hides the non-match. So give every a tag naming the day's highest severity -- Breaking, Potentially breaking, or Non-breaking. The chips are OR'd and individually toggleable, so "show me anything that might break me" is Breaking + Potentially breaking, and each severity is now also visible at a glance under its date. This rules out the structural suspects, incidentally: the scratch repro had no table, no
, no rss= prop and no headings, and still failed -- so none of those, and not d4157be8's switch from headings to bold, had anything to do with it. The regenerated pages change only opening lines, adding an attribute. That is neither a new nor a heading edit inside an existing one, so it does not trip the feed republish triggers d4157be8 documented. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v1/Changelog.mdx | 2 +- docs/api-reference/v2/Changelog.mdx | 18 ++++---- scripts/generate_api_changelog.py | 29 +++++++++++-- scripts/tests/test_generate_api_changelog.py | 45 ++++++++++++++++++-- 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx index 60b76bdb48..9bd45a4f85 100644 --- a/docs/api-reference/v1/Changelog.mdx +++ b/docs/api-reference/v1/Changelog.mdx @@ -41,7 +41,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index ef71bf2da6..0a1393ab29 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -29,7 +29,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
@@ -46,7 +46,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
@@ -59,7 +59,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
@@ -74,7 +74,7 @@ are labeled; documentation-only edits are not listed.
- + **Potentially breaking changes**
@@ -104,7 +104,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
@@ -137,7 +137,7 @@ are labeled; documentation-only edits are not listed.
- + **Potentially breaking changes**
@@ -170,7 +170,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
@@ -184,7 +184,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
@@ -207,7 +207,7 @@ are labeled; documentation-only edits are not listed.
- + **Changes**
diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index efd6e60426..26573248a1 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -155,6 +155,19 @@ def humanize_change_text(change: dict) -> dict: 1: "Changes", } +# The side-panel filter tag, naming the day's highest severity. Every +# needs one: Mintlify hides an update only when it carries tags and none of +# them is selected, so an with no tags= at all stays visible whatever +# is selected. Tagging just the breaking days therefore rendered a "Breaking" +# filter that, when clicked, hid nothing -- the other days had no tags to fail +# to match. Levels outside this map fall in with the level-1 changes, matching +# how the severity sections are grouped. +UPDATE_TAGS = { + BREAKING: "Breaking", + POTENTIALLY_BREAKING: "Potentially breaking", + 1: "Non-breaking", +} + # One upstream sync often lands the same kind of change many times over — six # enum values added to one property, a dozen properties switching `uint32` to # `int64`. Rendering each as its own bullet buries the day's real news, so @@ -519,6 +532,14 @@ def _summary(changes: list) -> str: return " · ".join(_with_noun(parts, last)) +def _update_tag(changes: list) -> str: + """The day's highest severity, as its side-panel filter tag.""" + for level in (BREAKING, POTENTIALLY_BREAKING): + if any(c["level"] == level for c in changes): + return UPDATE_TAGS[level] + return UPDATE_TAGS[1] + + def _jsx_string(text: str) -> str: """Escape text for a double-quoted JSX string literal.""" return text.replace("\\", "\\\\").replace('"', '\\"') @@ -663,9 +684,11 @@ def _render_update( entry: Entry, links: dict, style: str = "table", api_name: Optional[str] = None ) -> str: label = format_date(entry.date) - attrs = [f'label="{label}"', f'description="{_summary(entry.changes)}"'] - if any(c["level"] == BREAKING for c in entry.changes): - attrs.append('tags={["Breaking"]}') + attrs = [ + f'label="{label}"', + f'description="{_summary(entry.changes)}"', + 'tags={["%s"]}' % _update_tag(entry.changes), + ] # Pins the feed to one item per . Without it Mintlify emits one # entry per Markdown heading inside the block, so a day with three # severity sections becomes three items titled "Breaking changes", diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index 63be580643..0768e391b2 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -496,7 +496,7 @@ def diff(base, rev): - removed the schema `ProjectFilter` - + **Changes** - POST [`/api/v1/policies`](/api-reference/v1/policiesservice/create-policy): endpoint added @@ -567,7 +567,7 @@ def test_render_page_golden_list_style():
- + **Changes**
@@ -628,7 +628,46 @@ def test_render_page_breaking_tag_and_summary(): 'description="2 breaking · 1 potentially breaking · 3 other changes" ' 'tags={["Breaking"]} rss=' ) in page - assert ' Date: Tue, 8 Sep 2026 09:44:02 -0700 Subject: [PATCH 09/27] style(api-reference): fix spacing and overflow in the changelog table Two presentation fixes for rows that span several endpoints: - The endpoint cell stacks its pills with `
`, which gave them no leading and read as one solid block. Adds a little vertical rhythm, scoped to the generated table so no badge elsewhere is affected. - The "and N more" hovercard lists full endpoint paths, and a path is one unbreakable token. Mintlify caps the popup at 16rem, so the text ran ~190px past the rounded background instead of wrapping inside it. Allows a break mid-path and widens the cap so a path takes two lines, not five. Verified at 1440px and 390px: no overflow, still bounded by the viewport. Co-Authored-By: Claude Opus 5 (1M context) --- docs/styles.css | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/styles.css b/docs/styles.css index 98dc71fd0e..edf3780f4c 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -24,3 +24,28 @@ h1 { width: 1%; white-space: nowrap; } + +/* Changelog endpoint column: a change to a shared type lists several endpoint + pills in one cell, stacked with
. Without a little leading they read as + one solid block. Scoped to the generated table's wrapper class so it cannot + affect badges anywhere else. */ +.api-changelog-table td:last-child a, +.api-changelog-table td:last-child > span { + display: inline-block; + margin-bottom: 0.3rem; +} + +.api-changelog-table td:last-child a:last-child { + margin-bottom: 0; +} + +/* The changelog's "and N more" hovercard lists full endpoint paths, and a path + is one unbreakable token -- `/api/notifications/deployments/{deploymentId}/ + automations/{automationId}` has nowhere to wrap. Mintlify caps the popup at + 16rem, so the text ran ~190px out past the rounded background rather than + wrapping inside it. Allow a break mid-path, and widen the cap a little so a + path takes two lines instead of five. Still bounded by the viewport. */ +[data-component-part="tooltip-content"] { + max-width: min(28rem, calc(100vw - 2rem)); + overflow-wrap: anywhere; +} From 80ed688fca494b60fa06a2d6f31cee8fc749bf66 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Tue, 8 Sep 2026 09:45:41 -0700 Subject: [PATCH 10/27] fix(api-reference): collapse repeated changelog rows into one oasdiff reports per endpoint and per property, so one change to a shared type fanned out into a row per place it surfaced. Adding a single enum value to `TriggerSource` produced six near-identical rows -- the same sentence, once for each automations endpoint, split again across the request and response copies of the property. The reader had to diff six rows by eye to work out they were one change. Two folds, both lossless: - `merge_across_endpoints` folds rows whose wording is *identical* into one carrying an `endpoints` list. Only identical wording merges, so nothing is summarised away. Endpoint-scoped checks (`endpoint-*`, `api-*`) are exempt: two endpoints added on the same day both read "endpoint added", and folding those would hide the endpoints. - `merge_enum_additions` then folds the request and response copies of one property together and lists the values once, normalising the wrapper path to its leaf. The endpoint cell shows the first three and puts the rest behind an "and N more" hovercard, so a broad change stays a readable height without making any endpoint unreachable. RSS is untouched: the feed description is built from the raw, unmerged changes, so its endpoint count stays exact and nothing is truncated there. 193 rows to 112 across the two changelogs; the Aug 20 v2 entry goes from 10 rows to 4, and `TRIGGER_SOURCE_PATCH_READY` from 6 rows to 1. Both files regenerate byte-identical from the committed specs with oasdiff 1.28.0. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v1/Changelog.mdx | 53 ++---- docs/api-reference/v2/Changelog.mdx | 96 +++------- scripts/generate_api_changelog.py | 179 +++++++++++++++++-- scripts/tests/test_generate_api_changelog.py | 68 +++++++ 4 files changed, 268 insertions(+), 128 deletions(-) diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx index 9bd45a4f85..890f5e796c 100644 --- a/docs/api-reference/v1/Changelog.mdx +++ b/docs/api-reference/v1/Changelog.mdx @@ -48,9 +48,7 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies
POST /api/v1/deployments/{deploymentId}/dependencies/repositories
POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | | Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies | | Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | | Deprecated | endpoint deprecated | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | @@ -66,45 +64,31 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| | Changed | the `deployments[].id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | | Changed | the `dependencyFilter.repositoryId[]` request property `format` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | | Changed | the `format` of the request properties `cursor`, `deploymentId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies | | Changed | the `cursor` response's property `format` changed from `uint64` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | +| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories
POST /api/v1/deployments/{deploymentId}/scans/search | | Changed | the `format` of the request properties `cursor`, `dependencyFilter.repositoryId[]`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | | Changed | the `format` of the response properties `cursor`, `repositorySummaries[].id`, `repositorySummaries[].numDependencies` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | | Changed | for the `path` request parameters `deploymentId`, `repositoryId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | | Changed | the `format` of the request properties `dependencyFilter.repositoryId[]`, `pageSize` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | -| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles
POST /api/v1/deployments/{deploymentId}/sbom/export | | Changed | the `lockfileSummaries[].numDependencies` response's property `format` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies | +| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies
GET /api/v1/deployments/{deploymentId}/secrets
POST /api/v1/deployments/{deploymentId}/dependencies
and 3 more | | Changed | the `policies[].id` response's property `format` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies | -| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | -| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId}
PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | +| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentId}/policies/{policyId}
GET /api/v1/deployments/{deploymentId}/secrets | | Changed | the `format` of the response properties `policy.id`, `rules[].id` changed from `uint64` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | -| Changed | for the `path` request parameters `deploymentId`, `policyId`, the `format` was changed from `uint64` to `int64` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | | Changed | the `format` of the request properties `deploymentId`, `policyId` changed from `uint64` to `int64` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | | Changed | the `format` of the response properties `policyId`, `updatedRule.id` changed from `uint64` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/sbom/export | -| Changed | the `format` of the request properties `deploymentId`, `repositoryId` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/sbom/export | | Changed | for the `path` request parameters `deploymentId`, `scanId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/scan/{scanId} | | Changed | the `format` of the response properties `deployment_id`, `exit_code`, `id`, `repository_id` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentId}/scan/{scanId} | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/scans/search | -| Changed | the `deploymentId` request property `format` changed from `uint64` to `int64` | POST /api/v1/deployments/{deploymentId}/scans/search | | Changed | the `format` of the response properties `scans[].deployment_id`, `scans[].findings_counts.code`, `scans[].findings_counts.secrets`, `scans[].findings_counts.supply_chain`, `scans[].findings_counts.total`, `scans[].id`, `scans[].repository_id` changed from `uint64` to `int64` for status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | -| Changed | for the `path` request parameter `deploymentId`, the `format` was changed from `uint64` to `int64` | GET /api/v1/deployments/{deploymentId}/secrets | -| Changed | for the `query` request parameter `limit`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentId}/secrets | | Changed | for the `path` request parameter `externalTicketId`, the `format` was changed from `uint32` to `int64` | DELETE /api/v1/deployments/{deploymentId}/ticketing/v2/tickets/{externalTicketId} | | Changed | for the `query` request parameter `repository_ids`, the `format` of property `items` was narrowed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/projects | +| Changed | for the `query` request parameters `page`, `page_size`, the `format` was changed from `uint32` to `int64` | GET /api/v1/deployments/{deploymentSlug}/findings
GET /api/v1/deployments/{deploymentSlug}/projects | | Changed | the `projects[].id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects | -| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | GET /api/v1/deployments/{deploymentSlug}/projects/{projectName} | -| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName} | -| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName}/managed-scan | -| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | DELETE /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | -| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | PUT /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags | +| Changed | the `project.id` response's property `format` changed from `uint32` to `int64` for status `200` | DELETE /api/v1/deployments/{deploymentSlug}/projects/{projectName}/tags
GET /api/v1/deployments/{deploymentSlug}/projects/{projectName}
PATCH /api/v1/deployments/{deploymentSlug}/projects/{projectName}
and 2 more | | Changed | the `limit` request property `format` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentSlug}/tickets | | Changed | the `format` of the response properties `failed[].issue_ids[]`, `skipped[].issue_ids[]`, `succeeded[].issue_ids[]`, `succeeded[].ticket_id` changed from `uint32` to `int64` for status `200` | POST /api/v1/deployments/{deploymentSlug}/tickets | | Changed | the `format` of the request properties `issue_ids[]`, `limit` changed from `uint32` to `int64` | POST /api/v1/deployments/{deploymentSlug}/triage | @@ -131,30 +115,17 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem[]` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the `dependencies[].ecosystem` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/dependencies | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem[]` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem[]` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | -| Added | added the new `cocoapods`, `mix`, `opam` enum values to the request property `dependencyFilter.ecosystem` | POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +| Added | added the new enum values to the `cocoapods` request and response property | POST /api/v1/deployments/{deploymentId}/dependencies
POST /api/v1/deployments/{deploymentId}/dependencies/repositories
POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | | Added | added the new optional request property `formatVersion.cyclonedxVersion` | POST /api/v1/deployments/{deploymentId}/sbom/export | -| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses[]` | POST /api/v1/deployments/{deploymentId}/scans/search | -| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the request property `statuses` | POST /api/v1/deployments/{deploymentId}/scans/search | +| Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the `statuses` request property | POST /api/v1/deployments/{deploymentId}/scans/search | | Added | added the new `SCAN_STATUS_CANCELLED`, `SCAN_STATUS_PENDING` enum values to the `scans[].status` response property for the response status `200` | POST /api/v1/deployments/{deploymentId}/scans/search | | Added | added the new `SCM_TYPE_HARNESS` enum value to the `findings[].repository.scmType` response property for the response status `200` | GET /api/v1/deployments/{deploymentId}/secrets | | Added | endpoint added | POST /api/v1/deployments/{deploymentId}/tickets/link | | Added | endpoint added | POST /api/v1/deployments/{deploymentId}/tickets/unlink | +| Added | added the new enum values to the `duplicate` request property | GET /api/v1/deployments/{deploymentSlug}/findings
POST /api/v1/deployments/{deploymentSlug}/tickets
POST /api/v1/deployments/{deploymentSlug}/triage | +| Added | added the new enum values to the `provisionally_ignored` request property | GET /api/v1/deployments/{deploymentSlug}/findings
POST /api/v1/deployments/{deploymentSlug}/tickets
POST /api/v1/deployments/{deploymentSlug}/triage | | Added | added the new enum value `ai_sast` to the `query` request parameter `issue_type` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Added | added the new enum value `duplicate` to the `query` request parameter `triage_reasons` | GET /api/v1/deployments/{deploymentSlug}/findings | -| Added | added the new enum value `provisionally_ignored` to the `query` request parameter `status` | GET /api/v1/deployments/{deploymentSlug}/findings | | Added | added the optional property `findings` to the response with the `200` status | GET /api/v1/deployments/{deploymentSlug}/findings | -| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST /api/v1/deployments/{deploymentSlug}/tickets | -| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST /api/v1/deployments/{deploymentSlug}/tickets | -| Added | added the new `duplicate` enum value to the request property `new_triage_reason` | POST /api/v1/deployments/{deploymentSlug}/triage | -| Added | added the new `duplicate` enum value to the request property `triage_reasons` | POST /api/v1/deployments/{deploymentSlug}/triage | -| Added | added the new `provisionally_ignored` enum value to the request property `new_triage_state` | POST /api/v1/deployments/{deploymentSlug}/triage | -| Added | added the new `provisionally_ignored` enum value to the request property `status` | POST /api/v1/deployments/{deploymentSlug}/triage |
diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index 0a1393ab29..a539553e1a 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -53,8 +53,7 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the optional property `byProject.additionalProperties.projectId` to the response with the `200` status | POST /api/reporting/{deploymentId}/reports/by-project/by-product | -| Added | added the optional property `byProject.additionalProperties.projectId` to the response with the `200` status | POST /api/reporting/{deploymentId}/reports/by-project/by-severity | +| Added | added the optional property `byProject.additionalProperties.projectId` to the response with the `200` status | POST /api/reporting/{deploymentId}/reports/by-project/by-product
POST /api/reporting/{deploymentId}/reports/by-project/by-severity |
@@ -66,10 +65,7 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `filters.latestScanStatus` | POST /api/agent/deployments/{deploymentId}/repos/_provision | -| Added | added the new optional request property `filters.latestScanStatus` | POST /api/agent/deployments/{deploymentId}/repos/_secret | -| Added | added the new optional request property `filters.latestScanStatus` | POST /api/agent/deployments/{deploymentId}/repos/count | -| Added | added the new optional request property `filters.latestScanStatus` | PATCH /api/agent/deployments/{deploymentId}/repos/filtered | +| Added | added the new optional request property `filters.latestScanStatus` | PATCH /api/agent/deployments/{deploymentId}/repos/filtered
POST /api/agent/deployments/{deploymentId}/repos/_provision
POST /api/agent/deployments/{deploymentId}/repos/_secret
and 1 more |
@@ -81,9 +77,8 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Removed | removed the optional property `products.sast.autofix_pr_enabled` from the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products | +| Removed | removed the optional property `products.sast.autofix_pr_enabled` from the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products
PUT /api/agent/deployments/{deploymentId}/products | | Removed | removed the request property `sast.autofix_pr_enabled` | PUT /api/agent/deployments/{deploymentId}/products | -| Removed | removed the optional property `products.sast.autofix_pr_enabled` from the response with the `200` status | PUT /api/agent/deployments/{deploymentId}/products |
@@ -93,13 +88,8 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the request property `automation.triggerSource` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automation.triggerSource` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the request property `automation.triggerSource` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automation.triggerSource` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `triggerSource` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | | Added | added the new enum value `TOKEN_ROLE_READ_ONLY` to the `query` request parameter `roleFilter` | DELETE /api/tokens/v1/deployments/{deploymentId}/tokens | -| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations |
@@ -111,19 +101,10 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | PATCH /api/agent/deployments/{deploymentId}/findings/v2 | -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issue_counts | -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issue_filters | -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issues | -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issues/export | -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/agent/deployments/{deploymentId}/issues/search | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | PATCH /api/agent/deployments/{deploymentId}/findings/v2
POST /api/agent/deployments/{deploymentId}/issue_counts
POST /api/agent/deployments/{deploymentId}/issue_filters
and 5 more | | Added | added the optional property `packageManagerConfigsWithCredentials[].config.registryType` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | -| Added | added the new optional request property `packageManagerConfigForInsert.config.registryType` | POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | -| Added | added the optional property `packageManagerConfig.registryType` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | -| Added | added the new optional request property `packageManagerConfigForInsert.config.registryType` | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId} | -| Added | added the optional property `packageManagerConfig.registryType` to the response with the `200` status | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId} | -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/ai/deployments/{deploymentId}/memories/triage | -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | POST /api/ai/{deploymentId}/combined_task | +| Added | added the new optional request property `packageManagerConfigForInsert.config.registryType` | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}
POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | +| Added | added the optional property `packageManagerConfig.registryType` to the response with the `200` status | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}
POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | | Added | endpoint added | POST /api/issues/{deploymentId}/counts | | Added | endpoint added | GET /api/issues/{deploymentId}/issue/{issueId} | | Added | endpoint added | POST /api/issues/{deploymentId}/issue/{issueId} | @@ -131,8 +112,7 @@ are labeled; documentation-only edits are not listed. | Added | endpoint added | POST /api/issues/{deploymentId}/search | | Added | added the new optional request property `issueFilters.hasInFlightAutofixPr` | POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets | | Added | added the optional property `projects[].scmType` to the response with the `200` status | POST /api/v2/deployments/{deploymentId}/projects/list | -| Added | added the optional property `project.scmType` to the response with the `200` status | DELETE /api/v2/deployments/{deploymentId}/projects/{projectId} | -| Added | added the optional property `project.scmType` to the response with the `200` status | GET /api/v2/deployments/{deploymentId}/projects/{projectId} | +| Added | added the optional property `project.scmType` to the response with the `200` status | DELETE /api/v2/deployments/{deploymentId}/projects/{projectId}
GET /api/v2/deployments/{deploymentId}/projects/{projectId} |
@@ -154,18 +134,13 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test | +| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test
POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test | | Added | added the new `FIX_JOB_ERROR_TYPE_AI_PROVIDER_NOT_ALLOWED`, `FIX_JOB_ERROR_TYPE_ARCHIVED_REPOSITORY`, `FIX_JOB_ERROR_TYPE_AUTOFIX_DISABLED`, `FIX_JOB_ERROR_TYPE_GROUPING_DISABLED`, `FIX_JOB_ERROR_TYPE_INVALID_GROUP`, `FIX_JOB_ERROR_TYPE_NON_PRIMARY_BRANCH_DISABLED`, `FIX_JOB_ERROR_TYPE_UNSUPPORTED_ISSUE_TYPE` enum values to the `errors[].errorType` response property for the response status `200` | POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs | -| Added | added the optional property `products.ai.mdr_enabled` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products | -| Added | added the optional property `products.ai.mdr_enabled` to the response with the `200` status | PUT /api/agent/deployments/{deploymentId}/products | -| Added | added the optional property `errorDetail` to the response with the `200` status | POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test | -| Added | added the optional property `automations[].actions[].id` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new optional request property `automation.actions[].id` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the optional property `automation.actions[].id` to the response with the `200` status | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new optional request property `automation.actions[].id` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the optional property `automation.actions[].id` to the response with the `200` status | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `products.ai.mdr_enabled` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/products
PUT /api/agent/deployments/{deploymentId}/products | +| Added | added the optional property `automations[].actions[].id` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the new optional request property `automation.actions[].id` | POST /api/notifications/deployments/{deploymentId}/automations
PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `automation.actions[].id` to the response with the `200` status | POST /api/notifications/deployments/{deploymentId}/automations
PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | | Added | added the optional property `instances[].fixedTransitionState` to the response with the `200` status | GET /api/notifications/deployments/{deploymentId}/ticketing/v2 | -| Added | added the optional property `automations[].actions[].id` to the response with the `200` status | GET /api/v2/deployments/{deploymentId}/automations |
@@ -177,8 +152,7 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the `settings.tourStates[].id` response property for the response status `200` | GET /api/auth/users/current/settings | -| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the request property `tourStates[].id` | PATCH /api/auth/users/current/settings | +| Added | added the new `TOUR_ID_WORKFLOWS_ACTIVATION` enum value to the `id` request and response property | GET /api/auth/users/current/settings
PATCH /api/auth/users/current/settings | | Added | added the new `WorkflowsActivationBanner` enum value to the request property `eventSource` | PATCH /api/auth/users/current/settings |
@@ -191,18 +165,8 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation.filters.conditions[].type` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation.triggerSource` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation.triggerSource` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the request property `automation.filters.conditions[].type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the request property `automation.triggerSource` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automation.triggerSource` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `automations[].triggerSource` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `triggerSource` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more |
@@ -214,26 +178,16 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `includeFirstDetectedAt` | POST /api/agent/deployments/{deploymentId}/issues | +| Added | added the new optional request property `includeFirstDetectedAt` | POST /api/agent/deployments/{deploymentId}/issues
POST /api/agent/deployments/{deploymentId}/issues/export | | Added | added the optional property `issues[].issue.exceptionRequest.requesterName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | | Added | added the optional property `issues[].issue.exceptionRequest.reviewerName` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | | Added | added the optional property `issues[].issue.firstDetectedAt` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/issues | -| Added | added the new optional request property `includeFirstDetectedAt` | POST /api/agent/deployments/{deploymentId}/issues/export | | Added | added the optional property `exceptionRequest.requesterName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | | Added | added the optional property `exceptionRequest.reviewerName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | | Added | added the optional property `firstDetectedAt` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | -| Added | added the optional property `users[].scimManaged` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/users | -| Added | added the optional property `users[].scimManaged` to the response with the `200` status | POST /api/agent/deployments/{deploymentId}/users/list | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations[].actions[].type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation.actions[].type` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation.filters.conditions[].type` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation.actions[].type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | POST /api/notifications/deployments/{deploymentId}/automations | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the request property `automation.actions[].type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the request property `automation.filters.conditions[].type` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automation.actions[].type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automation.filters.conditions[].type` response property for the response status `200` | PUT /api/notifications/deployments/{deploymentId}/automations/{automationId} | +| Added | added the optional property `users[].scimManaged` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/users
POST /api/agent/deployments/{deploymentId}/users/list | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | | Added | added the new optional request property `issueTypes` | POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets | | Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId} | | Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules | @@ -241,17 +195,11 @@ are labeled; documentation-only edits are not listed. | Added | added the optional property `findings[].exceptionRequest.requesterName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | | Added | added the optional property `findings[].exceptionRequest.reviewerName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | | Added | added the optional property `findings[].firstDetectedAt` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | -| Added | added the new optional request property `filters.timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory | -| Added | added the new optional request property `filters.timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem | -| Added | added the new optional request property `filters.timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem | +| Added | added the new optional request property `filters.timeBucketSize` | POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory
POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem
POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem | | Added | endpoint added | POST /api/reporting/{deploymentId}/reports/malware-firewall/scan-activity | | Added | endpoint added | POST /api/reporting/{deploymentId}/reports/malware-firewall/summary | | Added | added the new optional `query` request parameter `dependencyFilter.includeArchived` | GET /api/sca/deployments/{deploymentId}/dependencies | -| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/sca/deployments/{deploymentId}/dependencies | -| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/sca/deployments/{deploymentId}/repositories | -| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `automations[].actions[].type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `automations[].filters.conditions[].type` response property for the response status `200` | GET /api/v2/deployments/{deploymentId}/automations | +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/sca/deployments/{deploymentId}/dependencies
POST /api/sca/deployments/{deploymentId}/repositories
POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles |
diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index 26573248a1..aede775a42 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -410,6 +410,133 @@ def coalesce_changes(changes: list) -> list: return merged +# Enum-addition checks, which get a second merge pass beyond the identical-text +# one. An enum is a shared type: `automation.triggerSource` and +# `automations[].triggerSource` are the same enum field reached through a +# different wrapper, and a value added to it applies to requests and responses +# alike. So for these -- and only these -- the wrapper path and the direction +# are normalised away, taking one enum value from three rows to one. +# +# Not done for the other checks. Collapsing `a.id` and `b.id` to `id` would +# conflate genuinely different fields, and a type change on one of them is not +# a type change on the other. Enum values are safe because the value itself is +# part of the key: two fields do not gain the same new constant by coincidence. +ENUM_ADDITION_SUFFIX = "-enum-value-added" +ENUM_VALUE_RE = re.compile(r"`([A-Z][A-Z0-9_]{2,})`") +ENUM_FIELD_RE = re.compile(r"`([A-Za-z][A-Za-z0-9_.\[\]]*)`") + + +def merge_enum_additions(changes: list) -> list: + """Fold an enum addition into one row regardless of wrapper or direction. + + Runs after merge_across_endpoints, so the endpoint lists it finds are + already deduplicated and it only has to union them. + """ + buckets: dict = {} + order: list = [] + for change in changes: + if not str(change.get("id", "")).endswith(ENUM_ADDITION_SUFFIX): + key = ("verbatim", len(order)) + buckets[key] = [change] + else: + text = change["text"] + values = tuple(sorted(set(ENUM_VALUE_RE.findall(text)))) + fields = [f for f in ENUM_FIELD_RE.findall(text) if not f.isupper()] + leaf = fields[0].split(".")[-1].removesuffix("[]") if fields else "" + key = ("enum", values, leaf) + buckets.setdefault(key, []).append(change) + if key not in order: + order.append(key) + + merged = [] + for key in order: + group = buckets[key] + if key[0] != "enum" or len(group) == 1: + merged.append(group[0]) + continue + values, leaf = key[1], key[2] + listed = ", ".join(f"`{v}`" for v in values) + plural = "values" if len(values) != 1 else "value" + directions = sorted( + {"request" if "request" in c["text"] else "response" for c in group} + ) + endpoints: set = set() + for c in group: + endpoints.update( + c.get("endpoints") or [(c.get("operation"), c.get("path"))] + ) + merged.append( + { + **group[0], + "text": ( + f"added the new {listed} enum {plural} to the `{leaf}` " + f"{' and '.join(directions)} property" + ), + "endpoints": sorted(e for e in endpoints if e[1]), + } + ) + return merged + + +# How many endpoint pills a merged row shows before it truncates. +MAX_ENDPOINT_PILLS = 3 + + +# Checks whose subject *is* the endpoint, rather than something shared that +# happens to surface on one. These must never merge across endpoints: two +# endpoints being added on the same day both read "endpoint added", and folding +# them into one row would hide the endpoints behind "and N more" -- which is +# the only information those rows carry. +ENDPOINT_SCOPED_PREFIXES = ("endpoint-", "api-") + + +def _is_endpoint_scoped(change: dict) -> bool: + return str(change.get("id", "")).startswith(ENDPOINT_SCOPED_PREFIXES) + + +def merge_across_endpoints(changes: list) -> list: + """Fold one change surfaced on several endpoints into a single row. + + A change to a *shared type* is not an endpoint-level change. One protobuf + enum gaining one value, or one field going from `uint32` to `int64`, + surfaces once per endpoint that touches it -- `TRIGGER_SOURCE_PATCH_READY` + produced six rows on one day, and the `uint32` sweep produced dozens. The + reader wants "this changed, here is where it shows up", not the same + sentence restated per endpoint. + + Merges only rows whose wording is *identical*, so nothing is lost: the + property path, direction and status code all still have to match. That is + the inverse of what COALESCIBLE does, where the identifier varies and the + endpoint is fixed. + + A merged change carries an `endpoints` list, which the table renderer turns + into several pills in one cell. Order follows first appearance. + """ + buckets: dict = {} + order: list = [] + for change in changes: + if _is_endpoint_scoped(change): + key = ("verbatim", len(order)) + buckets[key] = [change] + else: + key = (change.get("id"), change["text"]) + buckets.setdefault(key, []).append(change) + if key not in order: + order.append(key) + + merged = [] + for key in order: + group = buckets[key] + if len(group) == 1: + merged.append(group[0]) + continue + endpoints = sorted( + {(c.get("operation"), c.get("path")) for c in group if c.get("path")} + ) + merged.append({**group[0], "endpoints": endpoints}) + return merged + + HTTP_METHODS = frozenset( ("get", "post", "put", "patch", "delete", "options", "head", "trace") ) @@ -653,11 +780,39 @@ def _endpoint_pill(key: tuple, links: dict) -> str: ) +def _endpoints_cell(change: dict, links: dict) -> str: + """The Endpoint cell: one pill, or several for a change that spans them. + + Truncated at MAX_ENDPOINT_PILLS. A shared-type change can touch a dozen + endpoints, and a cell that tall pushes everything else off the screen -- + the reader needs to know it is broad, not to read every path. + """ + endpoints = change.get("endpoints") + if not endpoints: + return _endpoint_pill(_endpoint_key(change), links) + + shown = endpoints[:MAX_ENDPOINT_PILLS] + pills = [_endpoint_pill((0, path, operation), links) for operation, path in shown] + hidden = endpoints[len(shown):] + if hidden: + # Name the hidden ones on hover rather than making them unreachable. + # The cell is truncated to keep the row a readable height, not to + # withhold the list. + listed = _escape_jsx_text( + ", ".join(f"{operation} {path}" for operation, path in hidden) + ) + pills.append(f'and {len(hidden)} more') + return "
".join(pills) + + def _render_section_table(changes: list, links: dict) -> list: """One table for a severity section: Change | Description | Endpoint.""" - groups: dict[tuple, list] = {} - for change in changes: - groups.setdefault(_endpoint_key(change), []).append(change) + # Sort by the first endpoint so rows still cluster by area, then by the + # change itself. A merged row sorts by the first endpoint it lists. + def sort_key(change: dict) -> tuple: + endpoints = change.get("endpoints") + key = (0,) + endpoints[0][::-1] if endpoints else _endpoint_key(change) + return (key, change["id"], change["text"]) # The div carries a hook for docs/styles.css, which hugs the Change # column to its chip; plain markdown tables offer no width control. @@ -667,15 +822,13 @@ def _render_section_table(changes: list, links: dict) -> list: "| Change | Description | Endpoint |", "|---|---|---|", ] - for key in sorted(groups): - cell = _endpoint_pill(key, links) - for change in sorted(groups[key], key=lambda c: (c["id"], c["text"])): - verb, color = _change_verb(change) - description = escape_mdx(change["text"]).replace("|", "\\|") - lines.append( - f'| {verb}' - f" | {description} | {cell} |" - ) + for change in sorted(changes, key=sort_key): + verb, color = _change_verb(change) + description = escape_mdx(change["text"]).replace("|", "\\|") + lines.append( + f'| {verb}' + f" | {description} | {_endpoints_cell(change, links)} |" + ) lines.extend(["", "
"]) return lines @@ -703,7 +856,7 @@ def _render_update( ) render_section = _render_section_table if style == "table" else _render_section - changes = coalesce_changes(entry.changes) + changes = merge_enum_additions(merge_across_endpoints(coalesce_changes(entry.changes))) lines = [f""] first = True for level in (BREAKING, POTENTIALLY_BREAKING, 1): diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index 0768e391b2..3c7d751c57 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -420,6 +420,74 @@ def test_build_entries_keeps_real_changes_shipped_alongside_a_retag(): assert [c["id"] for c in entries[0].changes] == ["endpoint-added"] +def _shared_type_change(op, path): + """The same change surfaced on a different endpoint: identical wording.""" + return { + "id": "response-property-enum-value-added", "level": 1, "section": "paths", + "operation": op, "path": path, + "text": "added the new `X` enum value to the `s` response property", + } + + +def test_merge_across_endpoints_folds_one_change_surfaced_on_many(): + merged = gac.merge_across_endpoints( + [_shared_type_change("GET", "/a"), _shared_type_change("POST", "/b")] + ) + + assert len(merged) == 1 + assert merged[0]["endpoints"] == [("GET", "/a"), ("POST", "/b")] + + +def test_merge_across_endpoints_keeps_differently_worded_changes_apart(): + a = _shared_type_change("GET", "/a") + b = {**_shared_type_change("GET", "/b"), "text": "a different sentence"} + + assert len(gac.merge_across_endpoints([a, b])) == 2 + + +def test_merge_across_endpoints_never_folds_endpoint_scoped_changes(): + """Two endpoints added on one day both read "endpoint added"; folding them + would hide the endpoints behind "and N more", which is all those rows say.""" + added = [ + {"id": "endpoint-added", "level": 1, "section": "paths", + "operation": m, "path": p, "text": "endpoint added"} + for m, p in (("GET", "/a"), ("POST", "/b")) + ] + + merged = gac.merge_across_endpoints(added) + + assert len(merged) == 2 + assert all("endpoints" not in c for c in merged) + + +def test_truncated_endpoint_cell_names_the_hidden_ones_in_a_tooltip(): + change = { + **_shared_type_change("GET", "/a"), + "endpoints": [("GET", f"/e{i}") for i in range(6)], + } + + cell = gac._endpoints_cell(change, links={}) + + assert cell.count("
@@ -88,7 +88,7 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `triggerSource` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | +| Added | added the new `TRIGGER_SOURCE_PATCH_READY` enum value to the `triggerSource` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | | Added | added the new enum value `TOKEN_ROLE_READ_ONLY` to the `query` request parameter `roleFilter` | DELETE /api/tokens/v1/deployments/{deploymentId}/tokens |
@@ -101,7 +101,7 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `filter.hasInFlightAutofixPr` | PATCH /api/agent/deployments/{deploymentId}/findings/v2
POST /api/agent/deployments/{deploymentId}/issue_counts
POST /api/agent/deployments/{deploymentId}/issue_filters
and 5 more | +| Added | added the new optional request property `filter.hasInFlightAutofixPr` | PATCH /api/agent/deployments/{deploymentId}/findings/v2
POST /api/agent/deployments/{deploymentId}/issue_counts
POST /api/agent/deployments/{deploymentId}/issue_filters
and 5 more | | Added | added the optional property `packageManagerConfigsWithCredentials[].config.registryType` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | | Added | added the new optional request property `packageManagerConfigForInsert.config.registryType` | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}
POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | | Added | added the optional property `packageManagerConfig.registryType` to the response with the `200` status | PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}
POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs | @@ -165,8 +165,8 @@ are labeled; documentation-only edits are not listed. | Change | Description | Endpoint | |---|---|---| -| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | -| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `triggerSource` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | +| Added | added the new `CONDITION_TYPE_BREAKING_CHANGE`, `CONDITION_TYPE_FULL_SCAN` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | +| Added | added the new `TRIGGER_SOURCE_UPGRADE_GUIDANCE` enum value to the `triggerSource` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more |
@@ -186,8 +186,8 @@ are labeled; documentation-only edits are not listed. | Added | added the optional property `exceptionRequest.reviewerName` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | | Added | added the optional property `firstDetectedAt` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId} | | Added | added the optional property `users[].scimManaged` to the response with the `200` status | GET /api/agent/deployments/{deploymentId}/users
POST /api/agent/deployments/{deploymentId}/users/list | -| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | -| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | +| Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | +| Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | | Added | added the new optional request property `issueTypes` | POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets | | Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId} | | Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules | diff --git a/docs/styles.css b/docs/styles.css index edf3780f4c..4b56f52b94 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -48,4 +48,8 @@ h1 { [data-component-part="tooltip-content"] { max-width: min(28rem, calc(100vw - 2rem)); overflow-wrap: anywhere; + /* The hovercard lists one endpoint per line. `pre-line` honours those + newlines while still collapsing runs of spaces and wrapping long + paths, which `pre` would not. */ + white-space: pre-line; } diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index aede775a42..6f90c0aabc 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -798,10 +798,17 @@ def _endpoints_cell(change: dict, links: dict) -> str: # Name the hidden ones on hover rather than making them unreachable. # The cell is truncated to keep the row a readable height, not to # withhold the list. - listed = _escape_jsx_text( - ", ".join(f"{operation} {path}" for operation, path in hidden) + # + # One bullet per line, not a comma-separated run: paths are long + # enough to wrap, so a run gives no way to tell where one ends and + # the next begins. Passed as a JSX expression rather than an + # attribute string so the newlines survive -- json.dumps handles the + # escaping, and braces inside a JS string literal need no entity + # encoding. `white-space: pre-line` in styles.css renders the breaks. + listed = "\n".join(f"\u2022 {operation} {path}" for operation, path in hidden) + pills.append( + f"and {len(hidden)} more" ) - pills.append(f'and {len(hidden)} more') return "
".join(pills) diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index 3c7d751c57..4603f514f0 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -475,17 +475,58 @@ def test_truncated_endpoint_cell_names_the_hidden_ones_in_a_tooltip(): assert f"GET /e{i}" in cell -def test_truncation_escapes_braces_in_the_tooltip(): +def _tip_of(cell: str) -> str: + """The tooltip's text, decoded from the JSX expression carrying it.""" + literal = cell[cell.index("tip={") + 5 : cell.index("}>and")] + return json.loads(literal) + + +def test_truncation_lists_one_endpoint_per_line(): + """A comma-separated run of paths gives the reader no way to tell where + one path ends and the next begins -- they are long enough to wrap.""" + change = { + **_shared_type_change("GET", "/a"), + "endpoints": [("GET", f"/e{i}") for i in range(6)], + } + + tip = _tip_of(gac._endpoints_cell(change, links={})) + + assert tip.splitlines() == [ + "\u2022 GET /e3", + "\u2022 GET /e4", + "\u2022 GET /e5", + ] + assert ", " not in tip + + +def test_the_tooltip_survives_braces_in_a_path(): + """Braces open a JSX expression as bare text, but are ordinary characters + inside the string literal the tip is passed as -- so they must NOT be + entity-encoded, or the reader sees `{` instead of `{`.""" change = { **_shared_type_change("GET", "/a"), "endpoints": [("GET", f"/x/{{id{i}}}") for i in range(5)], } cell = gac._endpoints_cell(change, links={}) - tip = cell[cell.index('tip="') + 5 : cell.index('">and')] + tip = _tip_of(cell) # raises if the literal is not valid JSON/JS + + assert "{" not in tip + assert tip.endswith("\u2022 GET /x/{id4}") + + +def test_the_tooltip_literal_has_no_raw_newlines(): + """A raw newline is not legal inside a JS string literal, and the row is + emitted as a single Markdown table line regardless.""" + change = { + **_shared_type_change("GET", "/a"), + "endpoints": [("GET", f"/e{i}") for i in range(6)], + } + + cell = gac._endpoints_cell(change, links={}) - assert "{" not in tip and "}" not in tip # would open a JSX expression - assert "{" in tip + assert "\n" not in cell + assert "\\n" in cell # the escape sequence, not the character def test_build_entries_drops_unparseable_base_and_promotes_revision(): From 90462724971043f9cbc72ff388ebc3f3a4fda58c Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 10 Sep 2026 14:27:21 -0700 Subject: [PATCH 12/27] fix(api-reference): make the changelog usable for tracking deprecations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems, both found by running the deprecation lifecycle through the pinned oasdiff (v1.28.0) rather than reasoning about it. **A deprecation announcement was filed as "Non-breaking."** oasdiff rates `endpoint-deprecated` INFO, which put it under "Changes" with that tag. The announcement is the entire advance warning a caller gets, and "Non-breaking" hides it from the one reader who most needs it: someone filtering the changelog for what is going to break them. Re-levelled to potentially breaking -- true on the day it ships, where "Breaking" would cry wolf. This is not hypothetical; three v1 policies endpoints are already deprecated, and that day moves from "6 changes / Non-breaking" to "3 potentially breaking · 3 other changes". **A removal done right produced no entry at all.** oasdiff reports a removal only when something went wrong with it -- gone with no deprecation, or gone before its published sunset date. Retire an endpoint exactly as the policy prescribes and `oasdiff changelog` says "No changes to report", so the day the endpoint stopped answering was the one day missing from the changelog. The changelog recorded only the removals we botched. There is no oasdiff flag for that case; the check does not exist upstream. So `sunset_removals` diffs the two snapshots' operation sets directly and synthesizes the entry, staying quiet whenever oasdiff already had something to say so a removal never lands twice. Both need `x-sunset` in the published spec to work, which protobuf-tools#202 now emits -- `x-deprecation` alone is invisible to oasdiff. Not doing the third thing considered here: `--deprecation-days-stable 180` would re-check every historical deprecation against *today*, so regenerating the page would retroactively flag long-served windows as too short. The notice windows are enforced at the proto level by protobuf-tools#203, which is the right layer -- it fails the change before it ships rather than narrating it afterwards. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v1/Changelog.mdx | 15 +- docs/api-reference/v2/Changelog.mdx | 17 ++- scripts/generate_api_changelog.py | 100 +++++++++++++ scripts/tests/test_generate_api_changelog.py | 145 +++++++++++++++++++ 4 files changed, 270 insertions(+), 7 deletions(-) diff --git a/docs/api-reference/v1/Changelog.mdx b/docs/api-reference/v1/Changelog.mdx index 42052e918e..8397c32543 100644 --- a/docs/api-reference/v1/Changelog.mdx +++ b/docs/api-reference/v1/Changelog.mdx @@ -41,18 +41,27 @@ are labeled; documentation-only edits are not listed.
- -**Changes** + +**Potentially breaking changes**
| Change | Description | Endpoint | |---|---|---| -| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies
POST /api/v1/deployments/{deploymentId}/dependencies/repositories
POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | | Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies | | Deprecated | endpoint deprecated | GET /api/v1/deployments/{deploymentId}/policies/{policyId} | | Deprecated | endpoint deprecated | PUT /api/v1/deployments/{deploymentId}/policies/{policyId} | +
+ +**Changes** + +
+ +| Change | Description | Endpoint | +|---|---|---| +| Added | added the new optional request property `dependencyFilter.includeArchived` | POST /api/v1/deployments/{deploymentId}/dependencies
POST /api/v1/deployments/{deploymentId}/dependencies/repositories
POST /api/v1/deployments/{deploymentId}/dependencies/repositories/{repositoryId}/lockfiles | +
diff --git a/docs/api-reference/v2/Changelog.mdx b/docs/api-reference/v2/Changelog.mdx index 38f97840b6..3474752aa9 100644 --- a/docs/api-reference/v2/Changelog.mdx +++ b/docs/api-reference/v2/Changelog.mdx @@ -171,7 +171,19 @@ are labeled; documentation-only edits are not listed.
- + +**Potentially breaking changes** + +
+ +| Change | Description | Endpoint | +|---|---|---| +| Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId} | +| Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules | +| Deprecated | endpoint deprecated | PUT /api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath} | + +
+ **Changes**
@@ -189,9 +201,6 @@ are labeled; documentation-only edits are not listed. | Added | added the new `ACTION_TYPE_OPEN_AUTOFIX_PR`, `ACTION_TYPE_TRIGGER_WORKFLOW` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | | Added | added the new `CONDITION_TYPE_CWE`, `CONDITION_TYPE_OWASP` enum values to the `type` request and response property | GET /api/notifications/deployments/{deploymentId}/automations
GET /api/v2/deployments/{deploymentId}/automations
POST /api/notifications/deployments/{deploymentId}/automations
and 1 more | | Added | added the new optional request property `issueTypes` | POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets | -| Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId} | -| Deprecated | endpoint deprecated | GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules | -| Deprecated | endpoint deprecated | PUT /api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath} | | Added | added the optional property `findings[].exceptionRequest.requesterName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | | Added | added the optional property `findings[].exceptionRequest.reviewerName` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | | Added | added the optional property `findings[].firstDetectedAt` to the response with the `200` status | POST /api/reporting/{deploymentId}/findings | diff --git a/scripts/generate_api_changelog.py b/scripts/generate_api_changelog.py index 6f90c0aabc..82f0ce4b4b 100644 --- a/scripts/generate_api_changelog.py +++ b/scripts/generate_api_changelog.py @@ -85,6 +85,16 @@ # side needs correcting. LEVEL_OVERRIDES = { "response-property-enum-value-added": 1, + # oasdiff files a deprecation announcement as INFO, which lands it under + # "Changes" with the filter tag "Non-breaking". That is the wrong shelf: + # the announcement is the only advance warning a caller gets, and the one + # reader who most needs it -- someone filtering the changelog for what will + # break them -- is exactly the reader a "Non-breaking" tag hides it from. + # It is not breaking on the day it ships, so BREAKING would cry wolf; + # POTENTIALLY_BREAKING says what is true, that this breaks you unless you + # act before the sunset date. + "endpoint-deprecated": 2, + "endpoint-deprecated-with-sunset": 2, } # oasdiff names properties with JSON Schema's internal vocabulary, which leaks @@ -294,10 +304,98 @@ def _revision_arg(snapshot: Snapshot, spec_rel: str) -> str: return f"{snapshot.ref}:{spec_rel}" if snapshot.ref else spec_rel +# Our own check id for a removal oasdiff stays silent about. Prefixed +# "endpoint-" so it reads as endpoint-scoped and chips as "Removed", like the +# oasdiff removals it sits beside. +SUNSET_REMOVAL_ID = "endpoint-removed-after-sunset" + + +def load_spec(snapshot: Snapshot, spec_rel: str, cwd: Path) -> Optional[dict]: + """A snapshot's parsed spec, or None if it cannot be read.""" + try: + if snapshot.ref is None: + text = (cwd / spec_rel).read_text() + else: + text = subprocess.run( + ["git", "show", f"{snapshot.ref}:{spec_rel}"], + capture_output=True, text=True, check=True, cwd=cwd, + ).stdout + return yaml.safe_load(text) + except (subprocess.CalledProcessError, OSError, yaml.YAMLError) as exc: + print(f"warning: could not read {spec_rel} at {snapshot.ref}: {exc}", file=sys.stderr) + return None + + +def spec_operations(spec: dict) -> dict: + """(METHOD, path) -> the operation's `x-sunset` date, "" when it has none.""" + ops = {} + for path, item in ((spec or {}).get("paths") or {}).items(): + if not isinstance(item, dict): + continue + for method, op in item.items(): + if method not in HTTP_METHODS or not isinstance(op, dict): + continue + ops[(method.upper(), path)] = str(op.get("x-sunset") or "") + return ops + + +def sunset_removals( + base: Snapshot, + rev: Snapshot, + spec_rel: str, + load: Optional[Callable[[Snapshot], Optional[dict]]], + reported: list, +) -> list: + """Removals that honoured their sunset date, which oasdiff omits entirely. + + oasdiff reports a removal only when something went wrong with it -- gone + with no deprecation, or gone before the sunset date it published. Retire an + endpoint exactly as the policy prescribes and `oasdiff changelog` says "No + changes to report", so the day the endpoint actually stopped answering is + the one day missing from the changelog. That inverts the incentive: the + changelog would record only the removals we botched. + + Detected by diffing the two snapshots' operation sets rather than by asking + oasdiff differently, because there is no flag for this -- the check simply + does not exist upstream. + """ + if load is None: + return [] + base_spec, rev_spec = load(base), load(rev) + if base_spec is None or rev_spec is None: + return [] + + surviving = spec_operations(rev_spec) + already = { + (c.get("operation"), c.get("path")) + for c in reported + if "removed" in str(c.get("id", "")).split("-") + } + + removals = [] + for (method, path), sunset in sorted(spec_operations(base_spec).items()): + # No sunset date, or oasdiff already had something to say: either way + # not ours to report. + if not sunset or (method, path) in surviving or (method, path) in already: + continue + removals.append( + { + "id": SUNSET_REMOVAL_ID, + "text": f"endpoint removed after its sunset date `{sunset}`", + "level": BREAKING, + "operation": method, + "path": path, + "section": "paths", + } + ) + return removals + + def build_entries( snapshots: list[Snapshot], spec_rel: str, diff: Callable[[str, str], Optional[list]], + load: Optional[Callable[[Snapshot], Optional[dict]]] = None, ) -> list[Entry]: """Diff consecutive snapshots into per-date entries, newest first. @@ -326,6 +424,7 @@ def build_entries( if c.get("section") not in EXCLUDED_SECTIONS and c.get("id") not in EXCLUDED_CHECKS ] + changes += sunset_removals(last_good, snapshot, spec_rel, load, changes) changes = [humanize_change_text(apply_level_override(c)) for c in changes] if changes: entries.append(Entry(snapshot.date, changes)) @@ -1006,6 +1105,7 @@ def main(argv: Optional[list[str]] = None) -> int: snapshots, spec_rel, lambda base, rev: run_oasdiff(base, rev, oasdiff_bin=args.oasdiff_bin, cwd=cwd), + lambda snapshot: load_spec(snapshot, spec_rel, cwd), ) if args.max_entries: entries = entries[: args.max_entries] diff --git a/scripts/tests/test_generate_api_changelog.py b/scripts/tests/test_generate_api_changelog.py index 4603f514f0..12461270f9 100644 --- a/scripts/tests/test_generate_api_changelog.py +++ b/scripts/tests/test_generate_api_changelog.py @@ -1165,3 +1165,148 @@ def test_run_oasdiff_error_returns_none(tmp_path): good = tmp_path / "good.yaml" good.write_text(BASE_SPEC) assert gac.run_oasdiff(str(bad), str(good), oasdiff_bin="oasdiff", cwd=tmp_path) is None + + +# --- post-sunset removals --------------------------------------------------- +# +# oasdiff reports a removal only when it went wrong. A removal that served its +# full notice window produces no change at all, so the generator has to notice +# the endpoint is gone by itself. + + +def spec_with(*operations): + """A minimal spec. Each operation is (method, path, sunset-or-None).""" + paths = {} + for method, path, sunset in operations: + op = {"responses": {"200": {"description": "ok"}}} + if sunset: + op["deprecated"] = True + op["x-sunset"] = sunset + paths.setdefault(path, {})[method] = op + return {"openapi": "3.0.0", "paths": paths} + + +def test_spec_operations_reads_the_sunset_date(): + spec = spec_with(("get", "/a", "2027-03-01"), ("post", "/b", None)) + + assert gac.spec_operations(spec) == {("GET", "/a"): "2027-03-01", ("POST", "/b"): ""} + + +def test_spec_operations_ignores_non_method_keys(): + spec = {"paths": {"/a": {"parameters": [], "get": {}}, "/b": "nonsense"}} + + assert gac.spec_operations(spec) == {("GET", "/a"): ""} + + +def sunset_removals_between(before, after, reported=()): + base, rev = gac.Snapshot("aaa", "2026-07-14"), gac.Snapshot("bbb", "2026-08-07") + specs = {base: before, rev: after} + return gac.sunset_removals(base, rev, "spec.yaml", specs.get, list(reported)) + + +def test_synthesizes_a_removal_oasdiff_stays_silent_about(): + removals = sunset_removals_between( + spec_with(("get", "/gone", "2026-08-01"), ("get", "/kept", None)), + spec_with(("get", "/kept", None)), + ) + + assert removals == [ + { + "id": "endpoint-removed-after-sunset", + "text": "endpoint removed after its sunset date `2026-08-01`", + "level": gac.BREAKING, + "operation": "GET", + "path": "/gone", + "section": "paths", + } + ] + + +def test_stays_quiet_when_oasdiff_already_reported_the_removal(): + """An early removal is oasdiff's to report; two rows for it would be wrong.""" + reported = [ + { + "id": "api-path-removed-before-sunset", + "operation": "GET", + "path": "/gone", + "level": 3, + } + ] + removals = sunset_removals_between( + spec_with(("get", "/gone", "2027-03-01")), spec_with(), reported + ) + + assert removals == [] + + +def test_stays_quiet_for_a_removal_that_was_never_deprecated(): + """oasdiff rates that api-path-removed-without-deprecation; not ours.""" + removals = sunset_removals_between( + spec_with(("get", "/gone", None)), spec_with() + ) + + assert removals == [] + + +def test_stays_quiet_when_the_endpoint_survives(): + removals = sunset_removals_between( + spec_with(("get", "/kept", "2027-03-01")), + spec_with(("get", "/kept", "2027-03-01")), + ) + + assert removals == [] + + +def test_an_unreadable_spec_synthesizes_nothing_rather_than_guessing(): + base, rev = gac.Snapshot("aaa", "2026-07-14"), gac.Snapshot("bbb", "2026-08-07") + + assert gac.sunset_removals(base, rev, "spec.yaml", lambda s: None, []) == [] + assert gac.sunset_removals(base, rev, "spec.yaml", None, []) == [] + + +def test_build_entries_reports_the_day_a_sunset_endpoint_stopped_answering(): + snaps = snapshots_for(("bbb", "2026-08-07"), ("aaa", "2026-07-14")) + specs = { + snaps[1]: spec_with(("get", "/gone", "2026-08-01")), + snaps[0]: spec_with(), + } + + entries = gac.build_entries(snaps, "spec.yaml", lambda b, r: [], specs.get) + + assert len(entries) == 1 + assert entries[0].date == "2026-08-07" + (change,) = entries[0].changes + assert change["id"] == "endpoint-removed-after-sunset" + assert gac._change_verb(change) == ("Removed", "red") + assert gac._update_tag(entries[0].changes) == "Breaking" + + +# --- deprecation announcements are not "Non-breaking" ----------------------- + + +def test_deprecation_announcements_are_re_levelled_to_potentially_breaking(): + for check_id in ("endpoint-deprecated", "endpoint-deprecated-with-sunset"): + change = {"id": check_id, "level": 1, "text": "endpoint deprecated"} + + assert gac.apply_level_override(change)["level"] == gac.POTENTIALLY_BREAKING + + +def test_a_deprecation_day_is_filterable_as_potentially_breaking(): + """The advance notice is the whole point of the feed; a reader filtering + for what will break them must not have it hidden behind "Non-breaking".""" + snaps = snapshots_for(("bbb", "2026-08-07"), ("aaa", "2026-07-14")) + announcement = [ + { + "id": "endpoint-deprecated-with-sunset", + "text": "endpoint deprecated with sunset date `2027-03-01`", + "level": 1, + "operation": "GET", + "path": "/going", + "section": "paths", + } + ] + + entries = gac.build_entries(snaps, "spec.yaml", lambda b, r: announcement) + + assert gac._update_tag(entries[0].changes) == "Potentially breaking" + assert gac._change_verb(entries[0].changes[0]) == ("Deprecated", "yellow") From 41f082ec869ac924760e0dbabe259ce6c5a34d7c Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 11:53:23 -0700 Subject: [PATCH 13/27] docs(api-reference): publish the API deprecation policy Adds a Deprecation Policy page alongside the changelog in both API versions, from the customer-facing half of the APPEX-956 policy doc. The internal appendix (notice channels, the 410-vs-301 decision record, open gaps) is not published. The body lives in a snippet imported by both version pages. Terms-of-Use duplicates its one sentence per version, but 30 lines of policy would drift, and the repo already shares longer content this way (see snippets/metrics.mdx). One content change from the source doc: it was written before we decided to publish a changelog, so "Finding out what is deprecated today" had no push channel -- only the reference, response headers, and support. It now leads with the changelog and its RSS feed, which records every deprecation tagged Deprecated on the day it ships. Deliberately not carried over: the in-app banner and admin email named in the internal appendix. Neither exists yet (appendix gap 4), so publishing them would commit us to channels we cannot serve. The invented /api/migrations/ URL (gap 5) appears only in the appendix and is not published either. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v1/Deprecation-Policy.mdx | 10 ++++++ docs/api-reference/v2/Deprecation-Policy.mdx | 10 ++++++ docs/docs.json | 6 ++-- docs/snippets/api-deprecation-policy.mdx | 33 ++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 docs/api-reference/v1/Deprecation-Policy.mdx create mode 100644 docs/api-reference/v2/Deprecation-Policy.mdx create mode 100644 docs/snippets/api-deprecation-policy.mdx diff --git a/docs/api-reference/v1/Deprecation-Policy.mdx b/docs/api-reference/v1/Deprecation-Policy.mdx new file mode 100644 index 0000000000..d309753338 --- /dev/null +++ b/docs/api-reference/v1/Deprecation-Policy.mdx @@ -0,0 +1,10 @@ +--- +title: "Deprecation Policy" +description: "How much notice you get before a Semgrep API endpoint changes, how you will hear about it, and what happens at sunset." +--- + +{/* Shared with the other API version -- edit /snippets/api-deprecation-policy.mdx */} + +import ApiDeprecationPolicy from "/snippets/api-deprecation-policy.mdx" + + diff --git a/docs/api-reference/v2/Deprecation-Policy.mdx b/docs/api-reference/v2/Deprecation-Policy.mdx new file mode 100644 index 0000000000..d309753338 --- /dev/null +++ b/docs/api-reference/v2/Deprecation-Policy.mdx @@ -0,0 +1,10 @@ +--- +title: "Deprecation Policy" +description: "How much notice you get before a Semgrep API endpoint changes, how you will hear about it, and what happens at sunset." +--- + +{/* Shared with the other API version -- edit /snippets/api-deprecation-policy.mdx */} + +import ApiDeprecationPolicy from "/snippets/api-deprecation-policy.mdx" + + diff --git a/docs/docs.json b/docs/docs.json index 72163dc9c3..20bb976330 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -619,7 +619,8 @@ "api-reference/v1/Introduction", "api-reference/v1/Authentication", "api-reference/v1/Terms-of-Use", - "api-reference/v1/Changelog" + "api-reference/v1/Changelog", + "api-reference/v1/Deprecation-Policy" ] }, { @@ -641,7 +642,8 @@ "api-reference/v2/Introduction", "api-reference/v2/Authentication", "api-reference/v2/Terms-of-Use", - "api-reference/v2/Changelog" + "api-reference/v2/Changelog", + "api-reference/v2/Deprecation-Policy" ] }, { diff --git a/docs/snippets/api-deprecation-policy.mdx b/docs/snippets/api-deprecation-policy.mdx new file mode 100644 index 0000000000..338a913c1b --- /dev/null +++ b/docs/snippets/api-deprecation-policy.mdx @@ -0,0 +1,33 @@ +## What this policy promises + +APIs change. This policy tells you how much warning you get before a change breaks your integration. + +In short: **stable endpoints get 6 months' notice before a breaking change, and 3 months before an already-deprecated optional field is removed.** After the deadline passes, the endpoint returns `410 Gone` with a machine-readable pointer to its replacement — it does not start returning wrong or partial data. + +## What this policy covers + +This policy applies to every endpoint documented on [docs.semgrep.dev](https://docs.semgrep.dev/), according to its maturity level. + +| Maturity | What we promise | Notice before removal or a breaking change | +| --- | --- | --- | +| **Stable** | Fully covered by this policy. | 6 months (breaking), 3 months (non-breaking removal) | +| **Beta** | Documented and supported. We keep it backwards compatible while it is in beta, but it may still be renamed or removed. | 30 days | +| **Experimental** | Use at your own risk. Published so you can see what is coming, but it may change or disappear at any time, without notice. | None | + +Each endpoint's maturity is shown as a badge in the API reference and as a badge extension in the OpenAPI spec. + +**Not covered:** undocumented endpoints. If an endpoint does not appear in the published API reference, it is not part of the supported API, no matter what your browser's network tab shows. That includes internal endpoints used by the Semgrep web app and by the Semgrep CLI. Those change without notice, and building against them is not supported. + +### The one exception: urgent security and legal changes + +If continuing to serve an endpoint or a field would expose customer data, create a security risk, or violate a legal obligation, we may change it with less notice than the table above — occasionally with none. This is the only exception, we do not use it for convenience, and when we invoke it we will tell you what changed and why as soon as we are able to. + +## Finding out what is deprecated today + +- The API changelog records every deprecation on the day it ships, tagged + **Deprecated**. Each version's changelog is an RSS feed — subscribe to the + [v1](/api-reference/v1/Changelog) or [v2](/api-reference/v2/Changelog) feed + and you will hear about a deprecation without having to come back and check. +- The API reference lists deprecated endpoints with their sunset dates. +- Any deprecated endpoint you call returns `Deprecation` and `Sunset` headers, so grepping your own logs for those headers tells you exactly what your integration depends on. +- If you are unsure whether something you rely on is covered by this policy, contact [support@semgrep.com](mailto:support@semgrep.com) and we will confirm in writing. From 590767c205330860048320881571dde217f48c20 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 21:40:48 -0700 Subject: [PATCH 14/27] docs(api-reference): drop the 3-month removal window Stable endpoints had two clocks: 6 months for a breaking change, 3 months for removing an already-deprecated optional field. The short one does not survive contact with how the clock actually starts. Per styleguide 13.2 the clock only runs once the notice is public -- `Deprecation` and `Sunset` headers live, spec marked. A field marked deprecated with no sunset date has therefore started no clock at all, so the 3 months would be the entire notice a customer gets, not a follow-on to time already served. That is half the window for something oasdiff rates `response-optional-property-removed` at WARN, potentially breaking: "optional" says the server may omit the field, not that nobody reads it. One window for stable, whatever the change. Simpler to state, simpler to honour, and it errs long on a public commitment. Styleguide 13.1 transcribes this table and still carries the 3-month row; that needs the same edit in semgrep-app. Its link to api-deprecation-policy.md is also dangling -- that file does not exist in that repo. Co-Authored-By: Claude Opus 5 (1M context) --- docs/snippets/api-deprecation-policy.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/snippets/api-deprecation-policy.mdx b/docs/snippets/api-deprecation-policy.mdx index 338a913c1b..ff43e2bfa7 100644 --- a/docs/snippets/api-deprecation-policy.mdx +++ b/docs/snippets/api-deprecation-policy.mdx @@ -2,7 +2,7 @@ APIs change. This policy tells you how much warning you get before a change breaks your integration. -In short: **stable endpoints get 6 months' notice before a breaking change, and 3 months before an already-deprecated optional field is removed.** After the deadline passes, the endpoint returns `410 Gone` with a machine-readable pointer to its replacement — it does not start returning wrong or partial data. +In short: **stable endpoints get 6 months' notice before a breaking change or a removal.** After the deadline passes, the endpoint returns `410 Gone` with a machine-readable pointer to its replacement — it does not start returning wrong or partial data. ## What this policy covers @@ -10,7 +10,7 @@ This policy applies to every endpoint documented on [docs.semgrep.dev](https://d | Maturity | What we promise | Notice before removal or a breaking change | | --- | --- | --- | -| **Stable** | Fully covered by this policy. | 6 months (breaking), 3 months (non-breaking removal) | +| **Stable** | Fully covered by this policy. | 6 months | | **Beta** | Documented and supported. We keep it backwards compatible while it is in beta, but it may still be renamed or removed. | 30 days | | **Experimental** | Use at your own risk. Published so you can see what is coming, but it may change or disappear at any time, without notice. | None | From 493b4a3f7da2c7428445bdf64660eeae23c631c0 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Fri, 4 Sep 2026 15:26:18 -0700 Subject: [PATCH 15/27] docs(api-reference): apply tech writing review to the deprecation policy Takes all twelve suggestions from the review on #2832: the intro and summary sentences, the maturity table's header and all three rows, the undocumented-endpoints paragraph, the security and legal exception (now two paragraphs), the "Identify deprecated endpoints" heading, and the four-bullet list in its label-prefixed form. Two of the twelve needed a judgement call rather than a substitution: - One was a question -- "Do we add callouts in the API docs? If so, this info would benefit from being in a callout box." We do; appears 152 times in this repo. The summary paragraph is now a , using the suggested wording. - The frontmatter description suggestion was left on the v1 page, but v1 and v2 are deliberate duplicates importing the same snippet, so applying it to one only would have made them diverge. Both are updated. The undocumented-endpoints suggestion carried a double space after its first period, which is dropped as a typo rather than reproduced. Style and voice only. Nothing here changes what the policy commits to, including the six-month window. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v1/Deprecation-Policy.mdx | 2 +- docs/api-reference/v2/Deprecation-Policy.mdx | 2 +- docs/snippets/api-deprecation-policy.mdx | 33 ++++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/api-reference/v1/Deprecation-Policy.mdx b/docs/api-reference/v1/Deprecation-Policy.mdx index d309753338..965b076020 100644 --- a/docs/api-reference/v1/Deprecation-Policy.mdx +++ b/docs/api-reference/v1/Deprecation-Policy.mdx @@ -1,6 +1,6 @@ --- title: "Deprecation Policy" -description: "How much notice you get before a Semgrep API endpoint changes, how you will hear about it, and what happens at sunset." +description: "Learn how Semgrep communicates API deprecations, including notice periods and endpoint removal timelines." --- {/* Shared with the other API version -- edit /snippets/api-deprecation-policy.mdx */} diff --git a/docs/api-reference/v2/Deprecation-Policy.mdx b/docs/api-reference/v2/Deprecation-Policy.mdx index d309753338..965b076020 100644 --- a/docs/api-reference/v2/Deprecation-Policy.mdx +++ b/docs/api-reference/v2/Deprecation-Policy.mdx @@ -1,6 +1,6 @@ --- title: "Deprecation Policy" -description: "How much notice you get before a Semgrep API endpoint changes, how you will hear about it, and what happens at sunset." +description: "Learn how Semgrep communicates API deprecations, including notice periods and endpoint removal timelines." --- {/* Shared with the other API version -- edit /snippets/api-deprecation-policy.mdx */} diff --git a/docs/snippets/api-deprecation-policy.mdx b/docs/snippets/api-deprecation-policy.mdx index ff43e2bfa7..75501ac301 100644 --- a/docs/snippets/api-deprecation-policy.mdx +++ b/docs/snippets/api-deprecation-policy.mdx @@ -1,33 +1,34 @@ ## What this policy promises -APIs change. This policy tells you how much warning you get before a change breaks your integration. +APIs change over time. This policy explains how much notice you'll receive before a breaking change affects your integration. -In short: **stable endpoints get 6 months' notice before a breaking change or a removal.** After the deadline passes, the endpoint returns `410 Gone` with a machine-readable pointer to its replacement — it does not start returning wrong or partial data. + + In summary, **stable endpoints receive at least 6 months' notice before a breaking change or a removal.** After the deprecation period ends, the endpoint returns `410 Gone` with a machine-readable pointer to its replacement. It never returns incorrect or partial data. + ## What this policy covers This policy applies to every endpoint documented on [docs.semgrep.dev](https://docs.semgrep.dev/), according to its maturity level. -| Maturity | What we promise | Notice before removal or a breaking change | +| Maturity | What to expect | Notice before a breaking change or removal | | --- | --- | --- | -| **Stable** | Fully covered by this policy. | 6 months | -| **Beta** | Documented and supported. We keep it backwards compatible while it is in beta, but it may still be renamed or removed. | 30 days | -| **Experimental** | Use at your own risk. Published so you can see what is coming, but it may change or disappear at any time, without notice. | None | +| **Stable** | Covered by the Semgrep API deprecation policy. | 6 months | +| **Beta** | Supported and documented. Semgrep maintains backward compatibility during the beta period, but the endpoint may still be renamed or removed. | 30 days | +| **Experimental** | Published to help you preview functionality. Experimental endpoints may change, be renamed, or be removed at any time without notice. | None | Each endpoint's maturity is shown as a badge in the API reference and as a badge extension in the OpenAPI spec. -**Not covered:** undocumented endpoints. If an endpoint does not appear in the published API reference, it is not part of the supported API, no matter what your browser's network tab shows. That includes internal endpoints used by the Semgrep web app and by the Semgrep CLI. Those change without notice, and building against them is not supported. +**Undocumented endpoints are not covered**. If an endpoint does not appear in the published API reference, it is not part of the supported API, regardless of what you see in your browser's network tab. This includes internal endpoints used by Semgrep AppSec Platform and the Semgrep CLI. These endpoints may change or be removed without notice and are not supported for customer integrations. ### The one exception: urgent security and legal changes -If continuing to serve an endpoint or a field would expose customer data, create a security risk, or violate a legal obligation, we may change it with less notice than the table above — occasionally with none. This is the only exception, we do not use it for convenience, and when we invoke it we will tell you what changed and why as soon as we are able to. +If continuing to support an endpoint or field would expose customer data, create a security risk, or violate a legal obligation, Semgrep may make changes with less notice than described above, or in rare cases, without notice. -## Finding out what is deprecated today +This is the only exception to the deprecation policy. Semgrep does not invoke it for convenience. When invoked, Semgrep will communicate what changed and why as soon as possible. -- The API changelog records every deprecation on the day it ships, tagged - **Deprecated**. Each version's changelog is an RSS feed — subscribe to the - [v1](/api-reference/v1/Changelog) or [v2](/api-reference/v2/Changelog) feed - and you will hear about a deprecation without having to come back and check. -- The API reference lists deprecated endpoints with their sunset dates. -- Any deprecated endpoint you call returns `Deprecation` and `Sunset` headers, so grepping your own logs for those headers tells you exactly what your integration depends on. -- If you are unsure whether something you rely on is covered by this policy, contact [support@semgrep.com](mailto:support@semgrep.com) and we will confirm in writing. +## Identify deprecated endpoints + +- Subscribe to the API changelog: Every deprecation is announced in the changelog on the day it ships and is tagged as Deprecated. Each API version has its own RSS feed. Subscribe to the [v1](/api-reference/v1/Changelog) or [v2](/api-reference/v2/Changelog) feed to receive deprecation notices automatically. +- Check the API reference: The API reference marks deprecated endpoints and includes their sunset dates. +- Monitor deprecation headers: Deprecated endpoints include `Deprecation` and `Sunset` response headers. Monitoring these headers in your application logs helps identify any deprecated endpoints that your integration still depends on. +- Contact Semgrep if you're unsure: If you're unsure whether an endpoint or feature is covered by this policy, contact [support@semgrep.com](mailto:support@semgrep.com) From 8b06c7f75ac6f47125da9fc1ba6cd8e883545b31 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Wed, 9 Sep 2026 10:26:49 -0700 Subject: [PATCH 16/27] docs(api-reference): give beta endpoints 60 days' notice Doubles the beta notice period from 30 days to 60. 30 days is a short window for a customer to notice a deprecation, schedule the work, and ship it -- particularly for teams on a monthly release train, where it can amount to a single opportunity to react. Stable stays at 6 months and experimental still promises nothing. Co-Authored-By: Claude Opus 5 (1M context) --- docs/snippets/api-deprecation-policy.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/snippets/api-deprecation-policy.mdx b/docs/snippets/api-deprecation-policy.mdx index 75501ac301..2a21c3dfdd 100644 --- a/docs/snippets/api-deprecation-policy.mdx +++ b/docs/snippets/api-deprecation-policy.mdx @@ -13,7 +13,7 @@ This policy applies to every endpoint documented on [docs.semgrep.dev](https://d | Maturity | What to expect | Notice before a breaking change or removal | | --- | --- | --- | | **Stable** | Covered by the Semgrep API deprecation policy. | 6 months | -| **Beta** | Supported and documented. Semgrep maintains backward compatibility during the beta period, but the endpoint may still be renamed or removed. | 30 days | +| **Beta** | Supported and documented. Semgrep maintains backward compatibility during the beta period, but the endpoint may still be renamed or removed. | 60 days | | **Experimental** | Published to help you preview functionality. Experimental endpoints may change, be renamed, or be removed at any time without notice. | None | Each endpoint's maturity is shown as a badge in the API reference and as a badge extension in the OpenAPI spec. From e252d9a9e8274fb93398acd4fa8c101a796a1d7a Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 10 Sep 2026 13:51:28 -0700 Subject: [PATCH 17/27] docs(api-reference): address review on the deprecation policy Connor's review, plus two accuracy fixes the 410 write-up turned up. Reviewer-requested: - Move the maturity-badge sentence above the table, so the question the table raises ("how do I know which one an endpoint is?") is answered before it is asked rather than after. - State stable's window as 180 days. Beta was already in days, so the column no longer mixes units, and a month-length assumption cannot change what the commitment means. - Rewrite the stable row, which said only "covered by this policy". Beta is covered too, so the cell distinguished nothing. - Soften the exception. It claimed to be "the only exception" and named a legal obligation specifically; both are more absolute than we can actually promise. Same care, less cornering. - Restore what counts as a breaking change, from the APPEX-956 draft. Split into what will not change without notice and what may change at any time, because the second half is what a caller has to build for -- tolerate new fields, do not match on error strings. APPEX-956 wanted that definition to be a link to the oasdiff ruleset in APPEX-959, so it would be mechanical rather than prose. APPEX-959 is cancelled, so prose is what is left. Accuracy, found while writing the sunset section against the implementation in semgrep-app#31680: - The `Link` header is the machine-readable pointer, and it is omitted when a removal has no replacement. "Returns 410 with a machine-readable pointer to its replacement" promised it unconditionally. - Document the sunset response itself: headers, body, and which parts are stable enough to parse. `error` wording is not. semgrep-app#31680 is still a draft, so this must not publish before it ships -- the page would describe a response nothing returns yet. Co-Authored-By: Claude Opus 5 (1M context) --- docs/snippets/api-deprecation-policy.mdx | 60 ++++++++++++++++++++---- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/docs/snippets/api-deprecation-policy.mdx b/docs/snippets/api-deprecation-policy.mdx index 2a21c3dfdd..4061f00b5b 100644 --- a/docs/snippets/api-deprecation-policy.mdx +++ b/docs/snippets/api-deprecation-policy.mdx @@ -3,32 +3,76 @@ APIs change over time. This policy explains how much notice you'll receive before a breaking change affects your integration. - In summary, **stable endpoints receive at least 6 months' notice before a breaking change or a removal.** After the deprecation period ends, the endpoint returns `410 Gone` with a machine-readable pointer to its replacement. It never returns incorrect or partial data. + In summary, **stable endpoints receive at least 180 days' notice before a breaking change or a removal.** After the deprecation period ends, the endpoint returns `410 Gone` and points you at its replacement. It never returns incorrect or partial data. ## What this policy covers This policy applies to every endpoint documented on [docs.semgrep.dev](https://docs.semgrep.dev/), according to its maturity level. +Each endpoint's maturity is shown as a badge in the API reference and as a badge extension in the OpenAPI spec. | Maturity | What to expect | Notice before a breaking change or removal | | --- | --- | --- | -| **Stable** | Covered by the Semgrep API deprecation policy. | 6 months | +| **Stable** | Supported and documented. Expected to evolve and improve without breaking changes. | 180 days | | **Beta** | Supported and documented. Semgrep maintains backward compatibility during the beta period, but the endpoint may still be renamed or removed. | 60 days | | **Experimental** | Published to help you preview functionality. Experimental endpoints may change, be renamed, or be removed at any time without notice. | None | -Each endpoint's maturity is shown as a badge in the API reference and as a badge extension in the OpenAPI spec. - **Undocumented endpoints are not covered**. If an endpoint does not appear in the published API reference, it is not part of the supported API, regardless of what you see in your browser's network tab. This includes internal endpoints used by Semgrep AppSec Platform and the Semgrep CLI. These endpoints may change or be removed without notice and are not supported for customer integrations. -### The one exception: urgent security and legal changes +### What counts as a breaking change + +On a stable endpoint, Semgrep does not do any of the following without the full notice period: + +- Remove or rename the endpoint, or change its URL path or HTTP method. +- Remove or rename a field in a response, whether or not that field is optional. +- Change the type of an existing field, or narrow the set of values it accepts. +- Add a new required request parameter, or make an existing optional one required. +- Change the meaning of an existing field without changing its name. + +Semgrep may make the following changes at any time, so build your integration to tolerate them: + +- Add a new endpoint, or a new optional request parameter. +- Add a new field to a response. Parse responses so that unrecognized fields are ignored rather than treated as an error. +- Add a new value to an existing enum, unless that enum is documented as closed. +- Change the order of items in a response where no order is documented. +- Change human-readable text, such as an `error` message, that is not documented as stable. + +### Exception: unavoidable urgent changes -If continuing to support an endpoint or field would expose customer data, create a security risk, or violate a legal obligation, Semgrep may make changes with less notice than described above, or in rare cases, without notice. +If continuing to support an endpoint, field, or other aspect of API behavior would expose customer data, create a security risk, or violate an obligation, Semgrep may make changes with less notice than described above, or in rare cases without notice. -This is the only exception to the deprecation policy. Semgrep does not invoke it for convenience. When invoked, Semgrep will communicate what changed and why as soon as possible. +Semgrep will not invoke this exception lightly. When invoked, Semgrep will communicate what changed and why as soon as possible. ## Identify deprecated endpoints - Subscribe to the API changelog: Every deprecation is announced in the changelog on the day it ships and is tagged as Deprecated. Each API version has its own RSS feed. Subscribe to the [v1](/api-reference/v1/Changelog) or [v2](/api-reference/v2/Changelog) feed to receive deprecation notices automatically. - Check the API reference: The API reference marks deprecated endpoints and includes their sunset dates. - Monitor deprecation headers: Deprecated endpoints include `Deprecation` and `Sunset` response headers. Monitoring these headers in your application logs helps identify any deprecated endpoints that your integration still depends on. -- Contact Semgrep if you're unsure: If you're unsure whether an endpoint or feature is covered by this policy, contact [support@semgrep.com](mailto:support@semgrep.com) +- Contact Semgrep if you're unsure: If you're unsure whether an endpoint or feature is covered by this policy, contact [support@semgrep.com](mailto:support@semgrep.com) and Semgrep will confirm in writing. + +## What happens at sunset + +Once an endpoint's sunset date arrives, it stops serving data and returns `410 Gone`. Semgrep does not silently leave a sunset endpoint running, and it never responds with partial or stale data in place of the data you asked for. + +A sunset response looks like this: + +```http +HTTP/1.1 410 Gone +Content-Type: application/json +Deprecation: Tue, 01 Sep 2026 00:00:00 GMT +Sunset: Mon, 01 Mar 2027 00:00:00 GMT +Link: ; rel="successor-version" + +{ + "error": "This endpoint was removed on 2027-03-01. Use GET /api/v2/issues instead." +} +``` + +| Field | Meaning | +| --- | --- | +| `Deprecation` | The date the deprecation became public, as an HTTP date. This is when the notice period started. | +| `Sunset` | The date the endpoint stopped serving data, as an HTTP date, per [RFC 8594](https://www.rfc-editor.org/rfc/rfc8594). | +| `Link` | The replacement endpoint, with the relation `successor-version`. Parse this rather than the `error` string when you need to route to the replacement automatically. | +| `error` | A human-readable explanation. Its wording is not stable, so don't match on it. | + +When an endpoint is removed with no replacement, the `Link` header is omitted and the `error` string says that no replacement exists. The `Deprecation` and `Sunset` headers are also sent on successful responses throughout the notice period, not only at sunset. From 37ee39b2aba27ac3442080c841a9f2ca740bb473 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 10 Sep 2026 14:28:13 -0700 Subject: [PATCH 18/27] docs(api-reference): describe the changelog as it now behaves Two claims on this page were ahead of the generator, and both are now true rather than aspirational. "Tagged as Deprecated" described a chip in the Change column as though it were a filter. The filter tag was "Non-breaking", so a reader who followed this page's advice -- subscribe, watch for what will break you -- was told to filter for exactly the thing that hid the notice. The generator now files deprecations as potentially breaking, so say both: what the row is marked, and which filter it survives. Also say that the removal itself lands in the changelog. It did not previously; a removal that served its full notice window produced no entry at all, which made "the changelog records every deprecation" true and "the changelog tells you when the endpoint went away" false. Co-Authored-By: Claude Opus 5 (1M context) --- docs/snippets/api-deprecation-policy.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/snippets/api-deprecation-policy.mdx b/docs/snippets/api-deprecation-policy.mdx index 4061f00b5b..576828888f 100644 --- a/docs/snippets/api-deprecation-policy.mdx +++ b/docs/snippets/api-deprecation-policy.mdx @@ -45,14 +45,14 @@ Semgrep will not invoke this exception lightly. When invoked, Semgrep will commu ## Identify deprecated endpoints -- Subscribe to the API changelog: Every deprecation is announced in the changelog on the day it ships and is tagged as Deprecated. Each API version has its own RSS feed. Subscribe to the [v1](/api-reference/v1/Changelog) or [v2](/api-reference/v2/Changelog) feed to receive deprecation notices automatically. +- Subscribe to the API changelog: Every deprecation is announced in the changelog on the day it ships, marked **Deprecated**, and filed under **Potentially breaking** so that it still appears when you filter the changelog for changes that can affect your integration. Each API version has its own RSS feed. Subscribe to the [v1](/api-reference/v1/Changelog) or [v2](/api-reference/v2/Changelog) feed to receive deprecation notices automatically. - Check the API reference: The API reference marks deprecated endpoints and includes their sunset dates. - Monitor deprecation headers: Deprecated endpoints include `Deprecation` and `Sunset` response headers. Monitoring these headers in your application logs helps identify any deprecated endpoints that your integration still depends on. - Contact Semgrep if you're unsure: If you're unsure whether an endpoint or feature is covered by this policy, contact [support@semgrep.com](mailto:support@semgrep.com) and Semgrep will confirm in writing. ## What happens at sunset -Once an endpoint's sunset date arrives, it stops serving data and returns `410 Gone`. Semgrep does not silently leave a sunset endpoint running, and it never responds with partial or stale data in place of the data you asked for. +Once an endpoint's sunset date arrives, it stops serving data and returns `410 Gone`. The removal is recorded in the changelog on the day it happens, so the end of an endpoint's life is announced the same way its deprecation was. Semgrep does not silently leave a sunset endpoint running, and it never responds with partial or stale data in place of the data you asked for. A sunset response looks like this: From 1f33cb441e167abc33218118817cfe84bbea0222 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 21:32:34 -0700 Subject: [PATCH 19/27] docs(api-reference): add v2 service landing pages Twelve landing pages under the v2 dropdown, one per service in the Stable API proposal, in a new "Services" nav group above the generated Endpoints group. Each describes what the service covers and where its endpoints will appear. They stay in v2 rather than a new unversioned section: customers already know the v1/v2 split, so v1 drains and disappears instead of a third concept arriving. Every page is empty of endpoints today, by design. Endpoints arrive one at a time as each is defined in its new service, migrated, and promoted, so the pages fill up as the migration lands. The placeholder block is what a generated endpoint list will replace once there is real data to generate from -- building it now would duplicate the slug rules in generate_api_changelog.py before we know the shape of what it renders. Also drops the blanket-experimental framing, which the new services contradict: - the dropdown was labelled "v2 (Experimental)", which claims the whole API is experimental while the page inside it explains per-endpoint maturity badges and the deprecation policy promises per-endpoint notice - the Introduction opened with "The v2 API is under active development", same problem; the rest of that sentence is kept, since not deprecating v1 until v2 covers it is still true Not addressed here: the Introduction's maturity list still says stable endpoints get "no breaking changes", while the deprecation policy says they get six months' notice before one. Two different public promises on adjacent pages, and reconciling them is an editorial call, not a scaffolding change. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v2/Introduction.mdx | 2 +- .../v2/services/agentic-workflows.mdx | 14 ++++++++++++++ docs/api-reference/v2/services/analytics.mdx | 14 ++++++++++++++ .../api-reference/v2/services/deployments.mdx | 14 ++++++++++++++ .../v2/services/integrations.mdx | 14 ++++++++++++++ docs/api-reference/v2/services/issues.mdx | 14 ++++++++++++++ docs/api-reference/v2/services/multimodal.mdx | 14 ++++++++++++++ docs/api-reference/v2/services/policies.mdx | 14 ++++++++++++++ docs/api-reference/v2/services/projects.mdx | 14 ++++++++++++++ docs/api-reference/v2/services/scans.mdx | 14 ++++++++++++++ .../v2/services/supply-chain.mdx | 14 ++++++++++++++ docs/api-reference/v2/services/tasks.mdx | 14 ++++++++++++++ .../v2/services/workflow-issues.mdx | 14 ++++++++++++++ docs/docs.json | 19 ++++++++++++++++++- 14 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 docs/api-reference/v2/services/agentic-workflows.mdx create mode 100644 docs/api-reference/v2/services/analytics.mdx create mode 100644 docs/api-reference/v2/services/deployments.mdx create mode 100644 docs/api-reference/v2/services/integrations.mdx create mode 100644 docs/api-reference/v2/services/issues.mdx create mode 100644 docs/api-reference/v2/services/multimodal.mdx create mode 100644 docs/api-reference/v2/services/policies.mdx create mode 100644 docs/api-reference/v2/services/projects.mdx create mode 100644 docs/api-reference/v2/services/scans.mdx create mode 100644 docs/api-reference/v2/services/supply-chain.mdx create mode 100644 docs/api-reference/v2/services/tasks.mdx create mode 100644 docs/api-reference/v2/services/workflow-issues.mdx diff --git a/docs/api-reference/v2/Introduction.mdx b/docs/api-reference/v2/Introduction.mdx index ed03f334bf..fee267889d 100644 --- a/docs/api-reference/v2/Introduction.mdx +++ b/docs/api-reference/v2/Introduction.mdx @@ -3,7 +3,7 @@ title: "Semgrep API v2" description: "Welcome to the portal for Semgrep AppSec Platform's web API v2." --- -The v2 API is under active development as Semgrep continues to expand platform capabilities. Semgrep will continue to support existing integrations and will not deprecate v1 until all v1 functionality is available in v2. +Semgrep will continue to support existing integrations and will not deprecate v1 until all v1 functionality is available in v2. ## API maturity levels diff --git a/docs/api-reference/v2/services/agentic-workflows.mdx b/docs/api-reference/v2/services/agentic-workflows.mdx new file mode 100644 index 0000000000..bff05db1f3 --- /dev/null +++ b/docs/api-reference/v2/services/agentic-workflows.mdx @@ -0,0 +1,14 @@ +--- +title: "Agentic Workflows" +description: "Workflow job execution and schedules." +--- + +Agentic Workflows run analysis jobs on a schedule or on demand. These endpoints cover the execution and scheduling of those jobs — the issues they produce live in [Workflow issues](/api-reference/v2/services/workflow-issues). + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/analytics.mdx b/docs/api-reference/v2/services/analytics.mdx new file mode 100644 index 0000000000..4f9a8f6d4b --- /dev/null +++ b/docs/api-reference/v2/services/analytics.mdx @@ -0,0 +1,14 @@ +--- +title: "Analytics" +description: "Aggregate program reporting." +--- + +Analytics answers questions about your program as a whole rather than about one project or issue — coverage, trends over time, and how remediation is progressing. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/deployments.mdx b/docs/api-reference/v2/services/deployments.mdx new file mode 100644 index 0000000000..e28addf567 --- /dev/null +++ b/docs/api-reference/v2/services/deployments.mdx @@ -0,0 +1,14 @@ +--- +title: "Deployments" +description: "The deployment, its users, product configuration, RBAC teams, and token identity." +--- + +A deployment is your organization in Semgrep, and the root of almost every other resource: projects belong to a deployment, as do policies, scans, and integrations. These endpoints manage the deployment itself, the people in it, and what each of them may do. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/integrations.mdx b/docs/api-reference/v2/services/integrations.mdx new file mode 100644 index 0000000000..e7ddef3b26 --- /dev/null +++ b/docs/api-reference/v2/services/integrations.mdx @@ -0,0 +1,14 @@ +--- +title: "Integrations" +description: "Source code manager connections, package manager credentials, and ticketing." +--- + +Integrations are the connections between Semgrep and the systems around it. These endpoints manage source code manager connections and apps, the credentials Semgrep uses to reach private package registries, and the ticketing systems it files issues into. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/issues.mdx b/docs/api-reference/v2/services/issues.mdx new file mode 100644 index 0000000000..2bb67a8715 --- /dev/null +++ b/docs/api-reference/v2/services/issues.mdx @@ -0,0 +1,14 @@ +--- +title: "Issues" +description: "Issues, counts, triage, exports, and AI fix jobs." +--- + +An issue is the persistent, triageable record of something Semgrep found — as distinct from a finding, which is one immutable detection from one scan. These endpoints list and filter issues, move them through triage, export them, and manage the AI fix jobs raised against them, across SAST, AI-powered detection, Supply Chain, and Secrets. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/multimodal.mdx b/docs/api-reference/v2/services/multimodal.mdx new file mode 100644 index 0000000000..ac8e63a91e --- /dev/null +++ b/docs/api-reference/v2/services/multimodal.mdx @@ -0,0 +1,14 @@ +--- +title: "Multimodal" +description: "Assistant and memories." +--- + +These endpoints cover Semgrep Assistant and the memories that shape its behavior for your deployment. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/policies.mdx b/docs/api-reference/v2/services/policies.mdx new file mode 100644 index 0000000000..75d2249d5b --- /dev/null +++ b/docs/api-reference/v2/services/policies.mdx @@ -0,0 +1,14 @@ +--- +title: "Policies" +description: "Detection and remediation policies, rules, dry runs, automations, and notification rules." +--- + +A policy decides which rules run and what happens when they match — whether a finding blocks a pull request, gets ignored, or raises a ticket. These endpoints manage detection and remediation policies and the rules inside them, let you dry-run a change before committing to it, and configure the automations and notifications that fire off the result. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/projects.mdx b/docs/api-reference/v2/services/projects.mdx new file mode 100644 index 0000000000..9229835080 --- /dev/null +++ b/docs/api-reference/v2/services/projects.mdx @@ -0,0 +1,14 @@ +--- +title: "Projects" +description: "Projects, branches, project tags, syncs, and per-project scan settings." +--- + +A project is what a scan sees: one codebase Semgrep is configured to analyze. These endpoints manage projects and their branches, the tags used to organize them, the syncs that keep them aligned with your source code manager, and the per-project scan and resolution settings. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/scans.mdx b/docs/api-reference/v2/services/scans.mdx new file mode 100644 index 0000000000..3991938680 --- /dev/null +++ b/docs/api-reference/v2/services/scans.mdx @@ -0,0 +1,14 @@ +--- +title: "Scans" +description: "Scan runs and retries." +--- + +A scan is one execution of Semgrep against a project. These endpoints report on scan runs and retry the ones that failed. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/supply-chain.mdx b/docs/api-reference/v2/services/supply-chain.mdx new file mode 100644 index 0000000000..17f7df65f0 --- /dev/null +++ b/docs/api-reference/v2/services/supply-chain.mdx @@ -0,0 +1,14 @@ +--- +title: "Supply Chain" +description: "Dependencies, advisories, and SBOMs." +--- + +Supply Chain covers what your code depends on. These endpoints list the dependencies Semgrep found across your projects, the advisories that affect them, and generate SBOM exports. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/tasks.mdx b/docs/api-reference/v2/services/tasks.mdx new file mode 100644 index 0000000000..cdc6b8e40e --- /dev/null +++ b/docs/api-reference/v2/services/tasks.mdx @@ -0,0 +1,14 @@ +--- +title: "Tasks" +description: "Async result polling." +--- + +Some operations are too slow to answer inline — exports, SBOM generation, and syncs all return a task handle instead of a result. These endpoints redeem that handle once the work finishes. Tasks is deliberately its own service rather than living under any one of the services that produce them, so the same polling call works whatever started the work. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/workflow-issues.mdx b/docs/api-reference/v2/services/workflow-issues.mdx new file mode 100644 index 0000000000..3c0c72218b --- /dev/null +++ b/docs/api-reference/v2/services/workflow-issues.mdx @@ -0,0 +1,14 @@ +--- +title: "Workflow issues" +description: "Issues, counts, and triage for Agentic Workflows." +--- + +Workflow issues are the issues produced by Agentic Workflows, with their own listing, counting, and triage endpoints. This service is intended to eventually replace [Issues](/api-reference/v2/services/issues) for every issue type; for now it serves Agentic Workflows only. + +## Endpoints + +This service has no published endpoints yet. Endpoints move here one at a time, +and each one appears in the navigation as soon as it is published. Every move is +recorded in the [changelog](/api-reference/v2/Changelog), and anything it +supersedes is deprecated under the +[deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/docs.json b/docs/docs.json index 20bb976330..51612aa7b8 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -633,7 +633,7 @@ ] }, { - "dropdown": "v2 (Experimental)", + "dropdown": "v2", "icon": "flask", "groups": [ { @@ -646,6 +646,23 @@ "api-reference/v2/Deprecation-Policy" ] }, + { + "group": "Services", + "pages": [ + "api-reference/v2/services/deployments", + "api-reference/v2/services/issues", + "api-reference/v2/services/workflow-issues", + "api-reference/v2/services/agentic-workflows", + "api-reference/v2/services/projects", + "api-reference/v2/services/scans", + "api-reference/v2/services/policies", + "api-reference/v2/services/integrations", + "api-reference/v2/services/supply-chain", + "api-reference/v2/services/analytics", + "api-reference/v2/services/multimodal", + "api-reference/v2/services/tasks" + ] + }, { "group": "Endpoints", "openapi": { From db820f113fee95ad5112efb7882c56d95a70cdd5 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 21:36:15 -0700 Subject: [PATCH 20/27] docs(api-reference): stop promising stable endpoints never break The v2 Introduction told customers "No breaking changes will be made to this API" for stable endpoints. The deprecation policy on the next page over says stable endpoints get six months' notice *before a breaking change* -- which concedes they happen. Two different public promises, and the weaker one is the honest one. Point the bullet at the policy rather than restating it, so there is one customer-facing definition of what each maturity level owes you (which is what the policy was written to be). Co-Authored-By: Claude Opus 5 (1M context) --- docs/api-reference/v2/Introduction.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/v2/Introduction.mdx b/docs/api-reference/v2/Introduction.mdx index fee267889d..9c15fe3790 100644 --- a/docs/api-reference/v2/Introduction.mdx +++ b/docs/api-reference/v2/Introduction.mdx @@ -11,7 +11,7 @@ Each endpoint in the v2 API is marked with a maturity badge to help you understa - 🚧 **Experimental** - Use at your own risk. This endpoint is not designed for third-party use or is under active development. Expect significant breaking changes. - ⚠️ **Beta** - This endpoint is being refined. Semgrep will communicate breaking changes to beta partners as we tweak the implementation. -- ✅ **Stable** - No breaking changes will be made to this API. You can confidently build production integrations against these endpoints. +- ✅ **Stable** - Build production integrations against these endpoints with confidence. Breaking changes are rare, and when one is unavoidable you get six months' notice — see the [deprecation policy](/api-reference/v2/Deprecation-Policy) for the notice you are owed at each maturity level. Use Stable endpoints for production applications and treat **experimental/beta** endpoints as previews of upcoming functionality. From 852c9e263cad776fb6f623086a2fddeb08a343ce Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Thu, 3 Sep 2026 22:23:52 -0700 Subject: [PATCH 21/27] feat(api-reference): redirect endpoints whose page URL moves Mintlify builds an endpoint's page URL from its first tag: /api-reference/v2// So regrouping the public API into its new services moves every affected page, even though the path, request and response are untouched. Today's /api-reference/v2/miscservice/check-container-health becomes /api-reference/v2/deployments/... and the old URL 404s for anyone who bookmarked it, linked it, or reached it from search. Derive the redirects instead of hand-maintaining ~200 of them: diff the committed spec against the working tree, compute both URLs with the same slug rules the changelog already uses, and emit one redirect per difference. Runs in the spec-sync workflow, so a tag change and its redirect land in the same PR. docs.json joins add-paths, or the redirects would be generated and then left behind. Chains collapse rather than accumulate: an endpoint that moves A -> B and later B -> C leaves A -> C, not A -> B pointing at a page that no longer exists. docs.json is patched textually. A json round-trip is not byte-identical against the checked-in formatting, so it would bury a two-line change in a whole-file diff. Slug rules are imported from generate_api_changelog rather than copied -- they were reverse-engineered against the rendered site, and two independent copies would drift into broken links. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/update-openapi-specs.yml | 12 ++ scripts/generate_openapi_redirects.py | 167 ++++++++++++++++++ .../tests/test_generate_openapi_redirects.py | 110 ++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 scripts/generate_openapi_redirects.py create mode 100644 scripts/tests/test_generate_openapi_redirects.py diff --git a/.github/workflows/update-openapi-specs.yml b/.github/workflows/update-openapi-specs.yml index 2ca12efb1a..1623be84ad 100644 --- a/.github/workflows/update-openapi-specs.yml +++ b/.github/workflows/update-openapi-specs.yml @@ -105,6 +105,17 @@ jobs: --link-base /api-reference/v2 \ --rss-url https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml + # Mintlify builds an endpoint's page URL from its first tag, so an + # endpoint regrouped into a new service moves and its old URL 404s. + # Must run before the changelog step commits, and while the previous + # spec is still what HEAD has -- this diffs the two. + - name: Redirect endpoints whose page URL moved + run: | + uv run scripts/generate_openapi_redirects.py docs/public_v1.openapi.yaml \ + docs/docs.json --link-base /api-reference/v1 + uv run scripts/generate_openapi_redirects.py docs/public_v2.openapi.yaml \ + docs/docs.json --link-base /api-reference/v2 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 22 @@ -123,6 +134,7 @@ jobs: add-paths: | docs/public_v1.openapi.yaml docs/public_v2.openapi.yaml + docs/docs.json docs/api-reference/v1/Changelog.mdx docs/api-reference/v2/Changelog.mdx branch: chore/update-openapi-specs diff --git a/scripts/generate_openapi_redirects.py b/scripts/generate_openapi_redirects.py new file mode 100644 index 0000000000..72b239ccc5 --- /dev/null +++ b/scripts/generate_openapi_redirects.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.9" +# dependencies = ["pyyaml"] +# +# [tool.uv] +# exclude-newer = "1 week" +# /// +# +# Keep docs.json redirects in step with endpoints that change OpenAPI tag. +# +# Why this exists: Mintlify builds an endpoint's page URL from its first tag, +# so `/api-reference/v2//`. Regrouping the public API +# into its new services changes tags without touching a path, a request or a +# response -- but every affected page moves, and the old URL 404s for anyone +# who bookmarked it, linked it, or found it in search. +# +# The move is derivable rather than something anyone should hand-maintain: +# diff the committed spec against the working tree, compute both URLs with the +# same slug rules the changelog uses, and emit a redirect for each difference. +# Runs in update-openapi-specs.yml alongside the changelog regeneration, so a +# tag change and its redirect land in the same PR. +# +# Chains are collapsed rather than accumulated. If an endpoint moves A -> B and +# later B -> C, the existing A -> B entry is repointed to C instead of leaving +# a redirect to a URL that no longer resolves. +# +# Requires the spec to be tracked in git; the first commit that adds a spec has +# nothing to diff against and is a no-op. + +from __future__ import annotations + +import argparse +import json +import pathlib +import subprocess +import sys +from typing import Optional + +import yaml + +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) +from generate_api_changelog import endpoint_urls # noqa: E402 + + +def committed_spec(spec_rel: str, cwd: pathlib.Path) -> Optional[dict]: + """The spec as of HEAD, or None if it is not tracked there yet.""" + out = subprocess.run( + ["git", "show", f"HEAD:{spec_rel}"], capture_output=True, text=True, cwd=cwd + ) + if out.returncode != 0: + return None + return yaml.safe_load(out.stdout) + + +def moved_endpoints(before: dict, after: dict, link_base: str) -> list[tuple[str, str]]: + """(old_url, new_url) for every operation whose page URL changed. + + Keyed by (method, path), which is the endpoint's identity: a tag change + moves the page but not the endpoint, so the key is stable across the move. + Endpoints added or removed outright are not redirects and are skipped. + """ + old_urls = endpoint_urls(before, link_base) + new_urls = endpoint_urls(after, link_base) + moves = [] + for key, old in sorted(old_urls.items()): + new = new_urls.get(key) + if new is not None and new != old: + moves.append((old, new)) + return moves + + +def apply_moves(docs_json: str, moves: list[tuple[str, str]]) -> tuple[str, int, int]: + """Patch the redirects array textually. Returns (text, added, repointed). + + Textual rather than a json round-trip: re-serialising docs.json reflows the + whole file and buries a two-line change in a whole-file diff. + """ + if not moves: + return docs_json, 0, 0 + + existing = {r["source"]: r["destination"] for r in json.loads(docs_json).get("redirects", [])} + added = repointed = 0 + new_entries = [] + + for old, new in moves: + # An earlier move already points somewhere; send it to the new home. + for source, destination in list(existing.items()): + if destination == old: + docs_json = docs_json.replace( + f'"source": "{source}",\n "destination": "{destination}"', + f'"source": "{source}",\n "destination": "{new}"', + ) + existing[source] = new + repointed += 1 + if old in existing: + continue # already redirected; the loop above kept it current + new_entries.append((old, new)) + existing[old] = new + added += 1 + + if new_entries: + block = ",\n".join( + f' {{\n "source": "{old}",\n "destination": "{new}"\n }}' + for old, new in new_entries + ) + marker = ' "redirects": [\n' + assert marker in docs_json, "redirects array not found in docs.json" + cut = docs_json.index(marker) + len(marker) + rest = docs_json[cut:] + # An empty array closes immediately, so the block must not end in a + # comma; otherwise it is separated from the entries that follow. + separator = "\n" if rest.lstrip().startswith("]") else ",\n" + docs_json = docs_json[:cut] + block + separator + rest + + return docs_json, added, repointed + + +def main(argv: Optional[list[str]] = None) -> int: + parser = argparse.ArgumentParser( + description="Add docs.json redirects for endpoints whose OpenAPI tag changed." + ) + parser.add_argument("spec", help="path to the OpenAPI spec (tracked in git)") + parser.add_argument("docs_json", help="path to docs.json") + parser.add_argument( + "--link-base", required=True, help="URL prefix of the generated endpoint pages" + ) + parser.add_argument( + "--dry-run", action="store_true", help="report the moves without writing" + ) + args = parser.parse_args(argv) + + root = pathlib.Path( + subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, text=True, check=True, + ).stdout.strip() + ) + spec_rel = pathlib.Path(args.spec).resolve().relative_to(root).as_posix() + + before = committed_spec(spec_rel, root) + if before is None: + print(f"{spec_rel}: not tracked at HEAD, nothing to diff") + return 0 + after = yaml.safe_load(pathlib.Path(args.spec).read_text()) + + moves = moved_endpoints(before, after, args.link_base) + if not moves: + print(f"{spec_rel}: no endpoint changed page URL") + return 0 + + for old, new in moves: + print(f" {old} -> {new}") + if args.dry_run: + print(f"{spec_rel}: {len(moves)} moved (dry run, nothing written)") + return 0 + + path = pathlib.Path(args.docs_json) + patched, added, repointed = apply_moves(path.read_text(), moves) + json.loads(patched) # fail loudly rather than write invalid JSON + path.write_text(patched) + print(f"{path}: {added} redirect(s) added, {repointed} repointed") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/tests/test_generate_openapi_redirects.py b/scripts/tests/test_generate_openapi_redirects.py new file mode 100644 index 0000000000..e3c20462f7 --- /dev/null +++ b/scripts/tests/test_generate_openapi_redirects.py @@ -0,0 +1,110 @@ +# Tests for scripts/generate_openapi_redirects.py +# +# Run with: +# uv run --with pyyaml --with pytest pytest scripts/tests + +from __future__ import annotations + +import json + +import pytest + +import generate_openapi_redirects as gor + +LINK_BASE = "/api-reference/v2" + + +def spec(tag: str, summary: str = "Get identity", path: str = "/api/agent/identity") -> dict: + return {"paths": {path: {"get": {"tags": [tag], "summary": summary}}}} + + +def docs_json(redirects: list[dict]) -> str: + """docs.json in the repo's own formatting, which the patcher edits textually.""" + body = ",\n".join( + f' {{\n "source": "{r["source"]}",\n' + f' "destination": "{r["destination"]}"\n }}' + for r in redirects + ) + body = body + "\n" if body else "" + return '{\n "name": "Semgrep",\n "redirects": [\n' + body + " ]\n}\n" + + +def test_moved_endpoints_reports_a_tag_change(): + moves = gor.moved_endpoints(spec("MiscService"), spec("Deployments"), LINK_BASE) + + assert moves == [ + ("/api-reference/v2/miscservice/get-identity", + "/api-reference/v2/deployments/get-identity") + ] + + +def test_moved_endpoints_ignores_an_unchanged_tag(): + assert gor.moved_endpoints(spec("MiscService"), spec("MiscService"), LINK_BASE) == [] + + +@pytest.mark.parametrize( + "before,after", + [ + # added: no old URL to redirect from + ({"paths": {}}, spec("Deployments")), + # removed: nowhere to redirect to, and the page is gone + (spec("MiscService"), {"paths": {}}), + ], +) +def test_moved_endpoints_skips_additions_and_removals(before, after): + assert gor.moved_endpoints(before, after, LINK_BASE) == [] + + +def test_moved_endpoints_follows_a_renamed_summary(): + """The page slug comes from the summary too, so a retitle moves the page.""" + moves = gor.moved_endpoints( + spec("Deployments", summary="Get identity"), + spec("Deployments", summary="Get token identity"), + LINK_BASE, + ) + + assert moves == [ + ("/api-reference/v2/deployments/get-identity", + "/api-reference/v2/deployments/get-token-identity") + ] + + +def test_apply_moves_appends_a_redirect_and_keeps_json_valid(): + text, added, repointed = gor.apply_moves(docs_json([]), [("/old", "/new")]) + + assert (added, repointed) == (1, 0) + assert json.loads(text)["redirects"] == [{"source": "/old", "destination": "/new"}] + + +def test_apply_moves_preserves_existing_redirects(): + start = docs_json([{"source": "/release-notes", "destination": "/release-notes/latest"}]) + + text, _, _ = gor.apply_moves(start, [("/old", "/new")]) + + assert {"source": "/release-notes", "destination": "/release-notes/latest"} in json.loads(text)["redirects"] + + +def test_apply_moves_collapses_a_chain_rather_than_dangling(): + """A -> B then B -> C must leave A -> C, not A -> B pointing at a 404.""" + start = docs_json([{"source": "/a", "destination": "/b"}]) + + text, added, repointed = gor.apply_moves(start, [("/b", "/c")]) + + entries = {r["source"]: r["destination"] for r in json.loads(text)["redirects"]} + assert entries["/a"] == "/c" + assert entries["/b"] == "/c" + assert (added, repointed) == (1, 1) + + +def test_apply_moves_is_idempotent(): + once, _, _ = gor.apply_moves(docs_json([]), [("/old", "/new")]) + twice, added, _ = gor.apply_moves(once, [("/old", "/new")]) + + assert added == 0 + assert json.loads(twice)["redirects"] == json.loads(once)["redirects"] + + +def test_apply_moves_noop_without_moves(): + start = docs_json([]) + + assert gor.apply_moves(start, []) == (start, 0, 0) From 83f117442ac39e1f3ed9f5c6eabd36d5233ce002 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Fri, 4 Sep 2026 08:35:00 -0700 Subject: [PATCH 22/27] feat(api-reference): give each service a landing page in the nav Services and endpoints were two separate things in the sidebar: a "Services" group of twelve descriptions, and an autogenerated "Endpoints" group of per-tag subgroups. Clicking a service in the second one opened whichever endpoint happened to sort first. Mintlify supports a `root` page on a nav group -- "clicking the group title in the sidebar navigation opens the root page" -- but only on an explicitly declared group, not an autogenerated one. So the endpoint list has to be enumerated, which is why this generates it rather than asking anyone to maintain 226 entries by hand. One group per tag now, each with its landing page as `root` and its endpoints as pages. The separate "Services" group is gone; its pages are the roots. A tag with no landing page still gets a working group, so this degrades gracefully for the services that do not have one yet. Landing pages bind by slugged display name, not by tag name, so they attach both before and after the endpoints are regrouped in semgrep-app. Today's spec still carries the old service tags and yields 48 groups, 7 of which already find their page; once the re-tagging deploys that collapses to twelve, all with pages, without touching this code. Runs in the spec-sync workflow after the sort step, whose ordering it inherits, so a newly published endpoint lands in the nav in the same PR that publishes it. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/update-openapi-specs.yml | 12 + docs/docs.json | 535 ++++++++++++++++++++- scripts/generate_api_nav.py | 137 ++++++ scripts/tests/test_generate_api_nav.py | 158 ++++++ 4 files changed, 823 insertions(+), 19 deletions(-) create mode 100644 scripts/generate_api_nav.py create mode 100644 scripts/tests/test_generate_api_nav.py diff --git a/.github/workflows/update-openapi-specs.yml b/.github/workflows/update-openapi-specs.yml index 1623be84ad..80743fb049 100644 --- a/.github/workflows/update-openapi-specs.yml +++ b/.github/workflows/update-openapi-specs.yml @@ -105,6 +105,18 @@ jobs: --link-base /api-reference/v2 \ --rss-url https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml + # One nav group per service, each opening its landing page rather than + # whichever endpoint sorts first. Mintlify's autogenerated groups cannot + # carry a `root`, so the endpoint list has to be enumerated -- which is + # why it is generated rather than hand-maintained. Runs after the sort + # step, whose order it inherits. + - name: Rebuild the API navigation + run: | + uv run scripts/generate_api_nav.py docs/public_v1.openapi.yaml \ + docs/docs.json --dropdown v1 --landing-dir api-reference/v1/services + uv run scripts/generate_api_nav.py docs/public_v2.openapi.yaml \ + docs/docs.json --dropdown v2 --landing-dir api-reference/v2/services + # Mintlify builds an endpoint's page URL from its first tag, so an # endpoint regrouped into a new service moves and its old URL 404s. # Must run before the changelog step commits, and while the previous diff --git a/docs/docs.json b/docs/docs.json index 51612aa7b8..b35ada1006 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -279,7 +279,7 @@ { "group": "View findings", "root": "semgrep-supply-chain/findings", - "pages": [ + "pages": [ "semgrep-supply-chain/finding-details" ] }, @@ -647,28 +647,525 @@ ] }, { - "group": "Services", + "group": "AI Fix Jobs", + "openapi": "public_v2.openapi.yaml", "pages": [ - "api-reference/v2/services/deployments", - "api-reference/v2/services/issues", - "api-reference/v2/services/workflow-issues", - "api-reference/v2/services/agentic-workflows", - "api-reference/v2/services/projects", - "api-reference/v2/services/scans", - "api-reference/v2/services/policies", - "api-reference/v2/services/integrations", - "api-reference/v2/services/supply-chain", - "api-reference/v2/services/analytics", - "api-reference/v2/services/multimodal", - "api-reference/v2/services/tasks" + "POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs" ] }, { - "group": "Endpoints", - "openapi": { - "source": "public_v2.openapi.yaml", - "directory": "api-reference/v2" - } + "group": "AI tasks", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/ai/{deploymentId}/combined_task", + "POST /api/ai/pattern_fix", + "GET /api/ai/{deploymentId}/info" + ] + }, + { + "group": "Autofix", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/v1/deployments/{deploymentId}/issues/{issueId}/autofix" + ] + }, + { + "group": "Automations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notifications/deployments/{deploymentId}/automations", + "GET /api/notifications/deployments/{deploymentId}/automations", + "DELETE /api/notifications/deployments/{deploymentId}/automations/{automationId}", + "PUT /api/notifications/deployments/{deploymentId}/automations/{automationId}", + "GET /api/v2/deployments/{deploymentId}/automations" + ] + }, + { + "group": "Autotriage Feedback", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/ai/deployments/{deploymentId}/autotriage_feedback" + ] + }, + { + "group": "Deployment", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/users", + "GET /api/agent/deployments/{deploymentId}/users", + "POST /api/agent/deployments", + "GET /api/agent/deployments/{deploymentId}", + "PATCH /api/agent/deployments/{deploymentId}", + "GET /api/agent/deployments/{deploymentId}/authorized_actions", + "GET /api/agent/deployments/{deploymentId}/default_user_role", + "GET /api/agent/deployment", + "GET /api/agent/deployments/{deploymentId}/github_app_status", + "POST /api/agent/deployments/{deploymentId}/users/list", + "DELETE /api/agent/deployments/{deploymentId}/users/{userId}", + "POST /api/agent/deployments/{deploymentId}/products/feedback", + "POST /api/agent/deployments/{deploymentId}/has_deepsemgrep", + "POST /api/agent/deployments/{deploymentId}/has_dependency_query", + "POST /api/agent/deployments/{deploymentId}/has_triage_via_comment", + "PATCH /api/agent/deployments/{deploymentId}/users/{userId}/roles" + ] + }, + { + "group": "Deployment Products", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "PUT /api/agent/deployments/{deploymentId}/products", + "GET /api/agent/deployments/{deploymentId}/products", + "GET /api/agent/deployments/{deploymentId}/products/admin" + ] + }, + { + "group": "Deployment SSO Providers", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/auth/sso/deployments/{deploymentId}/providers" + ] + }, + { + "group": "Deployment Tags", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "PUT /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", + "DELETE /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", + "GET /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", + "POST /api/agent/deployments/{deploymentId}/tags/find", + "GET /api/agent/deployments/{deploymentId}/tags" + ] + }, + { + "group": "Deployments", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/deployments", + "pages": [ + "GET /api/v2/deployments" + ] + }, + { + "group": "Editor", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/run" + ] + }, + { + "group": "External Ticketing", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets", + "DELETE /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/{nangoExternalTicketId}", + "GET /api/notifications/deployments/{deploymentId}/ticketing/v2", + "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/link", + "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/unlink" + ] + }, + { + "group": "Feature Rollouts", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/agent/features" + ] + }, + { + "group": "Global Ignores", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/ignores", + "DELETE /api/agent/deployments/{deploymentId}/ignores", + "GET /api/agent/deployments/{deploymentId}/ignores", + "PATCH /api/agent/deployments/{deploymentId}/ignores" + ] + }, + { + "group": "Infrastructure Configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/infra-config/bootstrap-sms-vpc" + ] + }, + { + "group": "Issues", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/issues", + "pages": [ + "PATCH /api/agent/deployments/{deploymentId}/findings/v2", + "POST /api/agent/deployments/{deploymentId}/issues/export", + "GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId}", + "GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId}/code-snippets", + "POST /api/agent/deployments/{deploymentId}/issue_counts", + "POST /api/agent/deployments/{deploymentId}/issue_filters", + "POST /api/agent/deployments/{deploymentId}/issues/search", + "POST /api/agent/deployments/{deploymentId}/issues" + ] + }, + { + "group": "Issues (Semgrep Agentic Workflows)", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/issues/{deploymentId}/counts", + "GET /api/issues/{deploymentId}/issue/{issueId}/activity", + "GET /api/issues/{deploymentId}/issue/{issueId}", + "POST /api/issues/{deploymentId}/issue/{issueId}", + "POST /api/issues/{deploymentId}/search" + ] + }, + { + "group": "Managed Scan Settings", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/managed_scan_settings", + "GET /api/agent/deployments/{deploymentId}/managed_scan_settings", + "DELETE /api/agent/deployments/{deploymentId}/managed_scan_settings/{managedScanSettingsId}", + "PUT /api/agent/deployments/{deploymentId}/managed_scan_settings/{managedScanSettingsId}" + ] + }, + { + "group": "Memories", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/ai/deployments/{deploymentId}/memories", + "POST /api/ai/deployments/{deploymentId}/memories/triage", + "DELETE /api/ai/deployments/{deploymentId}/memories/{memoryId}", + "PUT /api/ai/deployments/{deploymentId}/memories/{memoryId}", + "POST /api/ai/deployments/{deploymentId}/relevant_issues", + "POST /api/ai/deployments/{deploymentId}/memories/ids", + "GET /api/ai/deployments/{deploymentId}/memories/stats", + "POST /api/ai/deployments/{deploymentId}/memories/list", + "GET /api/ai/deployments/{deploymentId}/memories/suggested" + ] + }, + { + "group": "Notification Rules", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/actions", + "DELETE /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}", + "PUT /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}", + "POST /api/agent/deployments/{deploymentId}/actions/list", + "POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test" + ] + }, + { + "group": "Notification Webhooks", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notification_webhooks/deployments/{deploymentId}/webhooks", + "GET /api/notification_webhooks/deployments/{deploymentId}/webhooks", + "DELETE /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", + "GET /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", + "PUT /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", + "POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test" + ] + }, + { + "group": "Notifications", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/notifications/in-app/deployments/{deploymentId}", + "PUT /api/notifications/in-app/deployments/{deploymentId}/mark_as_seen" + ] + }, + { + "group": "Onboarding Checklist", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/onboarding/deployments/{deploymentId}/checklist", + "PATCH /api/onboarding/deployments/{deploymentId}/checklist", + "POST /api/onboarding/deployments/{deploymentId}/invite_members" + ] + }, + { + "group": "Policies", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/policies", + "pages": [ + "GET /api/policies/v1/deployments/{deploymentId}", + "GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules", + "PUT /api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath}" + ] + }, + { + "group": "Policies V2", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "PUT /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", + "GET /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", + "PUT /api/policies/v2/deployments/{deploymentId}/remediation-policies", + "GET /api/policies/v2/deployments/{deploymentId}/remediation-policies", + "GET /api/policies/v2/deployments/{deploymentId}/detection-policy", + "GET /api/policies/v2/deployments/{deploymentId}/vocab", + "POST /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}:dryRun", + "POST /api/policies/v2/deployments/{deploymentId}/remediation-policies:dryRun" + ] + }, + { + "group": "Projects", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/projects", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/repos/_secret", + "PATCH /api/agent/deployments/{deploymentId}/repos/filtered", + "PATCH /api/agent/deployments/{deploymentId}/repos", + "DELETE /api/v2/deployments/{deploymentId}/projects/{projectId}", + "GET /api/v2/deployments/{deploymentId}/projects/{projectId}", + "GET /api/v2/deployments/{deploymentId}/projects/{projectId}/branches/{branchId}", + "POST /api/agent/deployments/{deploymentId}/repos/count", + "GET /api/agent/deployments/{deploymentId}/repos/by-tag", + "POST /api/v2/deployments/{deploymentId}/projects/list", + "POST /api/agent/deployments/{deploymentId}/repos/_provision", + "POST /api/agent/deployments/{deploymentId}/repos/_refresh_async", + "POST /api/agent/deployments/{deploymentId}/repos/{repoId}/_sync", + "PATCH /api/agent/deployments/{deploymentId}/repos/{idOrName}" + ] + }, + { + "group": "Projects - Managed Scan Settings", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/sms/v2/deployments/{deploymentId}/project_settings", + "GET /api/sms/v2/deployments/{deploymentId}/projects/{projectId}/settings" + ] + }, + { + "group": "Reporting", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/reporting/{deploymentId}/reports/backlog/by-product", + "POST /api/reporting/{deploymentId}/reports/backlog/by-rule", + "POST /api/reporting/{deploymentId}/reports/backlog/by-severity", + "POST /api/reporting/{deploymentId}/reports/activity", + "POST /api/reporting/{deploymentId}/reports/by-project/by-product", + "POST /api/reporting/{deploymentId}/reports/by-project/by-severity", + "POST /api/reporting/{deploymentId}/reports/funnel", + "POST /api/reporting/{deploymentId}/reports/guardrails-activity", + "POST /api/reporting/{deploymentId}/reports/guardrails-adoption", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/scan-activity", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/summary", + "POST /api/reporting/{deploymentId}/reports/median-open-age/by-product", + "POST /api/reporting/{deploymentId}/reports/median-open-age/by-severity", + "POST /api/reporting/{deploymentId}/findings" + ] + }, + { + "group": "Review Comment Product Content", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/deployments/{deploymentId}/scm_comment_product_content", + "GET /api/deployments/{deploymentId}/scm_comment_product_content", + "DELETE /api/deployments/{deploymentId}/scm_comment_product_content/{reviewCommentProductContentId}", + "PUT /api/deployments/{deploymentId}/scm_comment_product_content/{reviewCommentProductContentId}" + ] + }, + { + "group": "Ruleboards", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/ruleboards", + "GET /api/agent/deployments/{deploymentId}/ruleboards", + "DELETE /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", + "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", + "PUT /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", + "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/finding_counts", + "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/overview", + "PUT /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/overview" + ] + }, + { + "group": "Scans", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/scans", + "pages": [ + "GET /api/v2/deployments/{deploymentId}/scans/{scanId}", + "POST /api/v2/deployments/{deploymentId}/projects/{projectId}/scans/list", + "POST /api/v2/deployments/{deploymentId}/scans/retry" + ] + }, + { + "group": "Semgrep Agentic Workflows", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/findings", + "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/overview", + "GET /api/workflows/v1/deployments/{deploymentId}/registry", + "POST /api/workflows/v1/deployments/{deploymentId}/jobs/list", + "POST /api/workflows/v1/deployments/{deploymentId}/trigger" + ] + }, + { + "group": "Slack", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "DELETE /api/agent/deployments/{deploymentId}/slack/installations", + "GET /api/agent/deployments/{deploymentId}/slack/installations", + "GET /api/agent/deployments/{deploymentId}/slack/install", + "GET /api/agent/deployments/{deploymentId}/slack/channel_mappings", + "GET /api/agent/deployments/{deploymentId}/slack/channels", + "POST /api/agent/deployments/{deploymentId}/slack/oauth/callback" + ] + }, + { + "group": "Source Code Management (SCM) apps", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}/add_ado_project", + "PUT /api/scm/ado_app_install", + "PUT /api/scm/app_request_complete", + "POST /api/scm/ado_app", + "POST /api/scm/app_request", + "DELETE /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}", + "GET /api/scm/deployments/{deploymentId}/scm_apps/public_gha", + "GET /api/scm/deployments/{deploymentId}/scm_apps/{requestId}", + "GET /api/scm/deployments/{deploymentId}/scm_apps", + "POST /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}/rotate_webhook_secret" + ] + }, + { + "group": "Source Code Management (SCM) configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/scm/deployments/{deploymentId}/configs/{configId}/check", + "POST /api/scm/deployments/{deploymentId}/configs", + "GET /api/scm/deployments/{deploymentId}/configs", + "DELETE /api/scm/deployments/{deploymentId}/configs/{configId}", + "PATCH /api/scm/deployments/{deploymentId}/configs/{configId}", + "POST /api/scm/deployments/{deploymentId}/configs/search", + "POST /api/scm/deployments/{deploymentId}/configs/{configId}/_sync" + ] + }, + { + "group": "Source Code Management (SCM) webhooks", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/scm/deployments/{deploymentId}/subscriptions/{configId}", + "PUT /api/scm/deployments/{deploymentId}/subscriptions/{configId}", + "DELETE /api/scm/deployments/{deploymentId}/subscriptions/{configId}", + "POST /api/scm/deployments/{deploymentId}/subscriptions/{configId}/rotate_secret" + ] + }, + { + "group": "Supply Chain", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/supply-chain", + "pages": [ + "POST /api/sca/deployments/{deploymentId}/sbom_async", + "GET /api/sca/deployments/{deploymentId}/dependencies", + "POST /api/sca/deployments/{deploymentId}/dependencies", + "POST /api/sca/deployments/{deploymentId}/advisories/{advisoryId}/findings", + "POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles", + "POST /api/sca/deployments/{deploymentId}/repositories" + ] + }, + { + "group": "Supply Chain - Managed Scans dependency resolution configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/agent/deployments/{deploymentId}/projects/{projectId}/resolution_configs" + ] + }, + { + "group": "Supply Chain - Managed Scans package manager configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", + "GET /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", + "DELETE /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}", + "PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}" + ] + }, + { + "group": "Support", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/support/cases", + "GET /api/support/cases/{orgid}" + ] + }, + { + "group": "Surveys", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/survey/name/{surveyName}", + "POST /api/survey/submit" + ] + }, + { + "group": "Tasks", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/tasks", + "pages": [ + "GET /api/tasks/v2/{taskTokenJwt}" + ] + }, + { + "group": "Teams", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/permissions/v2/deployments/{deploymentId}/status", + "POST /api/permissions/v2/deployments/{deploymentId}/teams", + "DELETE /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", + "PATCH /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/teams", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/repos", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/members", + "POST /api/permissions/v2/deployments/{deploymentId}/teams/list" + ] + }, + { + "group": "Tokens", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "DELETE /api/tokens/v1/deployments/{deploymentId}/tokens" + ] + }, + { + "group": "Users", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/auth/users/current", + "GET /api/auth/users/current/settings", + "PATCH /api/auth/users/current/settings", + "GET /api/auth/authorized_resources", + "GET /api/auth/users/current/deployments/joinable", + "GET /api/auth/users/current/deployments" + ] + }, + { + "group": "Versions", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/agent/versions/{deploymentId}/project-info/{product}" + ] + }, + { + "group": "Wiz Credentials", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notifications/deployments/{deploymentId}/wiz", + "GET /api/notifications/deployments/{deploymentId}/wiz", + "DELETE /api/notifications/deployments/{deploymentId}/wiz/{id}", + "GET /api/notifications/deployments/{deploymentId}/wiz/{id}", + "PUT /api/notifications/deployments/{deploymentId}/wiz/{id}", + "POST /api/notifications/deployments/{deploymentId}/wiz/validate" + ] + }, + { + "group": "Other", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/health-check", + "GET /api/readiness-check", + "GET /api/agent/identity", + "GET /api/agent/ip", + "GET /api/agent/tenant", + "GET /api/agent/ping" + ] } ] } diff --git a/scripts/generate_api_nav.py b/scripts/generate_api_nav.py new file mode 100644 index 0000000000..ebfcfb85c3 --- /dev/null +++ b/scripts/generate_api_nav.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.9" +# dependencies = ["pyyaml"] +# +# [tool.uv] +# exclude-newer = "1 week" +# /// +# +# Rebuild the API tab's endpoint navigation in docs.json from an OpenAPI spec. +# +# Why this exists: Mintlify can autogenerate a nav group per tag from a spec, +# and that is what this replaces. Autogenerated groups cannot carry a `root` +# page, so clicking a service in the sidebar opens whichever endpoint happens +# to sort first rather than a page describing the service. `root` is only +# available on an explicitly declared group -- which means the endpoint list +# has to be enumerated, which means it has to be generated. +# +# One group per tag, in the spec's own order (sort_openapi_nav.py has already +# put that in display-name order): +# +# { +# "group": "Issues", <- x-displayName +# "root": "api-reference/v2/services/issues", <- landing page +# "openapi": "public_v2.openapi.yaml", +# "pages": ["GET /api/issues", ...] <- METHOD /path +# } +# +# `root` is emitted only when a landing page for that service actually exists +# on disk, so a tag with no page still gets a working group. +# +# Runs in update-openapi-specs.yml after the spec is fetched, so a newly +# published endpoint appears in the nav in the same PR that adds it. + +from __future__ import annotations + +import argparse +import json +import pathlib +import re +import sys +from typing import Optional + +import yaml + +HTTP_METHODS = ("get", "post", "put", "patch", "delete", "options", "head", "trace") + + +def service_groups(spec: dict, docs_root: pathlib.Path, spec_name: str, + landing_dir: str) -> list[dict]: + """One nav group per tag, each with its endpoints and its landing page.""" + display = {} + for tag in spec.get("tags") or []: + display[tag["name"]] = tag.get("x-displayName") or tag["name"] + + # tag -> ["GET /path", ...], in the order the spec lists paths + endpoints: dict[str, list[str]] = {} + for path, item in (spec.get("paths") or {}).items(): + if not isinstance(item, dict): + continue + for method, op in item.items(): + if method not in HTTP_METHODS or not isinstance(op, dict): + continue + tags = op.get("tags") or [] + if not tags: + continue + endpoints.setdefault(tags[0], []).append(f"{method.upper()} {path}") + + groups = [] + for tag, pages in endpoints.items(): + label = display.get(tag, tag) + group: dict = {"group": label, "openapi": spec_name} + # slug the display name the way the landing pages are named + slug = re.sub(r"[^a-z0-9]+", "-", label.lower()).strip("-") + landing = f"{landing_dir}/{slug}" + if (docs_root / f"{landing}.mdx").exists(): + group["root"] = landing + group["pages"] = pages + groups.append(group) + return groups + + +def replace_groups(docs: dict, dropdown: str, groups: list[dict], + keep: tuple[str, ...]) -> dict: + """Swap the endpoint groups of one dropdown, keeping the named ones.""" + api = next(t for t in docs["navigation"]["tabs"] if t.get("tab") == "API") + dd = next(d for d in api["dropdowns"] if d["dropdown"] == dropdown) + dd["groups"] = [g for g in dd["groups"] if g.get("group") in keep] + groups + return docs + + +def main(argv: Optional[list[str]] = None) -> int: + parser = argparse.ArgumentParser( + description="Rebuild the API tab endpoint navigation from an OpenAPI spec." + ) + parser.add_argument("spec", help="path to the OpenAPI spec") + parser.add_argument("docs_json", help="path to docs.json") + parser.add_argument("--dropdown", required=True, help='e.g. "v2"') + parser.add_argument( + "--landing-dir", required=True, + help="docs-relative dir holding the service pages, e.g. api-reference/v2/services", + ) + parser.add_argument( + "--keep", default="Overview", + help="comma-separated groups to preserve ahead of the generated ones", + ) + args = parser.parse_args(argv) + + docs_path = pathlib.Path(args.docs_json) + docs_root = docs_path.parent + spec = yaml.safe_load(pathlib.Path(args.spec).read_text()) + + groups = service_groups( + spec, docs_root, pathlib.Path(args.spec).name, args.landing_dir + ) + if not groups: + print(f"{args.spec}: no tagged operations, nav left alone") + return 0 + + docs = json.loads(docs_path.read_text()) + docs = replace_groups(docs, args.dropdown, groups, tuple( + g.strip() for g in args.keep.split(",") if g.strip() + )) + docs_path.write_text(json.dumps(docs, indent=2, ensure_ascii=False) + "\n") + + rooted = sum(1 for g in groups if "root" in g) + total = sum(len(g["pages"]) for g in groups) + print(f"{docs_path}: {len(groups)} service groups, {rooted} with a landing page, " + f"{total} endpoints") + for g in groups: + mark = "" if "root" in g else " (no landing page)" + print(f" {len(g['pages']):4} {g['group']}{mark}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/tests/test_generate_api_nav.py b/scripts/tests/test_generate_api_nav.py new file mode 100644 index 0000000000..1115f33b3e --- /dev/null +++ b/scripts/tests/test_generate_api_nav.py @@ -0,0 +1,158 @@ +# Tests for scripts/generate_api_nav.py +# +# Run with: +# uv run --with pyyaml --with pytest pytest scripts/tests + +from __future__ import annotations + +import json + +import pytest + +import generate_api_nav as nav + + +def spec(*, tags, paths): + return {"tags": tags, "paths": paths} + + +ISSUES_SPEC = spec( + tags=[{"name": "IssuesService", "x-displayName": "Issues"}], + paths={ + "/api/issues": { + "get": {"tags": ["Issues"], "summary": "List issues"}, + "post": {"tags": ["Issues"], "summary": "Create issue"}, + } + }, +) + + +def test_group_carries_endpoints_in_spec_order(tmp_path): + groups = nav.service_groups(ISSUES_SPEC, tmp_path, "public_v2.openapi.yaml", "svc") + + assert len(groups) == 1 + assert groups[0]["pages"] == ["GET /api/issues", "POST /api/issues"] + assert groups[0]["openapi"] == "public_v2.openapi.yaml" + + +def test_root_is_set_when_a_landing_page_exists(tmp_path): + (tmp_path / "svc").mkdir() + (tmp_path / "svc" / "issues.mdx").write_text("landing") + + groups = nav.service_groups(ISSUES_SPEC, tmp_path, "s.yaml", "svc") + + assert groups[0]["root"] == "svc/issues" + + +def test_root_is_omitted_when_no_landing_page_exists(tmp_path): + groups = nav.service_groups(ISSUES_SPEC, tmp_path, "s.yaml", "svc") + + assert "root" not in groups[0] + assert groups[0]["pages"] # the group still works, it just opens an endpoint + + +@pytest.mark.parametrize( + "display,slug", + [ + ("Issues", "issues"), + ("Supply Chain", "supply-chain"), + ("Agentic Workflows", "agentic-workflows"), + ("Workflow issues", "workflow-issues"), + ("Issues (Agentic Workflows)", "issues-agentic-workflows"), + ], +) +def test_landing_page_is_matched_by_slugged_display_name(tmp_path, display, slug): + (tmp_path / "svc").mkdir() + (tmp_path / "svc" / f"{slug}.mdx").write_text("landing") + s = spec( + tags=[{"name": "SomeService", "x-displayName": display}], + paths={"/api/x": {"get": {"tags": [display]}}}, + ) + + groups = nav.service_groups(s, tmp_path, "s.yaml", "svc") + + assert groups[0]["group"] == display + assert groups[0]["root"] == f"svc/{slug}" + + +def test_group_label_falls_back_to_the_tag_name(tmp_path): + s = spec(tags=[], paths={"/api/x": {"get": {"tags": ["RawTag"]}}}) + + assert nav.service_groups(s, tmp_path, "s.yaml", "svc")[0]["group"] == "RawTag" + + +def test_untagged_and_non_method_keys_are_skipped(tmp_path): + s = spec( + tags=[], + paths={ + "/api/x": { + "get": {"tags": ["A"]}, + "parameters": [{"name": "q"}], # not an HTTP method + "put": {"summary": "no tags"}, # untagged + } + }, + ) + + groups = nav.service_groups(s, tmp_path, "s.yaml", "svc") + + assert len(groups) == 1 + assert groups[0]["pages"] == ["GET /api/x"] + + +def test_replace_groups_keeps_named_groups_and_drops_the_rest(): + docs = { + "navigation": { + "tabs": [ + { + "tab": "API", + "dropdowns": [ + { + "dropdown": "v2", + "groups": [ + {"group": "Overview", "pages": ["intro"]}, + {"group": "Services", "pages": ["svc/issues"]}, + {"group": "Endpoints", "openapi": {"source": "s.yaml"}}, + ], + } + ], + } + ] + } + } + + out = nav.replace_groups(docs, "v2", [{"group": "Issues", "pages": ["GET /x"]}], + keep=("Overview",)) + + groups = out["navigation"]["tabs"][0]["dropdowns"][0]["groups"] + assert [g["group"] for g in groups] == ["Overview", "Issues"] + + +def test_replace_groups_leaves_other_dropdowns_untouched(): + docs = { + "navigation": { + "tabs": [ + { + "tab": "API", + "dropdowns": [ + {"dropdown": "v1", "groups": [{"group": "Endpoints"}]}, + {"dropdown": "v2", "groups": [{"group": "Endpoints"}]}, + ], + } + ] + } + } + + out = nav.replace_groups(docs, "v2", [{"group": "Issues", "pages": []}], keep=()) + + v1 = out["navigation"]["tabs"][0]["dropdowns"][0] + assert v1["groups"] == [{"group": "Endpoints"}] + + +def test_output_is_valid_json_and_stable(tmp_path): + """Two runs over the same inputs produce the same file.""" + s = ISSUES_SPEC + groups = nav.service_groups(s, tmp_path, "s.yaml", "svc") + once = json.dumps(groups, indent=2) + twice = json.dumps(nav.service_groups(s, tmp_path, "s.yaml", "svc"), indent=2) + + assert once == twice From ff7310c67ba040c0ef691171e6dbc49941d8cad9 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Fri, 4 Sep 2026 08:42:45 -0700 Subject: [PATCH 23/27] fix(api-reference): nest the service groups so they stay collapsible The first version declared each service group at the dropdown's top level. Mintlify renders a top-level group as a flat section heading, so the services stopped being collapsible -- every endpoint of every service was expanded at once. The collapsibility came from the autogenerated `Endpoints` group nesting them one level down, and removing that group removed the nesting with it. Nest them inside a `Services` group instead. 27 other groups in this docs.json already nest this way, so this follows the file's own pattern rather than inventing one. Adds a regression test asserting the groups land nested rather than top-level, since the symptom is only visible in the rendered sidebar and `mintlify validate` passes either way. Co-Authored-By: Claude Opus 5 (1M context) --- docs/docs.json | 1039 ++++++++++++------------ scripts/generate_api_nav.py | 21 +- scripts/tests/test_generate_api_nav.py | 26 +- 3 files changed, 562 insertions(+), 524 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index b35ada1006..5b8f1705a5 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -647,524 +647,529 @@ ] }, { - "group": "AI Fix Jobs", - "openapi": "public_v2.openapi.yaml", + "group": "Services", "pages": [ - "POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs" - ] - }, - { - "group": "AI tasks", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/ai/{deploymentId}/combined_task", - "POST /api/ai/pattern_fix", - "GET /api/ai/{deploymentId}/info" - ] - }, - { - "group": "Autofix", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/v1/deployments/{deploymentId}/issues/{issueId}/autofix" - ] - }, - { - "group": "Automations", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/notifications/deployments/{deploymentId}/automations", - "GET /api/notifications/deployments/{deploymentId}/automations", - "DELETE /api/notifications/deployments/{deploymentId}/automations/{automationId}", - "PUT /api/notifications/deployments/{deploymentId}/automations/{automationId}", - "GET /api/v2/deployments/{deploymentId}/automations" - ] - }, - { - "group": "Autotriage Feedback", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/ai/deployments/{deploymentId}/autotriage_feedback" - ] - }, - { - "group": "Deployment", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/agent/deployments/{deploymentId}/users", - "GET /api/agent/deployments/{deploymentId}/users", - "POST /api/agent/deployments", - "GET /api/agent/deployments/{deploymentId}", - "PATCH /api/agent/deployments/{deploymentId}", - "GET /api/agent/deployments/{deploymentId}/authorized_actions", - "GET /api/agent/deployments/{deploymentId}/default_user_role", - "GET /api/agent/deployment", - "GET /api/agent/deployments/{deploymentId}/github_app_status", - "POST /api/agent/deployments/{deploymentId}/users/list", - "DELETE /api/agent/deployments/{deploymentId}/users/{userId}", - "POST /api/agent/deployments/{deploymentId}/products/feedback", - "POST /api/agent/deployments/{deploymentId}/has_deepsemgrep", - "POST /api/agent/deployments/{deploymentId}/has_dependency_query", - "POST /api/agent/deployments/{deploymentId}/has_triage_via_comment", - "PATCH /api/agent/deployments/{deploymentId}/users/{userId}/roles" - ] - }, - { - "group": "Deployment Products", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "PUT /api/agent/deployments/{deploymentId}/products", - "GET /api/agent/deployments/{deploymentId}/products", - "GET /api/agent/deployments/{deploymentId}/products/admin" - ] - }, - { - "group": "Deployment SSO Providers", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/auth/sso/deployments/{deploymentId}/providers" - ] - }, - { - "group": "Deployment Tags", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "PUT /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", - "DELETE /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", - "GET /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", - "POST /api/agent/deployments/{deploymentId}/tags/find", - "GET /api/agent/deployments/{deploymentId}/tags" - ] - }, - { - "group": "Deployments", - "openapi": "public_v2.openapi.yaml", - "root": "api-reference/v2/services/deployments", - "pages": [ - "GET /api/v2/deployments" - ] - }, - { - "group": "Editor", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/run" - ] - }, - { - "group": "External Ticketing", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets", - "DELETE /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/{nangoExternalTicketId}", - "GET /api/notifications/deployments/{deploymentId}/ticketing/v2", - "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/link", - "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/unlink" - ] - }, - { - "group": "Feature Rollouts", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/agent/features" - ] - }, - { - "group": "Global Ignores", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/agent/deployments/{deploymentId}/ignores", - "DELETE /api/agent/deployments/{deploymentId}/ignores", - "GET /api/agent/deployments/{deploymentId}/ignores", - "PATCH /api/agent/deployments/{deploymentId}/ignores" - ] - }, - { - "group": "Infrastructure Configurations", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/infra-config/bootstrap-sms-vpc" - ] - }, - { - "group": "Issues", - "openapi": "public_v2.openapi.yaml", - "root": "api-reference/v2/services/issues", - "pages": [ - "PATCH /api/agent/deployments/{deploymentId}/findings/v2", - "POST /api/agent/deployments/{deploymentId}/issues/export", - "GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId}", - "GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId}/code-snippets", - "POST /api/agent/deployments/{deploymentId}/issue_counts", - "POST /api/agent/deployments/{deploymentId}/issue_filters", - "POST /api/agent/deployments/{deploymentId}/issues/search", - "POST /api/agent/deployments/{deploymentId}/issues" - ] - }, - { - "group": "Issues (Semgrep Agentic Workflows)", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/issues/{deploymentId}/counts", - "GET /api/issues/{deploymentId}/issue/{issueId}/activity", - "GET /api/issues/{deploymentId}/issue/{issueId}", - "POST /api/issues/{deploymentId}/issue/{issueId}", - "POST /api/issues/{deploymentId}/search" - ] - }, - { - "group": "Managed Scan Settings", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/agent/deployments/{deploymentId}/managed_scan_settings", - "GET /api/agent/deployments/{deploymentId}/managed_scan_settings", - "DELETE /api/agent/deployments/{deploymentId}/managed_scan_settings/{managedScanSettingsId}", - "PUT /api/agent/deployments/{deploymentId}/managed_scan_settings/{managedScanSettingsId}" - ] - }, - { - "group": "Memories", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/ai/deployments/{deploymentId}/memories", - "POST /api/ai/deployments/{deploymentId}/memories/triage", - "DELETE /api/ai/deployments/{deploymentId}/memories/{memoryId}", - "PUT /api/ai/deployments/{deploymentId}/memories/{memoryId}", - "POST /api/ai/deployments/{deploymentId}/relevant_issues", - "POST /api/ai/deployments/{deploymentId}/memories/ids", - "GET /api/ai/deployments/{deploymentId}/memories/stats", - "POST /api/ai/deployments/{deploymentId}/memories/list", - "GET /api/ai/deployments/{deploymentId}/memories/suggested" - ] - }, - { - "group": "Notification Rules", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/agent/deployments/{deploymentId}/actions", - "DELETE /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}", - "PUT /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}", - "POST /api/agent/deployments/{deploymentId}/actions/list", - "POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test" - ] - }, - { - "group": "Notification Webhooks", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/notification_webhooks/deployments/{deploymentId}/webhooks", - "GET /api/notification_webhooks/deployments/{deploymentId}/webhooks", - "DELETE /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", - "GET /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", - "PUT /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", - "POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test" - ] - }, - { - "group": "Notifications", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/notifications/in-app/deployments/{deploymentId}", - "PUT /api/notifications/in-app/deployments/{deploymentId}/mark_as_seen" - ] - }, - { - "group": "Onboarding Checklist", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/onboarding/deployments/{deploymentId}/checklist", - "PATCH /api/onboarding/deployments/{deploymentId}/checklist", - "POST /api/onboarding/deployments/{deploymentId}/invite_members" - ] - }, - { - "group": "Policies", - "openapi": "public_v2.openapi.yaml", - "root": "api-reference/v2/services/policies", - "pages": [ - "GET /api/policies/v1/deployments/{deploymentId}", - "GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules", - "PUT /api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath}" - ] - }, - { - "group": "Policies V2", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "PUT /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", - "GET /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", - "PUT /api/policies/v2/deployments/{deploymentId}/remediation-policies", - "GET /api/policies/v2/deployments/{deploymentId}/remediation-policies", - "GET /api/policies/v2/deployments/{deploymentId}/detection-policy", - "GET /api/policies/v2/deployments/{deploymentId}/vocab", - "POST /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}:dryRun", - "POST /api/policies/v2/deployments/{deploymentId}/remediation-policies:dryRun" - ] - }, - { - "group": "Projects", - "openapi": "public_v2.openapi.yaml", - "root": "api-reference/v2/services/projects", - "pages": [ - "POST /api/agent/deployments/{deploymentId}/repos/_secret", - "PATCH /api/agent/deployments/{deploymentId}/repos/filtered", - "PATCH /api/agent/deployments/{deploymentId}/repos", - "DELETE /api/v2/deployments/{deploymentId}/projects/{projectId}", - "GET /api/v2/deployments/{deploymentId}/projects/{projectId}", - "GET /api/v2/deployments/{deploymentId}/projects/{projectId}/branches/{branchId}", - "POST /api/agent/deployments/{deploymentId}/repos/count", - "GET /api/agent/deployments/{deploymentId}/repos/by-tag", - "POST /api/v2/deployments/{deploymentId}/projects/list", - "POST /api/agent/deployments/{deploymentId}/repos/_provision", - "POST /api/agent/deployments/{deploymentId}/repos/_refresh_async", - "POST /api/agent/deployments/{deploymentId}/repos/{repoId}/_sync", - "PATCH /api/agent/deployments/{deploymentId}/repos/{idOrName}" - ] - }, - { - "group": "Projects - Managed Scan Settings", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/sms/v2/deployments/{deploymentId}/project_settings", - "GET /api/sms/v2/deployments/{deploymentId}/projects/{projectId}/settings" - ] - }, - { - "group": "Reporting", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/reporting/{deploymentId}/reports/backlog/by-product", - "POST /api/reporting/{deploymentId}/reports/backlog/by-rule", - "POST /api/reporting/{deploymentId}/reports/backlog/by-severity", - "POST /api/reporting/{deploymentId}/reports/activity", - "POST /api/reporting/{deploymentId}/reports/by-project/by-product", - "POST /api/reporting/{deploymentId}/reports/by-project/by-severity", - "POST /api/reporting/{deploymentId}/reports/funnel", - "POST /api/reporting/{deploymentId}/reports/guardrails-activity", - "POST /api/reporting/{deploymentId}/reports/guardrails-adoption", - "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory", - "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem", - "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem", - "POST /api/reporting/{deploymentId}/reports/malware-firewall/scan-activity", - "POST /api/reporting/{deploymentId}/reports/malware-firewall/summary", - "POST /api/reporting/{deploymentId}/reports/median-open-age/by-product", - "POST /api/reporting/{deploymentId}/reports/median-open-age/by-severity", - "POST /api/reporting/{deploymentId}/findings" - ] - }, - { - "group": "Review Comment Product Content", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/deployments/{deploymentId}/scm_comment_product_content", - "GET /api/deployments/{deploymentId}/scm_comment_product_content", - "DELETE /api/deployments/{deploymentId}/scm_comment_product_content/{reviewCommentProductContentId}", - "PUT /api/deployments/{deploymentId}/scm_comment_product_content/{reviewCommentProductContentId}" - ] - }, - { - "group": "Ruleboards", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/agent/deployments/{deploymentId}/ruleboards", - "GET /api/agent/deployments/{deploymentId}/ruleboards", - "DELETE /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", - "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", - "PUT /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", - "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/finding_counts", - "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/overview", - "PUT /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/overview" - ] - }, - { - "group": "Scans", - "openapi": "public_v2.openapi.yaml", - "root": "api-reference/v2/services/scans", - "pages": [ - "GET /api/v2/deployments/{deploymentId}/scans/{scanId}", - "POST /api/v2/deployments/{deploymentId}/projects/{projectId}/scans/list", - "POST /api/v2/deployments/{deploymentId}/scans/retry" - ] - }, - { - "group": "Semgrep Agentic Workflows", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/findings", - "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/overview", - "GET /api/workflows/v1/deployments/{deploymentId}/registry", - "POST /api/workflows/v1/deployments/{deploymentId}/jobs/list", - "POST /api/workflows/v1/deployments/{deploymentId}/trigger" - ] - }, - { - "group": "Slack", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "DELETE /api/agent/deployments/{deploymentId}/slack/installations", - "GET /api/agent/deployments/{deploymentId}/slack/installations", - "GET /api/agent/deployments/{deploymentId}/slack/install", - "GET /api/agent/deployments/{deploymentId}/slack/channel_mappings", - "GET /api/agent/deployments/{deploymentId}/slack/channels", - "POST /api/agent/deployments/{deploymentId}/slack/oauth/callback" - ] - }, - { - "group": "Source Code Management (SCM) apps", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}/add_ado_project", - "PUT /api/scm/ado_app_install", - "PUT /api/scm/app_request_complete", - "POST /api/scm/ado_app", - "POST /api/scm/app_request", - "DELETE /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}", - "GET /api/scm/deployments/{deploymentId}/scm_apps/public_gha", - "GET /api/scm/deployments/{deploymentId}/scm_apps/{requestId}", - "GET /api/scm/deployments/{deploymentId}/scm_apps", - "POST /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}/rotate_webhook_secret" - ] - }, - { - "group": "Source Code Management (SCM) configurations", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/scm/deployments/{deploymentId}/configs/{configId}/check", - "POST /api/scm/deployments/{deploymentId}/configs", - "GET /api/scm/deployments/{deploymentId}/configs", - "DELETE /api/scm/deployments/{deploymentId}/configs/{configId}", - "PATCH /api/scm/deployments/{deploymentId}/configs/{configId}", - "POST /api/scm/deployments/{deploymentId}/configs/search", - "POST /api/scm/deployments/{deploymentId}/configs/{configId}/_sync" - ] - }, - { - "group": "Source Code Management (SCM) webhooks", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/scm/deployments/{deploymentId}/subscriptions/{configId}", - "PUT /api/scm/deployments/{deploymentId}/subscriptions/{configId}", - "DELETE /api/scm/deployments/{deploymentId}/subscriptions/{configId}", - "POST /api/scm/deployments/{deploymentId}/subscriptions/{configId}/rotate_secret" - ] - }, - { - "group": "Supply Chain", - "openapi": "public_v2.openapi.yaml", - "root": "api-reference/v2/services/supply-chain", - "pages": [ - "POST /api/sca/deployments/{deploymentId}/sbom_async", - "GET /api/sca/deployments/{deploymentId}/dependencies", - "POST /api/sca/deployments/{deploymentId}/dependencies", - "POST /api/sca/deployments/{deploymentId}/advisories/{advisoryId}/findings", - "POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles", - "POST /api/sca/deployments/{deploymentId}/repositories" - ] - }, - { - "group": "Supply Chain - Managed Scans dependency resolution configurations", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/agent/deployments/{deploymentId}/projects/{projectId}/resolution_configs" - ] - }, - { - "group": "Supply Chain - Managed Scans package manager configurations", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", - "GET /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", - "DELETE /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}", - "PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}" - ] - }, - { - "group": "Support", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/support/cases", - "GET /api/support/cases/{orgid}" - ] - }, - { - "group": "Surveys", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/survey/name/{surveyName}", - "POST /api/survey/submit" - ] - }, - { - "group": "Tasks", - "openapi": "public_v2.openapi.yaml", - "root": "api-reference/v2/services/tasks", - "pages": [ - "GET /api/tasks/v2/{taskTokenJwt}" - ] - }, - { - "group": "Teams", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/permissions/v2/deployments/{deploymentId}/status", - "POST /api/permissions/v2/deployments/{deploymentId}/teams", - "DELETE /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", - "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", - "PATCH /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", - "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/teams", - "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/repos", - "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/members", - "POST /api/permissions/v2/deployments/{deploymentId}/teams/list" - ] - }, - { - "group": "Tokens", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "DELETE /api/tokens/v1/deployments/{deploymentId}/tokens" - ] - }, - { - "group": "Users", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/auth/users/current", - "GET /api/auth/users/current/settings", - "PATCH /api/auth/users/current/settings", - "GET /api/auth/authorized_resources", - "GET /api/auth/users/current/deployments/joinable", - "GET /api/auth/users/current/deployments" - ] - }, - { - "group": "Versions", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/agent/versions/{deploymentId}/project-info/{product}" - ] - }, - { - "group": "Wiz Credentials", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "POST /api/notifications/deployments/{deploymentId}/wiz", - "GET /api/notifications/deployments/{deploymentId}/wiz", - "DELETE /api/notifications/deployments/{deploymentId}/wiz/{id}", - "GET /api/notifications/deployments/{deploymentId}/wiz/{id}", - "PUT /api/notifications/deployments/{deploymentId}/wiz/{id}", - "POST /api/notifications/deployments/{deploymentId}/wiz/validate" - ] - }, - { - "group": "Other", - "openapi": "public_v2.openapi.yaml", - "pages": [ - "GET /api/health-check", - "GET /api/readiness-check", - "GET /api/agent/identity", - "GET /api/agent/ip", - "GET /api/agent/tenant", - "GET /api/agent/ping" + { + "group": "AI Fix Jobs", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs" + ] + }, + { + "group": "AI tasks", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/ai/{deploymentId}/combined_task", + "POST /api/ai/pattern_fix", + "GET /api/ai/{deploymentId}/info" + ] + }, + { + "group": "Autofix", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/v1/deployments/{deploymentId}/issues/{issueId}/autofix" + ] + }, + { + "group": "Automations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notifications/deployments/{deploymentId}/automations", + "GET /api/notifications/deployments/{deploymentId}/automations", + "DELETE /api/notifications/deployments/{deploymentId}/automations/{automationId}", + "PUT /api/notifications/deployments/{deploymentId}/automations/{automationId}", + "GET /api/v2/deployments/{deploymentId}/automations" + ] + }, + { + "group": "Autotriage Feedback", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/ai/deployments/{deploymentId}/autotriage_feedback" + ] + }, + { + "group": "Deployment", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/users", + "GET /api/agent/deployments/{deploymentId}/users", + "POST /api/agent/deployments", + "GET /api/agent/deployments/{deploymentId}", + "PATCH /api/agent/deployments/{deploymentId}", + "GET /api/agent/deployments/{deploymentId}/authorized_actions", + "GET /api/agent/deployments/{deploymentId}/default_user_role", + "GET /api/agent/deployment", + "GET /api/agent/deployments/{deploymentId}/github_app_status", + "POST /api/agent/deployments/{deploymentId}/users/list", + "DELETE /api/agent/deployments/{deploymentId}/users/{userId}", + "POST /api/agent/deployments/{deploymentId}/products/feedback", + "POST /api/agent/deployments/{deploymentId}/has_deepsemgrep", + "POST /api/agent/deployments/{deploymentId}/has_dependency_query", + "POST /api/agent/deployments/{deploymentId}/has_triage_via_comment", + "PATCH /api/agent/deployments/{deploymentId}/users/{userId}/roles" + ] + }, + { + "group": "Deployment Products", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "PUT /api/agent/deployments/{deploymentId}/products", + "GET /api/agent/deployments/{deploymentId}/products", + "GET /api/agent/deployments/{deploymentId}/products/admin" + ] + }, + { + "group": "Deployment SSO Providers", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/auth/sso/deployments/{deploymentId}/providers" + ] + }, + { + "group": "Deployment Tags", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "PUT /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", + "DELETE /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", + "GET /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", + "POST /api/agent/deployments/{deploymentId}/tags/find", + "GET /api/agent/deployments/{deploymentId}/tags" + ] + }, + { + "group": "Deployments", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/deployments", + "pages": [ + "GET /api/v2/deployments" + ] + }, + { + "group": "Editor", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/run" + ] + }, + { + "group": "External Ticketing", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets", + "DELETE /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/{nangoExternalTicketId}", + "GET /api/notifications/deployments/{deploymentId}/ticketing/v2", + "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/link", + "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/unlink" + ] + }, + { + "group": "Feature Rollouts", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/agent/features" + ] + }, + { + "group": "Global Ignores", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/ignores", + "DELETE /api/agent/deployments/{deploymentId}/ignores", + "GET /api/agent/deployments/{deploymentId}/ignores", + "PATCH /api/agent/deployments/{deploymentId}/ignores" + ] + }, + { + "group": "Infrastructure Configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/infra-config/bootstrap-sms-vpc" + ] + }, + { + "group": "Issues", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/issues", + "pages": [ + "PATCH /api/agent/deployments/{deploymentId}/findings/v2", + "POST /api/agent/deployments/{deploymentId}/issues/export", + "GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId}", + "GET /api/agent/deployments/{deploymentId}/issues/v2/{issueId}/code-snippets", + "POST /api/agent/deployments/{deploymentId}/issue_counts", + "POST /api/agent/deployments/{deploymentId}/issue_filters", + "POST /api/agent/deployments/{deploymentId}/issues/search", + "POST /api/agent/deployments/{deploymentId}/issues" + ] + }, + { + "group": "Issues (Semgrep Agentic Workflows)", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/issues/{deploymentId}/counts", + "GET /api/issues/{deploymentId}/issue/{issueId}/activity", + "GET /api/issues/{deploymentId}/issue/{issueId}", + "POST /api/issues/{deploymentId}/issue/{issueId}", + "POST /api/issues/{deploymentId}/search" + ] + }, + { + "group": "Managed Scan Settings", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/managed_scan_settings", + "GET /api/agent/deployments/{deploymentId}/managed_scan_settings", + "DELETE /api/agent/deployments/{deploymentId}/managed_scan_settings/{managedScanSettingsId}", + "PUT /api/agent/deployments/{deploymentId}/managed_scan_settings/{managedScanSettingsId}" + ] + }, + { + "group": "Memories", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/ai/deployments/{deploymentId}/memories", + "POST /api/ai/deployments/{deploymentId}/memories/triage", + "DELETE /api/ai/deployments/{deploymentId}/memories/{memoryId}", + "PUT /api/ai/deployments/{deploymentId}/memories/{memoryId}", + "POST /api/ai/deployments/{deploymentId}/relevant_issues", + "POST /api/ai/deployments/{deploymentId}/memories/ids", + "GET /api/ai/deployments/{deploymentId}/memories/stats", + "POST /api/ai/deployments/{deploymentId}/memories/list", + "GET /api/ai/deployments/{deploymentId}/memories/suggested" + ] + }, + { + "group": "Notification Rules", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/actions", + "DELETE /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}", + "PUT /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}", + "POST /api/agent/deployments/{deploymentId}/actions/list", + "POST /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}/test" + ] + }, + { + "group": "Notification Webhooks", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notification_webhooks/deployments/{deploymentId}/webhooks", + "GET /api/notification_webhooks/deployments/{deploymentId}/webhooks", + "DELETE /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", + "GET /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", + "PUT /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}", + "POST /api/notification_webhooks/deployments/{deploymentId}/webhooks/{webhookId}/test" + ] + }, + { + "group": "Notifications", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/notifications/in-app/deployments/{deploymentId}", + "PUT /api/notifications/in-app/deployments/{deploymentId}/mark_as_seen" + ] + }, + { + "group": "Onboarding Checklist", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/onboarding/deployments/{deploymentId}/checklist", + "PATCH /api/onboarding/deployments/{deploymentId}/checklist", + "POST /api/onboarding/deployments/{deploymentId}/invite_members" + ] + }, + { + "group": "Policies", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/policies", + "pages": [ + "GET /api/policies/v1/deployments/{deploymentId}", + "GET /api/policies/v1/deployments/{deploymentId}/{policyId}/rules", + "PUT /api/policies/v1/deployments/{deploymentId}/{policyId}/rules/{rulePath}" + ] + }, + { + "group": "Policies V2", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "PUT /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", + "GET /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", + "PUT /api/policies/v2/deployments/{deploymentId}/remediation-policies", + "GET /api/policies/v2/deployments/{deploymentId}/remediation-policies", + "GET /api/policies/v2/deployments/{deploymentId}/detection-policy", + "GET /api/policies/v2/deployments/{deploymentId}/vocab", + "POST /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}:dryRun", + "POST /api/policies/v2/deployments/{deploymentId}/remediation-policies:dryRun" + ] + }, + { + "group": "Projects", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/projects", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/repos/_secret", + "PATCH /api/agent/deployments/{deploymentId}/repos/filtered", + "PATCH /api/agent/deployments/{deploymentId}/repos", + "DELETE /api/v2/deployments/{deploymentId}/projects/{projectId}", + "GET /api/v2/deployments/{deploymentId}/projects/{projectId}", + "GET /api/v2/deployments/{deploymentId}/projects/{projectId}/branches/{branchId}", + "POST /api/agent/deployments/{deploymentId}/repos/count", + "GET /api/agent/deployments/{deploymentId}/repos/by-tag", + "POST /api/v2/deployments/{deploymentId}/projects/list", + "POST /api/agent/deployments/{deploymentId}/repos/_provision", + "POST /api/agent/deployments/{deploymentId}/repos/_refresh_async", + "POST /api/agent/deployments/{deploymentId}/repos/{repoId}/_sync", + "PATCH /api/agent/deployments/{deploymentId}/repos/{idOrName}" + ] + }, + { + "group": "Projects - Managed Scan Settings", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/sms/v2/deployments/{deploymentId}/project_settings", + "GET /api/sms/v2/deployments/{deploymentId}/projects/{projectId}/settings" + ] + }, + { + "group": "Reporting", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/reporting/{deploymentId}/reports/backlog/by-product", + "POST /api/reporting/{deploymentId}/reports/backlog/by-rule", + "POST /api/reporting/{deploymentId}/reports/backlog/by-severity", + "POST /api/reporting/{deploymentId}/reports/activity", + "POST /api/reporting/{deploymentId}/reports/by-project/by-product", + "POST /api/reporting/{deploymentId}/reports/by-project/by-severity", + "POST /api/reporting/{deploymentId}/reports/funnel", + "POST /api/reporting/{deploymentId}/reports/guardrails-activity", + "POST /api/reporting/{deploymentId}/reports/guardrails-adoption", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-advisory", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-dep-version-and-ecosystem", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/blocked-mal-deps/by-ecosystem", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/scan-activity", + "POST /api/reporting/{deploymentId}/reports/malware-firewall/summary", + "POST /api/reporting/{deploymentId}/reports/median-open-age/by-product", + "POST /api/reporting/{deploymentId}/reports/median-open-age/by-severity", + "POST /api/reporting/{deploymentId}/findings" + ] + }, + { + "group": "Review Comment Product Content", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/deployments/{deploymentId}/scm_comment_product_content", + "GET /api/deployments/{deploymentId}/scm_comment_product_content", + "DELETE /api/deployments/{deploymentId}/scm_comment_product_content/{reviewCommentProductContentId}", + "PUT /api/deployments/{deploymentId}/scm_comment_product_content/{reviewCommentProductContentId}" + ] + }, + { + "group": "Ruleboards", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/ruleboards", + "GET /api/agent/deployments/{deploymentId}/ruleboards", + "DELETE /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", + "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", + "PUT /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}", + "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/finding_counts", + "GET /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/overview", + "PUT /api/agent/deployments/{deploymentId}/ruleboards/{ruleboardSlug}/overview" + ] + }, + { + "group": "Scans", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/scans", + "pages": [ + "GET /api/v2/deployments/{deploymentId}/scans/{scanId}", + "POST /api/v2/deployments/{deploymentId}/projects/{projectId}/scans/list", + "POST /api/v2/deployments/{deploymentId}/scans/retry" + ] + }, + { + "group": "Semgrep Agentic Workflows", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/findings", + "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/overview", + "GET /api/workflows/v1/deployments/{deploymentId}/registry", + "POST /api/workflows/v1/deployments/{deploymentId}/jobs/list", + "POST /api/workflows/v1/deployments/{deploymentId}/trigger" + ] + }, + { + "group": "Slack", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "DELETE /api/agent/deployments/{deploymentId}/slack/installations", + "GET /api/agent/deployments/{deploymentId}/slack/installations", + "GET /api/agent/deployments/{deploymentId}/slack/install", + "GET /api/agent/deployments/{deploymentId}/slack/channel_mappings", + "GET /api/agent/deployments/{deploymentId}/slack/channels", + "POST /api/agent/deployments/{deploymentId}/slack/oauth/callback" + ] + }, + { + "group": "Source Code Management (SCM) apps", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}/add_ado_project", + "PUT /api/scm/ado_app_install", + "PUT /api/scm/app_request_complete", + "POST /api/scm/ado_app", + "POST /api/scm/app_request", + "DELETE /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}", + "GET /api/scm/deployments/{deploymentId}/scm_apps/public_gha", + "GET /api/scm/deployments/{deploymentId}/scm_apps/{requestId}", + "GET /api/scm/deployments/{deploymentId}/scm_apps", + "POST /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}/rotate_webhook_secret" + ] + }, + { + "group": "Source Code Management (SCM) configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/scm/deployments/{deploymentId}/configs/{configId}/check", + "POST /api/scm/deployments/{deploymentId}/configs", + "GET /api/scm/deployments/{deploymentId}/configs", + "DELETE /api/scm/deployments/{deploymentId}/configs/{configId}", + "PATCH /api/scm/deployments/{deploymentId}/configs/{configId}", + "POST /api/scm/deployments/{deploymentId}/configs/search", + "POST /api/scm/deployments/{deploymentId}/configs/{configId}/_sync" + ] + }, + { + "group": "Source Code Management (SCM) webhooks", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/scm/deployments/{deploymentId}/subscriptions/{configId}", + "PUT /api/scm/deployments/{deploymentId}/subscriptions/{configId}", + "DELETE /api/scm/deployments/{deploymentId}/subscriptions/{configId}", + "POST /api/scm/deployments/{deploymentId}/subscriptions/{configId}/rotate_secret" + ] + }, + { + "group": "Supply Chain", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/supply-chain", + "pages": [ + "POST /api/sca/deployments/{deploymentId}/sbom_async", + "GET /api/sca/deployments/{deploymentId}/dependencies", + "POST /api/sca/deployments/{deploymentId}/dependencies", + "POST /api/sca/deployments/{deploymentId}/advisories/{advisoryId}/findings", + "POST /api/sca/deployments/{deploymentId}/{repositoryId}/lockfiles", + "POST /api/sca/deployments/{deploymentId}/repositories" + ] + }, + { + "group": "Supply Chain - Managed Scans dependency resolution configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/agent/deployments/{deploymentId}/projects/{projectId}/resolution_configs" + ] + }, + { + "group": "Supply Chain - Managed Scans package manager configurations", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", + "GET /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", + "DELETE /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}", + "PATCH /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs/{packageManagerConfigId}" + ] + }, + { + "group": "Support", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/support/cases", + "GET /api/support/cases/{orgid}" + ] + }, + { + "group": "Surveys", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/survey/name/{surveyName}", + "POST /api/survey/submit" + ] + }, + { + "group": "Tasks", + "openapi": "public_v2.openapi.yaml", + "root": "api-reference/v2/services/tasks", + "pages": [ + "GET /api/tasks/v2/{taskTokenJwt}" + ] + }, + { + "group": "Teams", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/permissions/v2/deployments/{deploymentId}/status", + "POST /api/permissions/v2/deployments/{deploymentId}/teams", + "DELETE /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", + "PATCH /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/teams", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/repos", + "GET /api/permissions/v2/deployments/{deploymentId}/teams/{teamId}/members", + "POST /api/permissions/v2/deployments/{deploymentId}/teams/list" + ] + }, + { + "group": "Tokens", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "DELETE /api/tokens/v1/deployments/{deploymentId}/tokens" + ] + }, + { + "group": "Users", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/auth/users/current", + "GET /api/auth/users/current/settings", + "PATCH /api/auth/users/current/settings", + "GET /api/auth/authorized_resources", + "GET /api/auth/users/current/deployments/joinable", + "GET /api/auth/users/current/deployments" + ] + }, + { + "group": "Versions", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/agent/versions/{deploymentId}/project-info/{product}" + ] + }, + { + "group": "Wiz Credentials", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "POST /api/notifications/deployments/{deploymentId}/wiz", + "GET /api/notifications/deployments/{deploymentId}/wiz", + "DELETE /api/notifications/deployments/{deploymentId}/wiz/{id}", + "GET /api/notifications/deployments/{deploymentId}/wiz/{id}", + "PUT /api/notifications/deployments/{deploymentId}/wiz/{id}", + "POST /api/notifications/deployments/{deploymentId}/wiz/validate" + ] + }, + { + "group": "Other", + "openapi": "public_v2.openapi.yaml", + "pages": [ + "GET /api/health-check", + "GET /api/readiness-check", + "GET /api/agent/identity", + "GET /api/agent/ip", + "GET /api/agent/tenant", + "GET /api/agent/ping" + ] + } ] } ] diff --git a/scripts/generate_api_nav.py b/scripts/generate_api_nav.py index ebfcfb85c3..77f2742776 100644 --- a/scripts/generate_api_nav.py +++ b/scripts/generate_api_nav.py @@ -81,11 +81,20 @@ def service_groups(spec: dict, docs_root: pathlib.Path, spec_name: str, def replace_groups(docs: dict, dropdown: str, groups: list[dict], - keep: tuple[str, ...]) -> dict: - """Swap the endpoint groups of one dropdown, keeping the named ones.""" + keep: tuple[str, ...], parent: str) -> dict: + """Swap the endpoint groups of one dropdown, keeping the named ones. + + The service groups are nested one level inside `parent` rather than sitting + at the dropdown's top level. A top-level group renders as a flat section + heading; only a nested one collapses, which is what the autogenerated + `Endpoints` group used to provide. 27 other groups in this docs.json nest + the same way. + """ api = next(t for t in docs["navigation"]["tabs"] if t.get("tab") == "API") dd = next(d for d in api["dropdowns"] if d["dropdown"] == dropdown) - dd["groups"] = [g for g in dd["groups"] if g.get("group") in keep] + groups + dd["groups"] = [g for g in dd["groups"] if g.get("group") in keep] + [ + {"group": parent, "pages": groups} + ] return docs @@ -100,6 +109,10 @@ def main(argv: Optional[list[str]] = None) -> int: "--landing-dir", required=True, help="docs-relative dir holding the service pages, e.g. api-reference/v2/services", ) + parser.add_argument( + "--parent", default="Services", + help="name of the collapsible group the service groups nest inside", + ) parser.add_argument( "--keep", default="Overview", help="comma-separated groups to preserve ahead of the generated ones", @@ -120,7 +133,7 @@ def main(argv: Optional[list[str]] = None) -> int: docs = json.loads(docs_path.read_text()) docs = replace_groups(docs, args.dropdown, groups, tuple( g.strip() for g in args.keep.split(",") if g.strip() - )) + ), args.parent) docs_path.write_text(json.dumps(docs, indent=2, ensure_ascii=False) + "\n") rooted = sum(1 for g in groups if "root" in g) diff --git a/scripts/tests/test_generate_api_nav.py b/scripts/tests/test_generate_api_nav.py index 1115f33b3e..e7fd4232e2 100644 --- a/scripts/tests/test_generate_api_nav.py +++ b/scripts/tests/test_generate_api_nav.py @@ -121,10 +121,13 @@ def test_replace_groups_keeps_named_groups_and_drops_the_rest(): } out = nav.replace_groups(docs, "v2", [{"group": "Issues", "pages": ["GET /x"]}], - keep=("Overview",)) + keep=("Overview",), parent="Services") groups = out["navigation"]["tabs"][0]["dropdowns"][0]["groups"] - assert [g["group"] for g in groups] == ["Overview", "Issues"] + assert [g["group"] for g in groups] == ["Overview", "Services"] + # nested one level, not top-level: a top-level group renders as a flat + # heading, only a nested one collapses + assert [g["group"] for g in groups[1]["pages"]] == ["Issues"] def test_replace_groups_leaves_other_dropdowns_untouched(): @@ -142,7 +145,8 @@ def test_replace_groups_leaves_other_dropdowns_untouched(): } } - out = nav.replace_groups(docs, "v2", [{"group": "Issues", "pages": []}], keep=()) + out = nav.replace_groups(docs, "v2", [{"group": "Issues", "pages": []}], + keep=(), parent="Services") v1 = out["navigation"]["tabs"][0]["dropdowns"][0] assert v1["groups"] == [{"group": "Endpoints"}] @@ -156,3 +160,19 @@ def test_output_is_valid_json_and_stable(tmp_path): twice = json.dumps(nav.service_groups(s, tmp_path, "s.yaml", "svc"), indent=2) assert once == twice + + +def test_service_groups_nest_so_they_stay_collapsible(): + """Regression: the first version put these at the dropdown's top level, + which Mintlify renders as flat headings that cannot be collapsed.""" + docs = {"navigation": {"tabs": [{"tab": "API", "dropdowns": [ + {"dropdown": "v2", "groups": [{"group": "Overview", "pages": ["intro"]}]}]}]}} + svc = [{"group": "Issues", "root": "svc/issues", "pages": ["GET /x"]}, + {"group": "Scans", "pages": ["GET /y"]}] + + out = nav.replace_groups(docs, "v2", svc, keep=("Overview",), parent="Services") + + top = out["navigation"]["tabs"][0]["dropdowns"][0]["groups"] + assert len(top) == 2 and top[1]["group"] == "Services" + assert all("group" in p for p in top[1]["pages"]) + assert top[1]["pages"][0]["root"] == "svc/issues" From f039318dd133662562c46b269564b974a61a814e Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Fri, 4 Sep 2026 09:01:19 -0700 Subject: [PATCH 24/27] feat(api-reference): list each service's endpoints on its landing page The landing pages carried a hardcoded "This service has no published endpoints yet", which was wrong the moment the first endpoint was filed. Each page now imports a generated table of its endpoints: method badge, summary linked to the endpoint's reference page, and the lead sentence of its description. Generated per landing page rather than per spec tag, so a tag with no page produces nothing and a page whose service is still empty gets an accurate empty state -- generated, so it cannot go stale, which is what the hardcoded sentence could not manage. Prose on the page stays hand-written. Endpoint URLs come from generate_api_changelog's slug rules rather than a second copy: those were reverse-engineered against the rendered site, and two copies would drift into broken links. Also fixes a URL regression this work exposed. The previous commit replaced the autogenerated endpoint group, which carried `directory: api-reference/v2`, with explicit groups that did not -- so Mintlify served every endpoint from /api-reference// instead of /api-reference///. That silently moved every existing endpoint URL and would have collided v1 with v2. `mintlify validate` passes either way; the only thing that caught it was fetching the URLs, where all 202 generated links 404'd. The nav generator now emits the object form of `openapi` with `directory`. Verified against the re-tagged spec from semgrep-app on a local dev server: all 202 links 200, every landing page renders, and the tables show linked summaries with descriptions. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/update-openapi-specs.yml | 17 +- .../v2/services/agentic-workflows.mdx | 12 +- docs/api-reference/v2/services/analytics.mdx | 12 +- .../api-reference/v2/services/deployments.mdx | 12 +- .../v2/services/integrations.mdx | 12 +- docs/api-reference/v2/services/issues.mdx | 12 +- docs/api-reference/v2/services/multimodal.mdx | 12 +- docs/api-reference/v2/services/policies.mdx | 12 +- docs/api-reference/v2/services/projects.mdx | 12 +- docs/api-reference/v2/services/scans.mdx | 12 +- .../v2/services/supply-chain.mdx | 12 +- docs/api-reference/v2/services/tasks.mdx | 12 +- .../v2/services/workflow-issues.mdx | 12 +- docs/docs.json | 240 ++++++++++++++---- .../api/v2/agentic-workflows-endpoints.mdx | 5 + docs/snippets/api/v2/analytics-endpoints.mdx | 5 + .../snippets/api/v2/deployments-endpoints.mdx | 5 + .../api/v2/integrations-endpoints.mdx | 5 + docs/snippets/api/v2/issues-endpoints.mdx | 12 + docs/snippets/api/v2/multimodal-endpoints.mdx | 5 + docs/snippets/api/v2/policies-endpoints.mdx | 7 + docs/snippets/api/v2/projects-endpoints.mdx | 17 ++ docs/snippets/api/v2/scans-endpoints.mdx | 7 + .../api/v2/supply-chain-endpoints.mdx | 10 + docs/snippets/api/v2/tasks-endpoints.mdx | 5 + .../api/v2/workflow-issues-endpoints.mdx | 5 + scripts/generate_api_nav.py | 19 +- scripts/generate_service_endpoints.py | 158 ++++++++++++ .../tests/test_generate_service_endpoints.py | 126 +++++++++ 29 files changed, 679 insertions(+), 113 deletions(-) create mode 100644 docs/snippets/api/v2/agentic-workflows-endpoints.mdx create mode 100644 docs/snippets/api/v2/analytics-endpoints.mdx create mode 100644 docs/snippets/api/v2/deployments-endpoints.mdx create mode 100644 docs/snippets/api/v2/integrations-endpoints.mdx create mode 100644 docs/snippets/api/v2/issues-endpoints.mdx create mode 100644 docs/snippets/api/v2/multimodal-endpoints.mdx create mode 100644 docs/snippets/api/v2/policies-endpoints.mdx create mode 100644 docs/snippets/api/v2/projects-endpoints.mdx create mode 100644 docs/snippets/api/v2/scans-endpoints.mdx create mode 100644 docs/snippets/api/v2/supply-chain-endpoints.mdx create mode 100644 docs/snippets/api/v2/tasks-endpoints.mdx create mode 100644 docs/snippets/api/v2/workflow-issues-endpoints.mdx create mode 100644 scripts/generate_service_endpoints.py create mode 100644 scripts/tests/test_generate_service_endpoints.py diff --git a/.github/workflows/update-openapi-specs.yml b/.github/workflows/update-openapi-specs.yml index 80743fb049..68e9b2cdd9 100644 --- a/.github/workflows/update-openapi-specs.yml +++ b/.github/workflows/update-openapi-specs.yml @@ -105,6 +105,16 @@ jobs: --link-base /api-reference/v2 \ --rss-url https://docs.semgrep.dev/api-reference/v2/Changelog/rss.xml + # The table each service landing page shows. Hand-maintaining it would go + # stale every time an endpoint is published or regrouped, and a hardcoded + # "no endpoints yet" placeholder is wrong the moment the first one lands. + - name: Regenerate the service endpoint tables + run: | + uv run scripts/generate_service_endpoints.py docs/public_v2.openapi.yaml \ + docs/snippets/api/v2 --link-base /api-reference/v2 \ + --changelog /api-reference/v2/Changelog \ + --services-dir docs/api-reference/v2/services + # One nav group per service, each opening its landing page rather than # whichever endpoint sorts first. Mintlify's autogenerated groups cannot # carry a `root`, so the endpoint list has to be enumerated -- which is @@ -113,9 +123,11 @@ jobs: - name: Rebuild the API navigation run: | uv run scripts/generate_api_nav.py docs/public_v1.openapi.yaml \ - docs/docs.json --dropdown v1 --landing-dir api-reference/v1/services + docs/docs.json --dropdown v1 --landing-dir api-reference/v1/services \ + --directory api-reference/v1 uv run scripts/generate_api_nav.py docs/public_v2.openapi.yaml \ - docs/docs.json --dropdown v2 --landing-dir api-reference/v2/services + docs/docs.json --dropdown v2 --landing-dir api-reference/v2/services \ + --directory api-reference/v2 # Mintlify builds an endpoint's page URL from its first tag, so an # endpoint regrouped into a new service moves and its old URL 404s. @@ -149,6 +161,7 @@ jobs: docs/docs.json docs/api-reference/v1/Changelog.mdx docs/api-reference/v2/Changelog.mdx + docs/snippets/api branch: chore/update-openapi-specs delete-branch: true # Re-issued on every run, not just when the PR is first opened. That diff --git a/docs/api-reference/v2/services/agentic-workflows.mdx b/docs/api-reference/v2/services/agentic-workflows.mdx index bff05db1f3..50c817493f 100644 --- a/docs/api-reference/v2/services/agentic-workflows.mdx +++ b/docs/api-reference/v2/services/agentic-workflows.mdx @@ -3,12 +3,14 @@ title: "Agentic Workflows" description: "Workflow job execution and schedules." --- +import AgenticWorkflowsEndpoints from "/snippets/api/v2/agentic-workflows-endpoints.mdx" + Agentic Workflows run analysis jobs on a schedule or on demand. These endpoints cover the execution and scheduling of those jobs — the issues they produce live in [Workflow issues](/api-reference/v2/services/workflow-issues). ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/analytics.mdx b/docs/api-reference/v2/services/analytics.mdx index 4f9a8f6d4b..9c0653ffb6 100644 --- a/docs/api-reference/v2/services/analytics.mdx +++ b/docs/api-reference/v2/services/analytics.mdx @@ -3,12 +3,14 @@ title: "Analytics" description: "Aggregate program reporting." --- +import AnalyticsEndpoints from "/snippets/api/v2/analytics-endpoints.mdx" + Analytics answers questions about your program as a whole rather than about one project or issue — coverage, trends over time, and how remediation is progressing. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/deployments.mdx b/docs/api-reference/v2/services/deployments.mdx index e28addf567..b2031597bb 100644 --- a/docs/api-reference/v2/services/deployments.mdx +++ b/docs/api-reference/v2/services/deployments.mdx @@ -3,12 +3,14 @@ title: "Deployments" description: "The deployment, its users, product configuration, RBAC teams, and token identity." --- +import DeploymentsEndpoints from "/snippets/api/v2/deployments-endpoints.mdx" + A deployment is your organization in Semgrep, and the root of almost every other resource: projects belong to a deployment, as do policies, scans, and integrations. These endpoints manage the deployment itself, the people in it, and what each of them may do. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/integrations.mdx b/docs/api-reference/v2/services/integrations.mdx index e7ddef3b26..affe48f479 100644 --- a/docs/api-reference/v2/services/integrations.mdx +++ b/docs/api-reference/v2/services/integrations.mdx @@ -3,12 +3,14 @@ title: "Integrations" description: "Source code manager connections, package manager credentials, and ticketing." --- +import IntegrationsEndpoints from "/snippets/api/v2/integrations-endpoints.mdx" + Integrations are the connections between Semgrep and the systems around it. These endpoints manage source code manager connections and apps, the credentials Semgrep uses to reach private package registries, and the ticketing systems it files issues into. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/issues.mdx b/docs/api-reference/v2/services/issues.mdx index 2bb67a8715..4679a204d7 100644 --- a/docs/api-reference/v2/services/issues.mdx +++ b/docs/api-reference/v2/services/issues.mdx @@ -3,12 +3,14 @@ title: "Issues" description: "Issues, counts, triage, exports, and AI fix jobs." --- +import IssuesEndpoints from "/snippets/api/v2/issues-endpoints.mdx" + An issue is the persistent, triageable record of something Semgrep found — as distinct from a finding, which is one immutable detection from one scan. These endpoints list and filter issues, move them through triage, export them, and manage the AI fix jobs raised against them, across SAST, AI-powered detection, Supply Chain, and Secrets. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/multimodal.mdx b/docs/api-reference/v2/services/multimodal.mdx index ac8e63a91e..bad193dc7a 100644 --- a/docs/api-reference/v2/services/multimodal.mdx +++ b/docs/api-reference/v2/services/multimodal.mdx @@ -3,12 +3,14 @@ title: "Multimodal" description: "Assistant and memories." --- +import MultimodalEndpoints from "/snippets/api/v2/multimodal-endpoints.mdx" + These endpoints cover Semgrep Assistant and the memories that shape its behavior for your deployment. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/policies.mdx b/docs/api-reference/v2/services/policies.mdx index 75d2249d5b..6536933de6 100644 --- a/docs/api-reference/v2/services/policies.mdx +++ b/docs/api-reference/v2/services/policies.mdx @@ -3,12 +3,14 @@ title: "Policies" description: "Detection and remediation policies, rules, dry runs, automations, and notification rules." --- +import PoliciesEndpoints from "/snippets/api/v2/policies-endpoints.mdx" + A policy decides which rules run and what happens when they match — whether a finding blocks a pull request, gets ignored, or raises a ticket. These endpoints manage detection and remediation policies and the rules inside them, let you dry-run a change before committing to it, and configure the automations and notifications that fire off the result. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/projects.mdx b/docs/api-reference/v2/services/projects.mdx index 9229835080..cb302e5433 100644 --- a/docs/api-reference/v2/services/projects.mdx +++ b/docs/api-reference/v2/services/projects.mdx @@ -3,12 +3,14 @@ title: "Projects" description: "Projects, branches, project tags, syncs, and per-project scan settings." --- +import ProjectsEndpoints from "/snippets/api/v2/projects-endpoints.mdx" + A project is what a scan sees: one codebase Semgrep is configured to analyze. These endpoints manage projects and their branches, the tags used to organize them, the syncs that keep them aligned with your source code manager, and the per-project scan and resolution settings. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/scans.mdx b/docs/api-reference/v2/services/scans.mdx index 3991938680..f122a9ebf2 100644 --- a/docs/api-reference/v2/services/scans.mdx +++ b/docs/api-reference/v2/services/scans.mdx @@ -3,12 +3,14 @@ title: "Scans" description: "Scan runs and retries." --- +import ScansEndpoints from "/snippets/api/v2/scans-endpoints.mdx" + A scan is one execution of Semgrep against a project. These endpoints report on scan runs and retry the ones that failed. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/supply-chain.mdx b/docs/api-reference/v2/services/supply-chain.mdx index 17f7df65f0..35ec910b0c 100644 --- a/docs/api-reference/v2/services/supply-chain.mdx +++ b/docs/api-reference/v2/services/supply-chain.mdx @@ -3,12 +3,14 @@ title: "Supply Chain" description: "Dependencies, advisories, and SBOMs." --- +import SupplyChainEndpoints from "/snippets/api/v2/supply-chain-endpoints.mdx" + Supply Chain covers what your code depends on. These endpoints list the dependencies Semgrep found across your projects, the advisories that affect them, and generate SBOM exports. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/tasks.mdx b/docs/api-reference/v2/services/tasks.mdx index cdc6b8e40e..0fd2c88e38 100644 --- a/docs/api-reference/v2/services/tasks.mdx +++ b/docs/api-reference/v2/services/tasks.mdx @@ -3,12 +3,14 @@ title: "Tasks" description: "Async result polling." --- +import TasksEndpoints from "/snippets/api/v2/tasks-endpoints.mdx" + Some operations are too slow to answer inline — exports, SBOM generation, and syncs all return a task handle instead of a result. These endpoints redeem that handle once the work finishes. Tasks is deliberately its own service rather than living under any one of the services that produce them, so the same polling call works whatever started the work. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/workflow-issues.mdx b/docs/api-reference/v2/services/workflow-issues.mdx index 3c0c72218b..64afb1e2b8 100644 --- a/docs/api-reference/v2/services/workflow-issues.mdx +++ b/docs/api-reference/v2/services/workflow-issues.mdx @@ -3,12 +3,14 @@ title: "Workflow issues" description: "Issues, counts, and triage for Agentic Workflows." --- +import WorkflowIssuesEndpoints from "/snippets/api/v2/workflow-issues-endpoints.mdx" + Workflow issues are the issues produced by Agentic Workflows, with their own listing, counting, and triage endpoints. This service is intended to eventually replace [Issues](/api-reference/v2/services/issues) for every issue type; for now it serves Agentic Workflows only. ## Endpoints -This service has no published endpoints yet. Endpoints move here one at a time, -and each one appears in the navigation as soon as it is published. Every move is -recorded in the [changelog](/api-reference/v2/Changelog), and anything it -supersedes is deprecated under the -[deprecation policy](/api-reference/v2/Deprecation-Policy). + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/docs.json b/docs/docs.json index 5b8f1705a5..022db7479e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -651,14 +651,20 @@ "pages": [ { "group": "AI Fix Jobs", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/agent/deployments/{deploymentId}/issues/{issueId}/fix_jobs" ] }, { "group": "AI tasks", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/ai/{deploymentId}/combined_task", "POST /api/ai/pattern_fix", @@ -667,14 +673,20 @@ }, { "group": "Autofix", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/v1/deployments/{deploymentId}/issues/{issueId}/autofix" ] }, { "group": "Automations", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/notifications/deployments/{deploymentId}/automations", "GET /api/notifications/deployments/{deploymentId}/automations", @@ -685,14 +697,20 @@ }, { "group": "Autotriage Feedback", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/ai/deployments/{deploymentId}/autotriage_feedback" ] }, { "group": "Deployment", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/agent/deployments/{deploymentId}/users", "GET /api/agent/deployments/{deploymentId}/users", @@ -714,7 +732,10 @@ }, { "group": "Deployment Products", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "PUT /api/agent/deployments/{deploymentId}/products", "GET /api/agent/deployments/{deploymentId}/products", @@ -723,14 +744,20 @@ }, { "group": "Deployment SSO Providers", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/auth/sso/deployments/{deploymentId}/providers" ] }, { "group": "Deployment Tags", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "PUT /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", "DELETE /api/agent/deployments/{deploymentId}/tags/{repositoryTag}", @@ -741,7 +768,10 @@ }, { "group": "Deployments", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "root": "api-reference/v2/services/deployments", "pages": [ "GET /api/v2/deployments" @@ -749,14 +779,20 @@ }, { "group": "Editor", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/run" ] }, { "group": "External Ticketing", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/create_tickets", "DELETE /api/notifications/deployments/{deploymentId}/ticketing/v2/{ticketingInstanceId}/tickets/{nangoExternalTicketId}", @@ -767,14 +803,20 @@ }, { "group": "Feature Rollouts", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/agent/features" ] }, { "group": "Global Ignores", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/agent/deployments/{deploymentId}/ignores", "DELETE /api/agent/deployments/{deploymentId}/ignores", @@ -784,14 +826,20 @@ }, { "group": "Infrastructure Configurations", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/infra-config/bootstrap-sms-vpc" ] }, { "group": "Issues", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "root": "api-reference/v2/services/issues", "pages": [ "PATCH /api/agent/deployments/{deploymentId}/findings/v2", @@ -806,7 +854,10 @@ }, { "group": "Issues (Semgrep Agentic Workflows)", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/issues/{deploymentId}/counts", "GET /api/issues/{deploymentId}/issue/{issueId}/activity", @@ -817,7 +868,10 @@ }, { "group": "Managed Scan Settings", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/agent/deployments/{deploymentId}/managed_scan_settings", "GET /api/agent/deployments/{deploymentId}/managed_scan_settings", @@ -827,7 +881,10 @@ }, { "group": "Memories", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/ai/deployments/{deploymentId}/memories", "POST /api/ai/deployments/{deploymentId}/memories/triage", @@ -842,7 +899,10 @@ }, { "group": "Notification Rules", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/agent/deployments/{deploymentId}/actions", "DELETE /api/agent/deployments/{deploymentId}/actions/{notificationRuleId}", @@ -853,7 +913,10 @@ }, { "group": "Notification Webhooks", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/notification_webhooks/deployments/{deploymentId}/webhooks", "GET /api/notification_webhooks/deployments/{deploymentId}/webhooks", @@ -865,7 +928,10 @@ }, { "group": "Notifications", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/notifications/in-app/deployments/{deploymentId}", "PUT /api/notifications/in-app/deployments/{deploymentId}/mark_as_seen" @@ -873,7 +939,10 @@ }, { "group": "Onboarding Checklist", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/onboarding/deployments/{deploymentId}/checklist", "PATCH /api/onboarding/deployments/{deploymentId}/checklist", @@ -882,7 +951,10 @@ }, { "group": "Policies", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "root": "api-reference/v2/services/policies", "pages": [ "GET /api/policies/v1/deployments/{deploymentId}", @@ -892,7 +964,10 @@ }, { "group": "Policies V2", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "PUT /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", "GET /api/policies/v2/deployments/{deploymentId}/detection-policy/{product}", @@ -906,7 +981,10 @@ }, { "group": "Projects", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "root": "api-reference/v2/services/projects", "pages": [ "POST /api/agent/deployments/{deploymentId}/repos/_secret", @@ -926,7 +1004,10 @@ }, { "group": "Projects - Managed Scan Settings", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/sms/v2/deployments/{deploymentId}/project_settings", "GET /api/sms/v2/deployments/{deploymentId}/projects/{projectId}/settings" @@ -934,7 +1015,10 @@ }, { "group": "Reporting", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/reporting/{deploymentId}/reports/backlog/by-product", "POST /api/reporting/{deploymentId}/reports/backlog/by-rule", @@ -957,7 +1041,10 @@ }, { "group": "Review Comment Product Content", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/deployments/{deploymentId}/scm_comment_product_content", "GET /api/deployments/{deploymentId}/scm_comment_product_content", @@ -967,7 +1054,10 @@ }, { "group": "Ruleboards", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/agent/deployments/{deploymentId}/ruleboards", "GET /api/agent/deployments/{deploymentId}/ruleboards", @@ -981,7 +1071,10 @@ }, { "group": "Scans", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "root": "api-reference/v2/services/scans", "pages": [ "GET /api/v2/deployments/{deploymentId}/scans/{scanId}", @@ -991,7 +1084,10 @@ }, { "group": "Semgrep Agentic Workflows", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/findings", "GET /api/workflows/v1/deployments/{deploymentId}/jobs/id/{jobId}/overview", @@ -1002,7 +1098,10 @@ }, { "group": "Slack", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "DELETE /api/agent/deployments/{deploymentId}/slack/installations", "GET /api/agent/deployments/{deploymentId}/slack/installations", @@ -1014,7 +1113,10 @@ }, { "group": "Source Code Management (SCM) apps", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/scm/deployments/{deploymentId}/scm_apps/{scmAppId}/add_ado_project", "PUT /api/scm/ado_app_install", @@ -1030,7 +1132,10 @@ }, { "group": "Source Code Management (SCM) configurations", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/scm/deployments/{deploymentId}/configs/{configId}/check", "POST /api/scm/deployments/{deploymentId}/configs", @@ -1043,7 +1148,10 @@ }, { "group": "Source Code Management (SCM) webhooks", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/scm/deployments/{deploymentId}/subscriptions/{configId}", "PUT /api/scm/deployments/{deploymentId}/subscriptions/{configId}", @@ -1053,7 +1161,10 @@ }, { "group": "Supply Chain", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "root": "api-reference/v2/services/supply-chain", "pages": [ "POST /api/sca/deployments/{deploymentId}/sbom_async", @@ -1066,14 +1177,20 @@ }, { "group": "Supply Chain - Managed Scans dependency resolution configurations", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/agent/deployments/{deploymentId}/projects/{projectId}/resolution_configs" ] }, { "group": "Supply Chain - Managed Scans package manager configurations", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", "GET /api/agent/deployments/{deploymentId}/packagemanagerauthconfigs", @@ -1083,7 +1200,10 @@ }, { "group": "Support", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/support/cases", "GET /api/support/cases/{orgid}" @@ -1091,7 +1211,10 @@ }, { "group": "Surveys", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/survey/name/{surveyName}", "POST /api/survey/submit" @@ -1099,7 +1222,10 @@ }, { "group": "Tasks", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "root": "api-reference/v2/services/tasks", "pages": [ "GET /api/tasks/v2/{taskTokenJwt}" @@ -1107,7 +1233,10 @@ }, { "group": "Teams", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/permissions/v2/deployments/{deploymentId}/status", "POST /api/permissions/v2/deployments/{deploymentId}/teams", @@ -1122,14 +1251,20 @@ }, { "group": "Tokens", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "DELETE /api/tokens/v1/deployments/{deploymentId}/tokens" ] }, { "group": "Users", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/auth/users/current", "GET /api/auth/users/current/settings", @@ -1141,14 +1276,20 @@ }, { "group": "Versions", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/agent/versions/{deploymentId}/project-info/{product}" ] }, { "group": "Wiz Credentials", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "POST /api/notifications/deployments/{deploymentId}/wiz", "GET /api/notifications/deployments/{deploymentId}/wiz", @@ -1160,7 +1301,10 @@ }, { "group": "Other", - "openapi": "public_v2.openapi.yaml", + "openapi": { + "source": "public_v2.openapi.yaml", + "directory": "api-reference/v2" + }, "pages": [ "GET /api/health-check", "GET /api/readiness-check", diff --git a/docs/snippets/api/v2/agentic-workflows-endpoints.mdx b/docs/snippets/api/v2/agentic-workflows-endpoints.mdx new file mode 100644 index 0000000000..141fc3c6bb --- /dev/null +++ b/docs/snippets/api/v2/agentic-workflows-endpoints.mdx @@ -0,0 +1,5 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +No endpoints are published in this service yet. They appear here as each one +is published, and every change is recorded in the +[changelog](/api-reference/v2/Changelog). diff --git a/docs/snippets/api/v2/analytics-endpoints.mdx b/docs/snippets/api/v2/analytics-endpoints.mdx new file mode 100644 index 0000000000..141fc3c6bb --- /dev/null +++ b/docs/snippets/api/v2/analytics-endpoints.mdx @@ -0,0 +1,5 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +No endpoints are published in this service yet. They appear here as each one +is published, and every change is recorded in the +[changelog](/api-reference/v2/Changelog). diff --git a/docs/snippets/api/v2/deployments-endpoints.mdx b/docs/snippets/api/v2/deployments-endpoints.mdx new file mode 100644 index 0000000000..ce5516a168 --- /dev/null +++ b/docs/snippets/api/v2/deployments-endpoints.mdx @@ -0,0 +1,5 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +| Endpoint | Description | +| --- | --- | +| GET [List deployments](/api-reference/v2/deploymentsservice/list-deployments) | Request the deployments your auth can access. | diff --git a/docs/snippets/api/v2/integrations-endpoints.mdx b/docs/snippets/api/v2/integrations-endpoints.mdx new file mode 100644 index 0000000000..141fc3c6bb --- /dev/null +++ b/docs/snippets/api/v2/integrations-endpoints.mdx @@ -0,0 +1,5 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +No endpoints are published in this service yet. They appear here as each one +is published, and every change is recorded in the +[changelog](/api-reference/v2/Changelog). diff --git a/docs/snippets/api/v2/issues-endpoints.mdx b/docs/snippets/api/v2/issues-endpoints.mdx new file mode 100644 index 0000000000..8583beab3d --- /dev/null +++ b/docs/snippets/api/v2/issues-endpoints.mdx @@ -0,0 +1,12 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +| Endpoint | Description | +| --- | --- | +| PATCH [Bulk update issues](/api-reference/v2/issuesservice/bulk-update-issues) | Bulk triage issues that match specified filters | +| POST [Export issues](/api-reference/v2/issuesservice/export-issues) | Asynchronously export issues matching filters to a file | +| GET [Get issue](/api-reference/v2/issuesservice/get-issue) | Get a single issue by ID with details | +| GET [Get issue code snippets](/api-reference/v2/issuesservice/get-issue-code-snippets) | Get code snippets for an issue. | +| POST [Get issue counts](/api-reference/v2/issuesservice/get-issue-counts) | Get counts of issues by status with filtering | +| POST [Get issue filters](/api-reference/v2/issuesservice/get-issue-filters) | Get available filter options for issues | +| POST [List issue groups](/api-reference/v2/issuesservice/list-issue-groups) | Search and group issues by rule with filtering and pagination | +| POST [List issues](/api-reference/v2/issuesservice/list-issues) | List an organization's recent issues with filtering, sorting, and pagination | diff --git a/docs/snippets/api/v2/multimodal-endpoints.mdx b/docs/snippets/api/v2/multimodal-endpoints.mdx new file mode 100644 index 0000000000..141fc3c6bb --- /dev/null +++ b/docs/snippets/api/v2/multimodal-endpoints.mdx @@ -0,0 +1,5 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +No endpoints are published in this service yet. They appear here as each one +is published, and every change is recorded in the +[changelog](/api-reference/v2/Changelog). diff --git a/docs/snippets/api/v2/policies-endpoints.mdx b/docs/snippets/api/v2/policies-endpoints.mdx new file mode 100644 index 0000000000..a8e6f45651 --- /dev/null +++ b/docs/snippets/api/v2/policies-endpoints.mdx @@ -0,0 +1,7 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +| Endpoint | Description | +| --- | --- | +| GET [List policies](/api-reference/v2/policiesservice/list-policies) | List all policies for a given deployment. | +| GET [List policy rules](/api-reference/v2/policiesservice/list-policy-rules) | List the rules for a given policy. | +| PUT [Update policy rule](/api-reference/v2/policiesservice/update-policy-rule) | Update a specific rule within a policy. | diff --git a/docs/snippets/api/v2/projects-endpoints.mdx b/docs/snippets/api/v2/projects-endpoints.mdx new file mode 100644 index 0000000000..871b2210c0 --- /dev/null +++ b/docs/snippets/api/v2/projects-endpoints.mdx @@ -0,0 +1,17 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +| Endpoint | Description | +| --- | --- | +| POST [Add Semgrep CI secrets to GitHub repositories](/api-reference/v2/projectsservice/add-semgrep-ci-secrets-to-github-repositories) | Generates and adds a secret for the "semgrep-ci" GitHub Action to the GitHub Repositories that are associated with any Semgrep Projects that match the given filters. | +| PATCH [Bulk edit projects by filter](/api-reference/v2/projectsservice/bulk-edit-projects-by-filter) | Applies a change to many projects that match the given filters. | +| PATCH [Bulk edit selected projects](/api-reference/v2/projectsservice/bulk-edit-selected-projects) | Applies changes to multiple specified projects at once. | +| DELETE [Delete project](/api-reference/v2/projectsservice/delete-project) | Delete a project by its ID. | +| GET [Get project](/api-reference/v2/projectsservice/get-project) | Get a project by its ID. | +| GET [Get branch](/api-reference/v2/projectsservice/get-branch) | Get a branch by its ID. | +| POST [Get project count](/api-reference/v2/projectsservice/get-project-count) | Get the number of projects in a deployment. | +| GET [List project IDs by tag](/api-reference/v2/projectsservice/list-project-ids-by-tag) | Gets a mapping from tag ID to related project IDs. | +| POST [List projects](/api-reference/v2/projectsservice/list-projects) | List all projects based on provided filters. | +| POST [Provision Semgrep CI GitHub Actions](/api-reference/v2/projectsservice/provision-semgrep-ci-github-actions) | Adds a "semgrep-ci" GitHub Action to the GitHub Repositories that are associated with any Semgrep Projects that match the given filters. | +| POST [Sync all projects to SCM](/api-reference/v2/projectsservice/sync-all-projects-to-scm) | Schedules a job to sync all projects in a deployment to their SCM. | +| POST [Sync project to SCM](/api-reference/v2/projectsservice/sync-project-to-scm) | Schedules a job to sync a project with its SCM. | +| PATCH [Update project](/api-reference/v2/projectsservice/update-project) | Use a "Merge Patch" to update a project's name or SMS settings. | diff --git a/docs/snippets/api/v2/scans-endpoints.mdx b/docs/snippets/api/v2/scans-endpoints.mdx new file mode 100644 index 0000000000..2aaab4c150 --- /dev/null +++ b/docs/snippets/api/v2/scans-endpoints.mdx @@ -0,0 +1,7 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +| Endpoint | Description | +| --- | --- | +| GET [Get scan](/api-reference/v2/scansservice/get-scan) | Get a scan by ID. | +| POST [List scans](/api-reference/v2/scansservice/list-scans) | List the scans associated with a particular project from the past 30 days. | +| POST [Retry failed Managed Scans](/api-reference/v2/scansservice/retry-failed-managed-scans) | Retry one or more failed Semgrep Managed Scans (SMS). | diff --git a/docs/snippets/api/v2/supply-chain-endpoints.mdx b/docs/snippets/api/v2/supply-chain-endpoints.mdx new file mode 100644 index 0000000000..1f16013aad --- /dev/null +++ b/docs/snippets/api/v2/supply-chain-endpoints.mdx @@ -0,0 +1,10 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +| Endpoint | Description | +| --- | --- | +| POST [Generate a project's SBOM](/api-reference/v2/supplychain2service/generate-a-projects-sbom) | Starts a job to asynchronously generate a Software Bill of Materials (SBOM) for a given project. | +| GET [List dependencies](/api-reference/v2/supplychain2service/list-dependencies) | Returns all (or filtered) dependencies for a deployment using pagination | +| POST [List dependencies](/api-reference/v2/supplychain2service/list-dependencies) | Returns all (or filtered) dependencies for a deployment using pagination | +| POST [List findings for advisory](/api-reference/v2/supplychain2service/list-findings-for-advisory) | Returns a summary of findings grouped by project and branch for a specific advisory using pagination | +| POST [List lockfiles with dependencies in a given repository](/api-reference/v2/supplychain2service/list-lockfiles-with-dependencies-in-a-given-repository) | Returns all (or filtered) lockfiles that contain dependencies within the specified repository | +| POST [List repositories with dependencies](/api-reference/v2/supplychain2service/list-repositories-with-dependencies) | Returns any repositories which contain dependencies that match the given filter for a deployment | diff --git a/docs/snippets/api/v2/tasks-endpoints.mdx b/docs/snippets/api/v2/tasks-endpoints.mdx new file mode 100644 index 0000000000..d1e7f9a23a --- /dev/null +++ b/docs/snippets/api/v2/tasks-endpoints.mdx @@ -0,0 +1,5 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +| Endpoint | Description | +| --- | --- | +| GET [Get task result](/api-reference/v2/tasksservice/get-task-result) | Returns the status of a task or task group, and if completed, the result. | diff --git a/docs/snippets/api/v2/workflow-issues-endpoints.mdx b/docs/snippets/api/v2/workflow-issues-endpoints.mdx new file mode 100644 index 0000000000..141fc3c6bb --- /dev/null +++ b/docs/snippets/api/v2/workflow-issues-endpoints.mdx @@ -0,0 +1,5 @@ +{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} + +No endpoints are published in this service yet. They appear here as each one +is published, and every change is recorded in the +[changelog](/api-reference/v2/Changelog). diff --git a/scripts/generate_api_nav.py b/scripts/generate_api_nav.py index 77f2742776..3faddda800 100644 --- a/scripts/generate_api_nav.py +++ b/scripts/generate_api_nav.py @@ -47,7 +47,7 @@ def service_groups(spec: dict, docs_root: pathlib.Path, spec_name: str, - landing_dir: str) -> list[dict]: + landing_dir: str, directory: Optional[str] = None) -> list[dict]: """One nav group per tag, each with its endpoints and its landing page.""" display = {} for tag in spec.get("tags") or []: @@ -69,7 +69,14 @@ def service_groups(spec: dict, docs_root: pathlib.Path, spec_name: str, groups = [] for tag, pages in endpoints.items(): label = display.get(tag, tag) - group: dict = {"group": label, "openapi": spec_name} + # The object form carries `directory`, which is what keeps the + # generated endpoint pages under /api-reference//. Without it + # Mintlify serves them from /api-reference// instead, silently + # moving every endpoint URL and colliding v1 with v2. + source: object = ( + {"source": spec_name, "directory": directory} if directory else spec_name + ) + group: dict = {"group": label, "openapi": source} # slug the display name the way the landing pages are named slug = re.sub(r"[^a-z0-9]+", "-", label.lower()).strip("-") landing = f"{landing_dir}/{slug}" @@ -109,6 +116,11 @@ def main(argv: Optional[list[str]] = None) -> int: "--landing-dir", required=True, help="docs-relative dir holding the service pages, e.g. api-reference/v2/services", ) + parser.add_argument( + "--directory", + help="dir the generated endpoint pages live under, e.g. api-reference/v2; " + "omit and Mintlify serves them from /api-reference// instead", + ) parser.add_argument( "--parent", default="Services", help="name of the collapsible group the service groups nest inside", @@ -124,7 +136,8 @@ def main(argv: Optional[list[str]] = None) -> int: spec = yaml.safe_load(pathlib.Path(args.spec).read_text()) groups = service_groups( - spec, docs_root, pathlib.Path(args.spec).name, args.landing_dir + spec, docs_root, pathlib.Path(args.spec).name, args.landing_dir, + args.directory, ) if not groups: print(f"{args.spec}: no tagged operations, nav left alone") diff --git a/scripts/generate_service_endpoints.py b/scripts/generate_service_endpoints.py new file mode 100644 index 0000000000..1dc0830901 --- /dev/null +++ b/scripts/generate_service_endpoints.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.9" +# dependencies = ["pyyaml"] +# +# [tool.uv] +# exclude-newer = "1 week" +# /// +# +# Generate the endpoint table each service landing page shows. +# +# Why this exists: a service page needs to list the endpoints in that service, +# linked to their reference pages, and that list changes every time an endpoint +# is published, renamed or regrouped. Hand-maintaining it guarantees it goes +# stale, and a hardcoded "no endpoints yet" placeholder is wrong the moment the +# first one lands. +# +# One snippet per service, imported by the landing page, so the prose stays +# hand-written and the table stays correct. Rendered from the same spec the +# reference pages are generated from, in the sync workflow, so the two cannot +# disagree. +# +# Endpoint URLs come from generate_api_changelog rather than a second copy of +# the slug rules -- those were reverse-engineered against the rendered site and +# two copies would drift into broken links. + +from __future__ import annotations + +import argparse +import pathlib +import re +import sys +from typing import Optional + +import yaml + +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) +from generate_api_changelog import ( # noqa: E402 + METHOD_BADGE_COLORS, + endpoint_urls, + escape_mdx, + mintlify_slug, +) + +HTTP_METHODS = ("get", "post", "put", "patch", "delete", "options", "head", "trace") + +HEADER = """{{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */}} + +{body} +""" + +EMPTY = ( + "No endpoints are published in this service yet. They appear here as each one\n" + "is published, and every change is recorded in the\n" + "[changelog]({changelog})." +) + + +def first_sentence(text: str) -> str: + """The lead sentence of a description, for a one-line table cell. + + Descriptions run to several paragraphs and carry Markdown of their own; a + landing page wants the gist, with the endpoint page one click away. + """ + text = " ".join((text or "").split()) + if not text: + return "" + # ". " is not a sentence end inside e.g. "v1. 2" or an abbreviation we care + # about here, so accept the first period followed by a space and a capital. + m = re.search(r"\.(?=\s+[A-Z(])", text) + out = text[: m.start() + 1] if m else text + return out if out.endswith(".") or not m else out + + +def service_tables(spec: dict, link_base: str) -> dict[str, str]: + """display name -> the MDX table of that service's endpoints.""" + display = { + t["name"]: (t.get("x-displayName") or t["name"]) for t in (spec.get("tags") or []) + } + urls = endpoint_urls(spec, link_base) + + rows: dict[str, list[str]] = {} + for path, item in (spec.get("paths") or {}).items(): + if not isinstance(item, dict): + continue + for method, op in item.items(): + if method not in HTTP_METHODS or not isinstance(op, dict): + continue + tags = op.get("tags") or [] + if not tags: + continue + label = display.get(tags[0], tags[0]) + verb = method.upper() + colour = METHOD_BADGE_COLORS.get(verb, "gray") + title = op.get("summary") or f"{verb} {path}" + url = urls.get((verb, path)) + linked = f"[{escape_mdx(title)}]({url})" if url else escape_mdx(title) + desc = escape_mdx(first_sentence(op.get("description") or "")) + badge = f'{verb}' + rows.setdefault(label, []).append(f"| {badge} {linked} | {desc} |") + + out = {} + for label, body in rows.items(): + out[label] = "\n".join( + ["| Endpoint | Description |", "| --- | --- |", *body] + ) + return out + + +def main(argv: Optional[list[str]] = None) -> int: + parser = argparse.ArgumentParser( + description="Generate the endpoint table for each service landing page." + ) + parser.add_argument("spec", help="path to the OpenAPI spec") + parser.add_argument("out_dir", help="directory to write the snippets into") + parser.add_argument("--link-base", required=True, help="e.g. /api-reference/v2") + parser.add_argument( + "--changelog", required=True, help="changelog page path, for the empty state" + ) + parser.add_argument( + "--services-dir", required=True, + help="landing-page dir; one snippet is written per page found there", + ) + args = parser.parse_args(argv) + + spec = yaml.safe_load(pathlib.Path(args.spec).read_text()) + tables = service_tables(spec, args.link_base) + + # One snippet per landing page, not per spec tag. A tag with no landing + # page needs no snippet; a landing page whose service has no endpoints yet + # gets the empty state, which is generated rather than written into the + # page -- that is the thing that would otherwise go stale. + by_slug = {mintlify_slug(label): label for label in tables} + wanted = { + page.stem: by_slug.get(page.stem, page.stem) + for page in sorted(pathlib.Path(args.services_dir).glob("*.mdx")) + } + + out_dir = pathlib.Path(args.out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + written = empty = 0 + for slug, label in sorted(wanted.items()): + body = tables.get(label) or EMPTY.format(changelog=args.changelog) + if label not in tables: + empty += 1 + path = out_dir / f"{slug}-endpoints.mdx" + content = HEADER.format(body=body) + if path.exists() and path.read_text() == content: + continue + path.write_text(content) + written += 1 + print(f"{out_dir}: {len(wanted)} snippets ({empty} with no endpoints), " + f"{written} written") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/tests/test_generate_service_endpoints.py b/scripts/tests/test_generate_service_endpoints.py new file mode 100644 index 0000000000..e614c44b60 --- /dev/null +++ b/scripts/tests/test_generate_service_endpoints.py @@ -0,0 +1,126 @@ +# Tests for scripts/generate_service_endpoints.py +# +# Run with: +# uv run --with pyyaml --with pytest pytest scripts/tests + +from __future__ import annotations + +import pytest + +import generate_service_endpoints as gse + +LINK_BASE = "/api-reference/v2" + +SPEC = { + "tags": [{"name": "ScansService", "x-displayName": "Scans"}], + "paths": { + "/api/v2/scans/{scanId}": { + "get": { + "tags": ["Scans"], "summary": "Get scan", + "description": "Get a scan by ID. Only scans from the past 30 days.", + } + }, + "/api/v2/scans/retry": { + "post": {"tags": ["Scans"], "summary": "Retry failed scans", "description": ""} + }, + }, +} + + +def test_table_links_each_endpoint_to_its_reference_page(): + tables = gse.service_tables(SPEC, LINK_BASE) + + assert "Scans" in tables + assert "[Get scan](/api-reference/v2/scans/get-scan)" in tables["Scans"] + assert "[Retry failed scans](/api-reference/v2/scans/retry-failed-scans)" in tables["Scans"] + + +def test_table_carries_a_method_badge_coloured_by_verb(): + table = gse.service_tables(SPEC, LINK_BASE)["Scans"] + + assert 'GET' in table + assert 'POST' in table + + +def test_table_shows_only_the_lead_sentence_of_the_description(): + table = gse.service_tables(SPEC, LINK_BASE)["Scans"] + + assert "Get a scan by ID." in table + assert "past 30 days" not in table # the endpoint page is one click away + + +@pytest.mark.parametrize( + "raw,expected", + [ + ("One. Two.", "One."), + ("Retry failed scans (SMS). Only some qualify.", "Retry failed scans (SMS)."), + ("No trailing period", "No trailing period"), + ("Version v1. 2 is fine", "Version v1. 2 is fine"), # not a sentence break + ("", ""), + (" collapses\n whitespace ", "collapses whitespace"), + ], +) +def test_first_sentence(raw, expected): + assert gse.first_sentence(raw) == expected + + +def test_group_label_uses_the_display_name_not_the_tag(): + tables = gse.service_tables(SPEC, LINK_BASE) + + assert "Scans" in tables + assert "ScansService" not in tables + + +def test_untagged_and_non_method_keys_are_skipped(): + spec = { + "tags": [], + "paths": { + "/api/x": { + "get": {"tags": ["A"], "summary": "Ay"}, + "parameters": [{"name": "q"}], + "put": {"summary": "no tags"}, + } + }, + } + + tables = gse.service_tables(spec, LINK_BASE) + + assert list(tables) == ["A"] + assert tables["A"].count("|") == tables["A"].count("|") # one data row + assert "no tags" not in tables["A"] + + +def test_endpoint_without_a_summary_falls_back_to_method_and_path(): + spec = {"tags": [], "paths": {"/api/x": {"get": {"tags": ["A"]}}}} + + table = gse.service_tables(spec, LINK_BASE)["A"] + + assert "GET /api/x" in table + + +def test_mdx_hazards_in_a_summary_are_escaped(): + spec = { + "tags": [], + "paths": {"/api/x": {"get": {"tags": ["A"], "summary": "Get {id} "}}}, + } + + table = gse.service_tables(spec, LINK_BASE)["A"] + + # braces open a JSX expression and angle brackets a tag; both must not + # reach the renderer raw + assert "{id}" not in table + assert "" not in table + + +def test_service_with_no_endpoints_gets_no_table(): + tables = gse.service_tables({"tags": [], "paths": {}}, LINK_BASE) + + assert tables == {} + + +def test_empty_state_names_the_changelog_rather_than_hardcoding_a_claim(): + """A service with no endpoints must still say something accurate.""" + text = gse.EMPTY.format(changelog="/api-reference/v2/Changelog") + + assert "/api-reference/v2/Changelog" in text + assert "no endpoints are published in this service yet" in text.lower() From a945910d4a08d074a27303e0f4bdac3762ca62b1 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Tue, 8 Sep 2026 09:44:02 -0700 Subject: [PATCH 25/27] style(api-reference): trim the endpoint sidebar 38 of 43 entries under a service carry a maturity pill. In the narrow nav rail the pill wraps onto its own line, roughly doubling each entry's height and making a service look far larger than it is. Maturity is already stated on the endpoint page and defined in the deprecation policy, so hide it in the rail -- but keep `deprecated`, which is rare and worth interrupting for. Hides the pill's wrapper rather than the pill: the wrapper carries `h-[1lh]` inside a flex-wrap row, so hiding only the pill left a line-tall empty box that wrapped below any two-line title, showing up as a stray gap under exactly those entries. Also turns off Mintlify's `hyphens: auto` on nav titles, which was breaking words mid-syllable -- "Preview a detection poli-cy apply". Co-Authored-By: Claude Opus 5 (1M context) --- docs/styles.css | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/styles.css b/docs/styles.css index 4b56f52b94..570ef91572 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -25,6 +25,29 @@ h1 { white-space: nowrap; } +/* API reference sidebar density. + 38 of 43 entries under a service carry a maturity pill, and in the narrow + nav rail it wraps onto its own line -- roughly doubling each entry's height + and making a service look far larger than it is. Maturity is stated on the + endpoint page itself and defined in the deprecation policy, so the rail does + not need to repeat it. The `deprecated` badge is deliberately NOT hidden: + there are only a few, and that one is worth interrupting for. + + Hide the pill's WRAPPER, not just the pill: the wrapper carries h-[1lh] and + sits in a flex-wrap row, so hiding only the pill leaves a one-line-tall + empty box that wraps below any title long enough to take two lines -- + showing up as a stray gap under exactly those entries. */ +#sidebar div:has(> .nav-tag-pill) { + display: none !important; +} + +/* Mintlify sets `hyphens: auto` on nav titles, which breaks words mid-syllable + in the narrow rail -- "Preview a detection poli-cy apply". */ +#sidebar .hyphens-auto, +#sidebar .sidebar-title { + hyphens: none !important; +} + /* Changelog endpoint column: a change to a shared type lists several endpoint pills in one cell, stacked with
. Without a little leading they read as one solid block. Scoped to the generated table's wrapper class so it cannot From 041f108a7b4f5d71a14535715fb16ce51c9afbd5 Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Tue, 8 Sep 2026 11:03:44 -0700 Subject: [PATCH 26/27] fix(api-reference): scope the nav rebuild to v2 The step ran generate_api_nav.py over v1 as well as v2, which would have regrouped the v1 dropdown from its flat "Endpoints" list into per-service groups on the next sync. Nothing asked for that: v1 has no service landing pages, so the groups would open an arbitrary endpoint, and regrouping moves every v1 endpoint's page URL -- Mintlify builds it from the first tag. It also meant the committed docs.json did not match what the workflow produced, so the next run would have opened an unreviewed nav change. With the v1 call gone, running the step reproduces the committed file exactly. The redirect step still covers v1. It is a no-op while v1 keeps its flat group, and it is what would catch the URL moves if v1 is ever regrouped deliberately. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/update-openapi-specs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-openapi-specs.yml b/.github/workflows/update-openapi-specs.yml index 68e9b2cdd9..32a24e1c9a 100644 --- a/.github/workflows/update-openapi-specs.yml +++ b/.github/workflows/update-openapi-specs.yml @@ -120,11 +120,11 @@ jobs: # carry a `root`, so the endpoint list has to be enumerated -- which is # why it is generated rather than hand-maintained. Runs after the sort # step, whose order it inherits. + # v2 only. v1 has no service landing pages, and regrouping it would + # move every v1 endpoint's URL for no benefit -- it is legacy and its + # flat "Endpoints" group is fine. - name: Rebuild the API navigation run: | - uv run scripts/generate_api_nav.py docs/public_v1.openapi.yaml \ - docs/docs.json --dropdown v1 --landing-dir api-reference/v1/services \ - --directory api-reference/v1 uv run scripts/generate_api_nav.py docs/public_v2.openapi.yaml \ docs/docs.json --dropdown v2 --landing-dir api-reference/v2/services \ --directory api-reference/v2 From 54b2336961fe005650b30a1c26adfbed009458be Mon Sep 17 00:00:00 2001 From: Aaron Acosta Date: Mon, 21 Sep 2026 16:49:42 -0700 Subject: [PATCH 27/27] feat(api-reference): merge Scans and Agentic Workflows into Jobs A job is the parent category of both a scan and an Agentic Workflows job, so the two land as one service rather than two that would have to be merged later. Eleven service pages instead of twelve. The snippet and the nav entry are regenerated output: jobs-endpoints.mdx shows the empty state until the spec carries a Jobs tag, and the Scans nav group loses its root now that no page backs it. Pairs with the semgrep-app directory merge on APPEX-1609/new-api-v2-directory-structure. Co-Authored-By: Claude Opus 5 (1M context) --- .../v2/services/agentic-workflows.mdx | 16 ---------------- docs/api-reference/v2/services/jobs.mdx | 16 ++++++++++++++++ docs/api-reference/v2/services/scans.mdx | 16 ---------------- docs/docs.json | 5 ++--- ...orkflows-endpoints.mdx => jobs-endpoints.mdx} | 0 docs/snippets/api/v2/scans-endpoints.mdx | 7 ------- 6 files changed, 18 insertions(+), 42 deletions(-) delete mode 100644 docs/api-reference/v2/services/agentic-workflows.mdx create mode 100644 docs/api-reference/v2/services/jobs.mdx delete mode 100644 docs/api-reference/v2/services/scans.mdx rename docs/snippets/api/v2/{agentic-workflows-endpoints.mdx => jobs-endpoints.mdx} (100%) delete mode 100644 docs/snippets/api/v2/scans-endpoints.mdx diff --git a/docs/api-reference/v2/services/agentic-workflows.mdx b/docs/api-reference/v2/services/agentic-workflows.mdx deleted file mode 100644 index 50c817493f..0000000000 --- a/docs/api-reference/v2/services/agentic-workflows.mdx +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Agentic Workflows" -description: "Workflow job execution and schedules." ---- - -import AgenticWorkflowsEndpoints from "/snippets/api/v2/agentic-workflows-endpoints.mdx" - -Agentic Workflows run analysis jobs on a schedule or on demand. These endpoints cover the execution and scheduling of those jobs — the issues they produce live in [Workflow issues](/api-reference/v2/services/workflow-issues). - -## Endpoints - - - -Each change to this service is recorded in the -[changelog](/api-reference/v2/Changelog); anything superseded is deprecated -under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/jobs.mdx b/docs/api-reference/v2/services/jobs.mdx new file mode 100644 index 0000000000..e9f32c1a7e --- /dev/null +++ b/docs/api-reference/v2/services/jobs.mdx @@ -0,0 +1,16 @@ +--- +title: "Jobs" +description: "Scan runs, workflow job runs, and retries." +--- + +import JobsEndpoints from "/snippets/api/v2/jobs-endpoints.mdx" + +A job is one run of Semgrep work against a project: a scan, or an Agentic Workflows job. These endpoints report on those runs, retry the ones that failed, and cover how workflow jobs are triggered and scheduled. The issues a job produces live in [Workflow issues](/api-reference/v2/services/workflow-issues). + +## Endpoints + + + +Each change to this service is recorded in the +[changelog](/api-reference/v2/Changelog); anything superseded is deprecated +under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/api-reference/v2/services/scans.mdx b/docs/api-reference/v2/services/scans.mdx deleted file mode 100644 index f122a9ebf2..0000000000 --- a/docs/api-reference/v2/services/scans.mdx +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Scans" -description: "Scan runs and retries." ---- - -import ScansEndpoints from "/snippets/api/v2/scans-endpoints.mdx" - -A scan is one execution of Semgrep against a project. These endpoints report on scan runs and retry the ones that failed. - -## Endpoints - - - -Each change to this service is recorded in the -[changelog](/api-reference/v2/Changelog); anything superseded is deprecated -under the [deprecation policy](/api-reference/v2/Deprecation-Policy). diff --git a/docs/docs.json b/docs/docs.json index 022db7479e..588518fd44 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -292,8 +292,8 @@ "group": "Triage and fix vulnerabilities", "root": "semgrep-supply-chain/triage-and-fix/overview", "pages": [ - "semgrep-supply-chain/upgrade-guidance", - "semgrep-supply-chain/autofix" + "semgrep-supply-chain/upgrade-guidance", + "semgrep-supply-chain/autofix" ] }, { @@ -1075,7 +1075,6 @@ "source": "public_v2.openapi.yaml", "directory": "api-reference/v2" }, - "root": "api-reference/v2/services/scans", "pages": [ "GET /api/v2/deployments/{deploymentId}/scans/{scanId}", "POST /api/v2/deployments/{deploymentId}/projects/{projectId}/scans/list", diff --git a/docs/snippets/api/v2/agentic-workflows-endpoints.mdx b/docs/snippets/api/v2/jobs-endpoints.mdx similarity index 100% rename from docs/snippets/api/v2/agentic-workflows-endpoints.mdx rename to docs/snippets/api/v2/jobs-endpoints.mdx diff --git a/docs/snippets/api/v2/scans-endpoints.mdx b/docs/snippets/api/v2/scans-endpoints.mdx deleted file mode 100644 index 2aaab4c150..0000000000 --- a/docs/snippets/api/v2/scans-endpoints.mdx +++ /dev/null @@ -1,7 +0,0 @@ -{/* Auto-generated by scripts/generate_service_endpoints.py -- do not edit by hand. */} - -| Endpoint | Description | -| --- | --- | -| GET [Get scan](/api-reference/v2/scansservice/get-scan) | Get a scan by ID. | -| POST [List scans](/api-reference/v2/scansservice/list-scans) | List the scans associated with a particular project from the past 30 days. | -| POST [Retry failed Managed Scans](/api-reference/v2/scansservice/retry-failed-managed-scans) | Retry one or more failed Semgrep Managed Scans (SMS). |