From c9667de969b06a1abf1516beca1528876e0272a1 Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 11:36:36 +0100 Subject: [PATCH 01/13] Slim Beta tier page to narrative; endpoint detail lives in OpenAPI reference Removes the per-endpoint tables, request bodies, field reference, and response shapes from beta.md now that they're generated from the OpenAPI spec. Keeps the narrative that belongs on a prose page: auth, platforms, pagination strategy, error codes, testing recipes, and v2 migration notes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Threat Intelligence API/beta.md | 320 ++---------------- 1 file changed, 29 insertions(+), 291 deletions(-) diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index e34d3b6..ab60228 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -14,15 +14,17 @@ sidebar: _The Beta endpoints live alongside the v2 API and add npm coverage, an optional full advisory body, a consistent nested response shape, and cursor pagination. They are the recommended endpoints for new integrations; v2 remains available for backwards compatibility._ -## API Usage +> **Interactive reference:** Every endpoint, parameter, request body and response shape is documented in the [Threat Intelligence API (Beta) reference](/api-reference/threat-intelligence-beta/). The raw OpenAPI spec is at [`/schemas/threat-intel-beta.yaml`](https://github.com/patchstack/documentation/blob/main/schemas/threat-intel-beta.yaml) — import it into Postman, Insomnia, Bruno or Hoppscotch directly. -### Base URL +This page covers the concepts you need to use the API effectively — authentication, platforms, pagination, rate limiting, and migration from v2. Use it alongside the interactive reference. + +## Base URL ``` https://vdp-api.patchstack.com/database/api/beta/ ``` -### Authentication +## Authentication Every request must include your API key in the **`PSKey`** HTTP request header. You can request an API key by reaching out on . @@ -30,290 +32,54 @@ Every request must include your API key in the **`PSKey`** HTTP request header. PSKey: ``` -### Response format - -All responses are JSON. Beta responses are cached until the database updates, at which point the cache is cleared. A single response shape is shared across all three list endpoints (`/all`, `/latest`, `/product/npm/...`) so clients can parse them interchangeably. - -### Platforms +## Platforms Pass `?platform=npm` (or `?platform=wordpress` — the default) on list endpoints. Platform names are case-insensitive. -### Rate limiting - -Same policy as the Extended Threat Intelligence API — please contact if you need an elevated quota. - ---- - -## Endpoints +## Response format -| Method | Endpoint | Purpose | -|---|---|---| -| `GET` | `/database/api/beta/all` | Paginated listing of every published vulnerability for a platform | -| `GET` | `/database/api/beta/latest` | Vulnerabilities added in the last 24 hours | -| `GET` | `/database/api/beta/product/{type}/{name}/{version}` | Match a single product/version (npm & WordPress family) | -| `GET` | `/database/api/beta/product/{type}/{name}/{version}/exists` | Boolean-only variant of the above | -| `POST` | `/database/api/beta/batch` | Check up to 50 products in a single request | - ---- - -## List all vulnerabilities - -**Description:** Paginated listing of every published vulnerability for the given platform, ordered by descending `id`. -**Endpoint:** `/database/api/beta/all` -**Method:** `GET` - -### Query parameters - -| Name | Type | Default | Description | -|---|---|---|---| -| `platform` | string | `wordpress` | `npm` or `wordpress`. Case-insensitive. | -| `page` | integer | `1` | Offset-pagination page (1-indexed). | -| `per_page` | integer | `100` | Page size, max `500`. | -| `cursor` | string | — | Opaque cursor (see below). Presence of the param switches to cursor mode. | -| `include` | string | — | Pass `details` to include the full advisory body in each item. | +All responses are JSON. Beta responses are cached until the database updates, at which point the cache is cleared. A single response shape is shared across all three list endpoints (`/all`, `/latest`, `/product/npm/...`) so clients can parse them interchangeably. -### Pagination modes +## Pagination -`/all` supports **two independent pagination strategies**. Use whichever fits your client: +`/all` and `/latest` support **two independent pagination strategies**. Use whichever fits your client: - **Offset (`?page=&per_page=`)** — returns a `pagination` block with totals, `has_next_page`, `has_previous_page`, etc. Easy to jump to a specific page; slower at depth and susceptible to row-shift when new vulnerabilities land while you're paging. - **Cursor (`?cursor=`)** — returns a `cursor` block with `next_cursor`, `has_more`, `per_page`. Stable under concurrent inserts and faster at any depth. No `total` count (deliberately skipped to keep cursor mode fast). -`cursor` and `page` are mutually exclusive; passing both returns `422 Unprocessable Entity`. - -### Example — offset mode - -```bash -curl 'https://patchstack.com/database/api/beta/all?platform=npm&page=1&per_page=25' \ - -H 'PSKey: ' -``` - -Response: - -```json -{ - "vulnerabilities": [ /* 25 items */ ], - "pagination": { - "current_page": 1, - "per_page": 25, - "total": 6115, - "total_pages": 245, - "has_next_page": true, - "has_previous_page": false, - "next_page": 2, - "previous_page": null, - "from": 1, - "to": 25 - } -} -``` - -### Example — cursor mode - -First page — send `cursor=` with an empty value to bootstrap: - -```bash -curl 'https://patchstack.com/database/api/beta/all?platform=npm&per_page=25&cursor=' \ - -H 'PSKey: ' -``` - -Response: - -```json -{ - "vulnerabilities": [ /* 25 items */ ], - "cursor": { - "next_cursor": "djE6NDYzMzk", - "has_more": true, - "per_page": 25 - } -} -``` - -Follow `next_cursor` on the next request: - -```bash -curl 'https://patchstack.com/database/api/beta/all?platform=npm&per_page=25&cursor=djE6NDYzMzk' \ - -H 'PSKey: ' -``` - -Stop when `has_more: false` and `next_cursor: null`. - -### Example — with full advisory body - -```bash -curl 'https://patchstack.com/database/api/beta/all?platform=npm&per_page=25&include=details' \ - -H 'PSKey: ' -``` - -Adds an `advisory_details` field (markdown) to every item. - ---- - -## Latest (last 24 hours) +`cursor` and `page` are mutually exclusive; passing both returns `422 Unprocessable Entity`. To bootstrap cursor mode, send `?cursor=` with an empty value. -**Description:** Returns vulnerabilities whose row was inserted into the Patchstack database in the last 24 hours. The filter is on `created_at` (insertion time), **not** `disclosure_date`. -**Endpoint:** `/database/api/beta/latest` -**Method:** `GET` - -Accepts the **same query parameters** as `/all` — `platform`, `page`, `per_page`, `cursor`, `include`. - -```bash -curl 'https://patchstack.com/database/api/beta/latest?platform=npm&per_page=50' \ - -H 'PSKey: ' -``` - -Cursor pagination works identically: - -```bash -curl 'https://patchstack.com/database/api/beta/latest?platform=npm&per_page=50&cursor=' \ - -H 'PSKey: ' -``` - ---- +## Including full advisory bodies -## Find vulnerability for a product +Pass `?include=details` on any list endpoint to add an `advisory_details` markdown field to each item. Applies to npm results. -**Description:** Match a specific product + version against the vulnerability database and return every applicable advisory. -**Endpoint:** `/database/api/beta/product/{type}/{name}/{version}/{exists?}` -**Method:** `GET` - -| Path param | Description | -|---|---| -| `type` | `npm`, `plugin`, `theme`, or `wordpress`. | -| `name` | npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`. | -| `version` | Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product. | -| `exists` | Optional. Pass the literal string `exists` to get a boolean response only. | - -### Query parameters - -| Name | Type | Description | -|---|---|---| -| `include` | string | Pass `details` to include the full advisory body (`advisory_details`) per item. Applies only to npm. | - -### Example — npm, concrete version - -```bash -curl 'https://patchstack.com/database/api/beta/product/npm/axios/0.21.4?include=details' \ - -H 'PSKey: ' -``` - -### Example — npm, wildcard version (all advisories for the package) - -```bash -curl 'https://patchstack.com/database/api/beta/product/npm/axios/*' \ - -H 'PSKey: ' -``` - -### Example — boolean exists check - -```bash -curl 'https://patchstack.com/database/api/beta/product/npm/axios/0.21.4/exists' \ - -H 'PSKey: ' -``` - -Response: - -```json -{ "vulnerable": true } -``` - -### Note on scoped npm packages +## Scoped npm packages npm package slugs that include a `/` (e.g. `@scope/pkg`) conflict with the route separator. URL-encode the `/` as `%2F` or contact us for guidance on the encoding helper. ---- - -## Bulk product check +## Rate limiting -**Description:** Check up to 50 products in a single request. Mirrors the format documented for the Extended tier's `/batch` endpoint. -**Endpoint:** `/database/api/beta/batch` -**Method:** `POST` -**Payload:** Raw JSON array, ≤ 50 objects. - -```bash -curl -X POST 'https://patchstack.com/database/api/beta/batch' \ - -H 'PSKey: ' \ - -H 'Content-Type: application/json' \ - -d '[ - { "type": "npm", "name": "axios", "version": "0.21.4", "exists": false }, - { "type": "plugin", "name": "tutor", "version": "1.5.2", "exists": true }, - { "type": "wordpress","name":"wordpress","version": "6.0.0", "exists": true } - ]' -``` +Same policy as the Extended Threat Intelligence API — please contact if you need an elevated quota. ---- +## Errors -## Response shape (npm) +| Status | Meaning | +|---|---| +| `401 Unauthorized` | Missing or invalid `PSKey` header. | +| `403 Forbidden` | API key not authorised for the requested endpoint. | +| `422 Unprocessable Entity` | Invalid parameter combination (e.g. `cursor` + `page`), invalid `platform`, or `per_page > 500`. | +| `429 Too Many Requests` | Rate limit exceeded. | +| `500` | Server error — please include the request id in any bug report. | -The three list endpoints (`/all`, `/latest`, `/product/npm/...`) share the same per-item shape when `platform=npm`. `advisory_details` is only present when `?include=details` was passed. +When a cursor is malformed (invalid base64 or missing the `v1:` prefix), the endpoint returns `200` with an empty page: ```json { - "id": 46500, - "title": "NPM: OpenClaw: ...", - "disclosed_at": "2026-04-03T03:15:56+00:00", - "created_at": "2026-04-21T08:38:34+00:00", - "url": "https://patchstack.com/database/npm/npm/openclaw/vulnerability/...", - "vuln_type": "Other Vulnerability Type", - "cve": "2026-41331", - "is_exploited": false, - "patch_priority": 2, - "advisory_details": "## Summary\n...", - "product": { - "id": 23595, - "name": "openclaw", - "slug": "openclaw" - }, - "cvss": { - "score": 6.9, - "vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N" - }, - "cwe": { - "id": 770, - "name": "Allocation of Resources Without Limits or Throttling" - }, - "capec": { - "id": null, - "name": null - }, - "references": [ - "https://github.com/openclaw/openclaw/security/advisories/GHSA-m6fx-m8hc-572m", - "https://github.com/openclaw/openclaw/releases/tag/v2026.3.31" - ], - "ghsa": "GHSA-m6fx-m8hc-572m", - "version_info": { - "affected": "<= 2026.3.28", - "fixed": "2026.3.31", - "patched_ranges": [] - } + "vulnerabilities": [], + "cursor": { "next_cursor": null, "has_more": false, "per_page": 100 } } ``` -### Field reference - -| Field | Type | Description | -|---|---|---| -| `id` | integer | Stable Patchstack vulnerability id. | -| `title` | string | Human-readable title (prefixed with `NPM:` for npm advisories). | -| `disclosed_at` | ISO-8601 | When the vulnerability was publicly disclosed. | -| `created_at` | ISO-8601 | When the row was inserted into the Patchstack DB. Drives `/latest` windowing. | -| `url` | string | Public Patchstack vulnerability page (token-tagged). | -| `vuln_type` | string | High-level vulnerability category. | -| `cve` | string | First CVE identifier, or `""` when none is assigned. | -| `is_exploited` | boolean | Whether exploitation has been observed in the wild. | -| `patch_priority` | integer | 1 (low) to 3 (high). | -| `advisory_details` | string \| absent | Full advisory body. Only present with `?include=details`. | -| `product.{id,name,slug}` | object | The affected package/product. | -| `cvss.{score,vector}` | object | CVSS score and vector (may be `null`). | -| `cwe.{id,name}` | object | CWE classification (may be `null`). | -| `capec.{id,name}` | object | CAPEC classification (may be `null`). | -| `references` | string[] | External reference URLs (advisories, commits, tags). | -| `ghsa` | string | GHSA identifier when the advisory came from the GitHub Advisory Database. | -| `version_info.affected` | string | Affected version range (e.g. `<= 2026.3.28`). | -| `version_info.fixed` | string | First fixed version. | -| `version_info.patched_ranges` | array | Structured list of `{from_version, to_version, fixed_in}` entries for advisories with multiple patch ranges. | - --- ## Testing @@ -338,16 +104,9 @@ curl 'https://patchstack.com/database/api/beta/product/npm/axios/0.21.4/exists' -H 'PSKey: ' ``` -### Postman +### Postman / Insomnia / Bruno / Hoppscotch -1. Create a new request, set the method, paste the URL. -2. Under **Headers**, add: - - Key: `PSKey` - - Value: your API key -3. Under **Headers**, add (recommended): - - Key: `Accept` - - Value: `application/json` -4. For `POST /batch`, set the body to **raw → JSON** and paste the array payload. +Import the OpenAPI spec directly from [`schemas/threat-intel-beta.yaml`](https://github.com/patchstack/documentation/blob/main/schemas/threat-intel-beta.yaml) — authentication, parameters and example payloads are preconfigured. Set the `PSKey` security value to your API key once and every request in the collection will use it. ### Cursor iteration (JavaScript / Node) @@ -405,27 +164,6 @@ do { --- -## Errors - -| Status | Meaning | -|---|---| -| `401 Unauthorized` | Missing or invalid `PSKey` header. | -| `403 Forbidden` | API key not authorised for the requested endpoint. | -| `422 Unprocessable Entity` | Invalid parameter combination (e.g. `cursor` + `page`), invalid `platform`, or `per_page > 500`. | -| `429 Too Many Requests` | Rate limit exceeded. | -| `500` | Server error — please include the request id in any bug report. | - -When a cursor is malformed (invalid base64 or missing the `v1:` prefix), the endpoint returns `200` with an empty page: - -```json -{ - "vulnerabilities": [], - "cursor": { "next_cursor": null, "has_more": false, "per_page": 100 } -} -``` - ---- - ## Migration notes (v2 → beta) - Beta npm responses use **nested objects** (`product`, `cvss`, `cwe`, `capec`, `version_info`) whereas the v2 shape is flat. Update parsers accordingly. From 2cc0e10e6425d3e3138f63d159f9bc7901cfbefa Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 11:37:49 +0100 Subject: [PATCH 02/13] Flesh out OpenAPI info, tag, and operation descriptions Tag landing pages (e.g. /operations/tags/vulnerabilities/) and the overview were rendering only the one-line blurb for each tag. Expand the info, tags[].description, and the thinner operation descriptions so each generated page has usable content: auth, platforms, pagination strategies, errors, related pages, and per-tag endpoint guides with deep links. Co-Authored-By: Claude Opus 4.7 (1M context) --- schemas/threat-intel-beta.yaml | 643 +++++++++++++++++++++++++++++++++ 1 file changed, 643 insertions(+) create mode 100644 schemas/threat-intel-beta.yaml diff --git a/schemas/threat-intel-beta.yaml b/schemas/threat-intel-beta.yaml new file mode 100644 index 0000000..23a28b0 --- /dev/null +++ b/schemas/threat-intel-beta.yaml @@ -0,0 +1,643 @@ +openapi: 3.1.0 +info: + title: Patchstack Threat Intelligence API — Beta + version: beta + summary: npm + WordPress vulnerability intelligence with nested response shape and cursor pagination. + description: | + The Beta endpoints live alongside the v2 API and add npm coverage, an optional + full advisory body, a consistent nested response shape, and cursor pagination. + They are the recommended endpoints for new integrations; v2 remains available + for backwards compatibility. + + ## Authentication + + Every request must include your API key in the `PSKey` HTTP request header. + Request a key via . + + ## Platforms + + Pass `?platform=npm` (or `?platform=wordpress` — the default) on list + endpoints. Platform names are case-insensitive. + + ## Pagination + + `/all` and `/latest` support two independent strategies, selected by which + query parameter you pass: + + - **Offset** (`?page=&per_page=`) — returns totals; easy to jump to a + specific page; susceptible to row-shift when new rows land while paging. + - **Cursor** (`?cursor=`) — stable under concurrent inserts, faster at + depth, no `total` count. Bootstrap with an empty value: `?cursor=`. + + `cursor` and `page` are mutually exclusive; passing both returns `422`. + + ## Including full advisory bodies + + Pass `?include=details` on any list endpoint to add an `advisory_details` + markdown field to each item. + + ## Scoped npm packages + + npm package slugs that include a `/` (e.g. `@scope/pkg`) conflict with the + route separator. URL-encode the `/` as `%2F`. + + ## Rate limiting + + Same policy as the Extended Threat Intelligence API. Contact + if you need an elevated quota. + + ## Errors + + | Status | Meaning | + |---|---| + | `401` | Missing or invalid `PSKey` header. | + | `403` | API key not authorised for the requested endpoint. | + | `422` | Invalid parameter combination or value (e.g. `cursor` + `page`, invalid `platform`, `per_page > 500`). | + | `429` | Rate limit exceeded. | + | `500` | Server error — include the request id in any bug report. | + + A malformed cursor returns `200` with an empty page rather than an error: + + ```json + { "vulnerabilities": [], "cursor": { "next_cursor": null, "has_more": false, "per_page": 100 } } + ``` + + ## Related pages + + - Narrative guide with code samples: [Beta tier API](/api-solutions/threat-intelligence-api/beta/) + - OpenAPI spec (import into Postman / Insomnia / Bruno / Hoppscotch): [`schemas/threat-intel-beta.yaml`](https://github.com/patchstack/documentation/blob/main/schemas/threat-intel-beta.yaml) + + Integration questions: . + contact: + name: Patchstack + url: https://patchstack.com/for-hosts/ + email: dave.jong@patchstack.com + +servers: + - url: https://patchstack.com/database/api/beta + description: Production + +security: + - PSKey: [] + +tags: + - name: Vulnerabilities + description: | + Listing endpoints that return published vulnerabilities for a platform, + ordered by descending `id`. + + - [`GET /all`](/api-reference/threat-intelligence-beta/operations/listallvulnerabilities/) + — every published vulnerability for the platform, paginated. + - [`GET /latest`](/api-reference/threat-intelligence-beta/operations/listlatestvulnerabilities/) + — vulnerabilities whose row was inserted in the last 24 hours (filter + is on `created_at`, not `disclosure_date`). + + Both endpoints share the same response shape and support offset and + cursor pagination — see the overview for details. + - name: Products + description: | + Match a specific product + version against the vulnerability database + and return every applicable advisory. + + - [`GET /product/{type}/{name}/{version}`](/api-reference/threat-intelligence-beta/operations/findproductvulnerabilities/) + — full advisory list for the product. Use `version=*` to return every + advisory for the product regardless of version. + - [`GET /product/{type}/{name}/{version}/exists`](/api-reference/threat-intelligence-beta/operations/productvulnerabilityexists/) + — boolean-only variant for fast "is this vulnerable?" checks. + + `type` accepts `npm`, `plugin`, `theme`, or `wordpress`. For scoped npm + packages, URL-encode the `/` as `%2F`. + - name: Batch + description: | + Bulk endpoint for checking up to 50 products in a single request. Use + this when walking a dependency manifest (e.g. `package.json` or a list + of installed WordPress plugins) — it's significantly cheaper than + issuing one request per product. + + - [`POST /batch`](/api-reference/threat-intelligence-beta/operations/batchproductcheck/) + — payload is a JSON array of up to 50 `{type, name, version, exists?}` + objects. Per-item results are returned in request order. + +paths: + /all: + get: + tags: [Vulnerabilities] + summary: List all vulnerabilities + description: | + Paginated listing of every published vulnerability for the given + platform, ordered by descending `id`. + + Supports **two independent pagination strategies**: + + - **Offset** (`?page=&per_page=`) — returns a `pagination` block with + totals. Easy to jump to a specific page; slower at depth and + susceptible to row-shift when new vulnerabilities land while paging. + - **Cursor** (`?cursor=`) — returns a `cursor` block with `next_cursor`, + `has_more`, `per_page`. Stable under concurrent inserts and faster at + any depth. No `total` count. + + `cursor` and `page` are mutually exclusive; passing both returns + `422 Unprocessable Entity`. + operationId: listAllVulnerabilities + parameters: + - $ref: '#/components/parameters/Platform' + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PerPage' + - $ref: '#/components/parameters/Cursor' + - $ref: '#/components/parameters/Include' + responses: + '200': + description: Paginated vulnerability listing. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/OffsetVulnerabilityList' + - $ref: '#/components/schemas/CursorVulnerabilityList' + examples: + offset: + summary: Offset mode + value: + vulnerabilities: [] + pagination: + current_page: 1 + per_page: 25 + total: 6115 + total_pages: 245 + has_next_page: true + has_previous_page: false + next_page: 2 + previous_page: null + from: 1 + to: 25 + cursor: + summary: Cursor mode + value: + vulnerabilities: [] + cursor: + next_cursor: djE6NDYzMzk + has_more: true + per_page: 25 + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/UnprocessableEntity' } + '429': { $ref: '#/components/responses/TooManyRequests' } + + /latest: + get: + tags: [Vulnerabilities] + summary: Latest vulnerabilities (last 24 hours) + description: | + Returns vulnerabilities whose row was inserted into the Patchstack + database in the last 24 hours. The filter is on `created_at` + (insertion time), **not** `disclosure_date`. + + Accepts the same query parameters as `/all`. + operationId: listLatestVulnerabilities + parameters: + - $ref: '#/components/parameters/Platform' + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PerPage' + - $ref: '#/components/parameters/Cursor' + - $ref: '#/components/parameters/Include' + responses: + '200': + description: Paginated 24h vulnerability listing. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/OffsetVulnerabilityList' + - $ref: '#/components/schemas/CursorVulnerabilityList' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/UnprocessableEntity' } + '429': { $ref: '#/components/responses/TooManyRequests' } + + /product/{type}/{name}/{version}: + get: + tags: [Products] + summary: Find vulnerabilities for a product + description: | + Match a specific product + version against the vulnerability database + and return every applicable advisory. + + npm package slugs that include a `/` (e.g. `@scope/pkg`) conflict with + the route separator. URL-encode the `/` as `%2F`. + operationId: findProductVulnerabilities + parameters: + - $ref: '#/components/parameters/ProductType' + - $ref: '#/components/parameters/ProductName' + - $ref: '#/components/parameters/ProductVersion' + - $ref: '#/components/parameters/Include' + responses: + '200': + description: Matched advisories (possibly empty). + content: + application/json: + schema: + type: object + properties: + vulnerabilities: + type: array + items: + $ref: '#/components/schemas/Vulnerability' + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/UnprocessableEntity' } + '429': { $ref: '#/components/responses/TooManyRequests' } + + /product/{type}/{name}/{version}/exists: + get: + tags: [Products] + summary: Boolean exists check for a product + description: | + Boolean-only variant of the product lookup. Returns `{ "vulnerable": true }` + or `{ "vulnerable": false }` without the advisory payload — useful for + lightweight health checks and dashboard tiles where the advisory body + isn't needed. + operationId: productVulnerabilityExists + parameters: + - $ref: '#/components/parameters/ProductType' + - $ref: '#/components/parameters/ProductName' + - $ref: '#/components/parameters/ProductVersion' + responses: + '200': + description: Boolean result. + content: + application/json: + schema: + type: object + required: [vulnerable] + properties: + vulnerable: + type: boolean + example: + vulnerable: true + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '429': { $ref: '#/components/responses/TooManyRequests' } + + /batch: + post: + tags: [Batch] + summary: Bulk product check + description: | + Check up to 50 products in a single request. Mirrors the format + documented for the Extended tier's `/batch` endpoint. + + The payload is a raw JSON array (not wrapped in an object). Each item + is `{ "type", "name", "version", "exists"? }` — when `exists: true` + the result for that item is boolean-only; otherwise it's the full + advisory list for the product. + operationId: batchProductCheck + requestBody: + required: true + content: + application/json: + schema: + type: array + maxItems: 50 + items: + $ref: '#/components/schemas/BatchItem' + example: + - { type: npm, name: axios, version: 0.21.4, exists: false } + - { type: plugin, name: tutor, version: 1.5.2, exists: true } + - { type: wordpress, name: wordpress, version: 6.0.0, exists: true } + responses: + '200': + description: Per-item results, in request order. + content: + application/json: + schema: + type: array + items: + type: object + '401': { $ref: '#/components/responses/Unauthorized' } + '403': { $ref: '#/components/responses/Forbidden' } + '422': { $ref: '#/components/responses/UnprocessableEntity' } + '429': { $ref: '#/components/responses/TooManyRequests' } + +components: + securitySchemes: + PSKey: + type: apiKey + in: header + name: PSKey + description: API key issued by Patchstack. Request one via . + + parameters: + Platform: + name: platform + in: query + description: Platform to query. Case-insensitive. + required: false + schema: + type: string + enum: [wordpress, npm] + default: wordpress + Page: + name: page + in: query + description: Offset-pagination page (1-indexed). Mutually exclusive with `cursor`. + required: false + schema: + type: integer + minimum: 1 + default: 1 + PerPage: + name: per_page + in: query + description: Page size. + required: false + schema: + type: integer + minimum: 1 + maximum: 500 + default: 100 + Cursor: + name: cursor + in: query + description: | + Opaque cursor. Presence of the param switches to cursor mode (send an + empty value to bootstrap). Mutually exclusive with `page`. + required: false + schema: + type: string + Include: + name: include + in: query + description: Pass `details` to include the full advisory body (`advisory_details`) per item. + required: false + schema: + type: string + enum: [details] + ProductType: + name: type + in: path + required: true + description: Product ecosystem. + schema: + type: string + enum: [npm, plugin, theme, wordpress] + ProductName: + name: name + in: path + required: true + description: npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`. + schema: + type: string + ProductVersion: + name: version + in: path + required: true + description: Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product. + schema: + type: string + + responses: + Unauthorized: + description: Missing or invalid `PSKey` header. + Forbidden: + description: API key not authorised for the requested endpoint. + UnprocessableEntity: + description: Invalid parameter combination (e.g. `cursor` + `page`), invalid `platform`, or `per_page > 500`. + TooManyRequests: + description: Rate limit exceeded. + + schemas: + Vulnerability: + type: object + description: Per-item shape shared across list endpoints when `platform=npm`. + required: + - id + - title + - disclosed_at + - created_at + - url + - vuln_type + - cve + - is_exploited + - patch_priority + - product + - cvss + - cwe + - capec + - references + - ghsa + - version_info + properties: + id: + type: integer + description: Stable Patchstack vulnerability id. + example: 46500 + title: + type: string + description: Human-readable title (prefixed with `NPM:` for npm advisories). + example: "NPM: OpenClaw: ..." + disclosed_at: + type: string + format: date-time + description: When the vulnerability was publicly disclosed. + created_at: + type: string + format: date-time + description: When the row was inserted into the Patchstack DB. Drives `/latest` windowing. + url: + type: string + format: uri + description: Public Patchstack vulnerability page (token-tagged). + vuln_type: + type: string + description: High-level vulnerability category. + example: Other Vulnerability Type + cve: + type: string + description: First CVE identifier, or empty string when none is assigned. + example: "2026-41331" + is_exploited: + type: boolean + description: Whether exploitation has been observed in the wild. + patch_priority: + type: integer + minimum: 1 + maximum: 3 + description: 1 (low) to 3 (high). + advisory_details: + type: string + description: Full advisory body (markdown). Only present when `?include=details` was passed. + product: + type: object + required: [id, name, slug] + properties: + id: { type: integer } + name: { type: string } + slug: { type: string } + cvss: + type: object + nullable: true + properties: + score: + type: number + format: float + nullable: true + vector: + type: string + nullable: true + cwe: + type: object + nullable: true + properties: + id: + type: integer + nullable: true + name: + type: string + nullable: true + capec: + type: object + nullable: true + properties: + id: + type: integer + nullable: true + name: + type: string + nullable: true + references: + type: array + description: External reference URLs (advisories, commits, tags). + items: + type: string + format: uri + ghsa: + type: string + description: GHSA identifier when the advisory came from the GitHub Advisory Database. + version_info: + type: object + required: [affected, fixed, patched_ranges] + properties: + affected: + type: string + description: Affected version range (e.g. `<= 2026.3.28`). + fixed: + type: string + description: First fixed version. + patched_ranges: + type: array + description: Structured list of patch ranges for advisories with multiple patch ranges. + items: + type: object + properties: + from_version: { type: string } + to_version: { type: string } + fixed_in: { type: string } + example: + id: 46500 + title: "NPM: OpenClaw: ..." + disclosed_at: "2026-04-03T03:15:56+00:00" + created_at: "2026-04-21T08:38:34+00:00" + url: "https://patchstack.com/database/npm/npm/openclaw/vulnerability/..." + vuln_type: Other Vulnerability Type + cve: "2026-41331" + is_exploited: false + patch_priority: 2 + advisory_details: "## Summary\n..." + product: + id: 23595 + name: openclaw + slug: openclaw + cvss: + score: 6.9 + vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N" + cwe: + id: 770 + name: Allocation of Resources Without Limits or Throttling + capec: + id: null + name: null + references: + - "https://github.com/openclaw/openclaw/security/advisories/GHSA-m6fx-m8hc-572m" + - "https://github.com/openclaw/openclaw/releases/tag/v2026.3.31" + ghsa: GHSA-m6fx-m8hc-572m + version_info: + affected: "<= 2026.3.28" + fixed: "2026.3.31" + patched_ranges: [] + + OffsetPagination: + type: object + required: + - current_page + - per_page + - total + - total_pages + - has_next_page + - has_previous_page + - from + - to + properties: + current_page: { type: integer } + per_page: { type: integer } + total: { type: integer } + total_pages: { type: integer } + has_next_page: { type: boolean } + has_previous_page: { type: boolean } + next_page: + type: integer + nullable: true + previous_page: + type: integer + nullable: true + from: { type: integer } + to: { type: integer } + + CursorPagination: + type: object + required: [next_cursor, has_more, per_page] + properties: + next_cursor: + type: string + nullable: true + description: Opaque cursor for the next page. `null` when there are no more pages. + has_more: + type: boolean + per_page: + type: integer + + OffsetVulnerabilityList: + type: object + required: [vulnerabilities, pagination] + properties: + vulnerabilities: + type: array + items: + $ref: '#/components/schemas/Vulnerability' + pagination: + $ref: '#/components/schemas/OffsetPagination' + + CursorVulnerabilityList: + type: object + required: [vulnerabilities, cursor] + properties: + vulnerabilities: + type: array + items: + $ref: '#/components/schemas/Vulnerability' + cursor: + $ref: '#/components/schemas/CursorPagination' + + BatchItem: + type: object + required: [type, name, version] + properties: + type: + type: string + enum: [npm, plugin, theme, wordpress] + name: + type: string + version: + type: string + exists: + type: boolean + description: When `true`, return a boolean-only result for this item. From bf8396b315a7332285de4ff3e2ac57bb7acd9924 Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 11:38:16 +0100 Subject: [PATCH 03/13] Wire starlight-openapi plugin and register API reference sidebar group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pins starlight-openapi ^0.22.1 (latest version compatible with Starlight 0.34 / Astro 5.9; 0.23+ requires Starlight ≥0.38). Renders the Threat Intelligence Beta YAML under /api-reference/threat-intelligence-beta/ and adds an "API reference" sidebar group populated by openAPISidebarGroups. Co-Authored-By: Claude Opus 4.7 (1M context) --- astro.config.mjs | 23 +++-- package-lock.json | 233 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 3 files changed, 245 insertions(+), 14 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 0c5339f..05e8819 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,6 +1,7 @@ import { defineConfig, passthroughImageService } from 'astro/config'; import starlight from '@astrojs/starlight'; import starlightLlmsTxt from 'starlight-llms-txt' +import starlightOpenAPI, { openAPISidebarGroups } from 'starlight-openapi' const site_url = process.env.URL; @@ -20,15 +21,14 @@ export default defineConfig({ integrations: [ starlight({ plugins: [ - starlightLlmsTxt() - // Generate the OpenAPI documentation pages. - // starlightOpenAPI([ - // { - // base: 'developer-api', - // label: 'My API', - // schema: './schemas/test.yaml', - // }, - // ]) + starlightLlmsTxt(), + starlightOpenAPI([ + { + base: 'api-reference/threat-intelligence-beta', + label: 'Threat Intelligence API (Beta)', + schema: './schemas/threat-intel-beta.yaml', + }, + ]), ], title: 'Patchstack Docs', favicon: '/images/psfavicon.svg', @@ -75,6 +75,11 @@ export default defineConfig({ collapsed: true, autogenerate: { directory: 'API solutions', collapsed: true }, }, + { + label: 'API reference', + collapsed: true, + items: openAPISidebarGroups, + }, { label: 'Vulnerability Disclosure Program', collapsed: true, diff --git a/package-lock.json b/package-lock.json index 8f9d49a..1d5f021 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,24 @@ "astro-og-canvas": "^0.5.3", "js-cookie": "^3.0.5", "sharp": "^0.33.4", - "starlight-llms-txt": "^0.5.1" + "starlight-llms-txt": "^0.5.1", + "starlight-openapi": "^0.22.1" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-13.0.5.tgz", + "integrity": "sha512-xfh4xVJD62gG6spIc7lwxoWT+l16nZu1ELyU8FkjaP/oD2yP09EvLAU6KhtudN9aML2Khhs9pY6Slr7KGTES3w==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" } }, "node_modules/@astrojs/compiler": { @@ -31,6 +48,7 @@ "version": "6.3.2", "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.2.tgz", "integrity": "sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q==", + "peer": true, "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.3.0", @@ -106,6 +124,7 @@ "version": "0.34.8", "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.34.8.tgz", "integrity": "sha512-XuYz0TfCZhje2u1Q9FNtmTdm7/B9QP91RDI1VkPgYvDhSYlME3k8gwgcBMHnR9ASDo2p9gskrqe7t1Pub/qryg==", + "peer": true, "dependencies": { "@astrojs/markdown-remark": "^6.3.1", "@astrojs/mdx": "^4.2.3", @@ -156,6 +175,20 @@ "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -165,9 +198,10 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -664,6 +698,15 @@ "@expressive-code/core": "^0.41.3" } }, + "node_modules/@humanwhocodes/momoa": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-2.0.4.tgz", + "integrity": "sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.10.0" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -1116,6 +1159,55 @@ "win32" ] }, + "node_modules/@readme/better-ajv-errors": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@readme/better-ajv-errors/-/better-ajv-errors-2.4.0.tgz", + "integrity": "sha512-9WODaOAKSl/mU+MYNZ2aHCrkoRSvmQ+1YkLj589OEqqjOAhbn8j7Z+ilYoiTu/he6X63/clsxxAB4qny9/dDzg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/runtime": "^7.22.5", + "@humanwhocodes/momoa": "^2.0.3", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ajv": "4.11.8 - 8" + } + }, + "node_modules/@readme/openapi-parser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@readme/openapi-parser/-/openapi-parser-4.1.2.tgz", + "integrity": "sha512-lAFH88r/CHs5VZDUocEda0OSMSQsr6801sziIjOKyVA+0hSFN+BPuelPF5XvkMROHecnPd+XEJN1iNQqCgER/g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^13.0.5", + "@readme/better-ajv-errors": "^2.3.2", + "@readme/openapi-schemas": "^3.1.0", + "@types/json-schema": "^7.0.15", + "ajv": "^8.12.0", + "ajv-draft-04": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@readme/openapi-schemas": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@readme/openapi-schemas/-/openapi-schemas-3.1.0.tgz", + "integrity": "sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", @@ -1497,6 +1589,12 @@ "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -1566,6 +1664,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1581,6 +1680,37 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -1710,6 +1840,7 @@ "version": "5.11.2", "resolved": "https://registry.npmjs.org/astro/-/astro-5.11.2.tgz", "integrity": "sha512-jKJCqp0PMZ1ZpP2xySghsJ1xK7ZNh/ISTRNBf/7khY3iEGq/zup49ZMhNZXK5Cd/dFWP/pdBNHD91SByA42IvQ==", + "peer": true, "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.6.1", @@ -2533,6 +2664,22 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fdir": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", @@ -3211,6 +3358,12 @@ "node": ">=14" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3222,6 +3375,21 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -3238,6 +3406,15 @@ "node": ">= 8" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -4451,6 +4628,13 @@ "regex-recursion": "^6.0.2" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/p-limit": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", @@ -4612,6 +4796,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5017,6 +5202,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/restructure": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", @@ -5083,6 +5277,7 @@ "version": "4.45.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -5284,6 +5479,25 @@ "astro": "^5.1.6" } }, + "node_modules/starlight-openapi": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/starlight-openapi/-/starlight-openapi-0.22.1.tgz", + "integrity": "sha512-hpYYpomwqb7f1wgpV+aV7O7sQu2QuG1VYFN+PZiqYj5RjbiCK0FKSF5YNnCZSfqiZRW3tCahu2JrxVVSqPUI+A==", + "license": "MIT", + "dependencies": { + "@readme/openapi-parser": "^4.1.2", + "github-slugger": "^2.0.0", + "url-template": "^3.1.1" + }, + "engines": { + "node": ">=18.17.1" + }, + "peerDependencies": { + "@astrojs/markdown-remark": ">=6.0.1", + "@astrojs/starlight": ">=0.34.0", + "astro": ">=5.5.0" + } + }, "node_modules/stream-replace-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", @@ -5761,6 +5975,15 @@ } } }, + "node_modules/url-template": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-3.1.1.tgz", + "integrity": "sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==", + "license": "BSD-3-Clause", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5809,6 +6032,7 @@ "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -6010,6 +6234,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 0b92ff4..89124ba 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "astro-og-canvas": "^0.5.3", "js-cookie": "^3.0.5", "sharp": "^0.33.4", - "starlight-llms-txt": "^0.5.1" + "starlight-llms-txt": "^0.5.1", + "starlight-openapi": "^0.22.1" } } From 30ae8c09241ab40d7e7da22d5abda19ba134c274 Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 11:45:28 +0100 Subject: [PATCH 04/13] Publish Threat Intelligence Beta spec + Postman collection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move schemas/threat-intel-beta.yaml into public/ so it's served at https://docs.patchstack.com/schemas/threat-intel-beta.yaml. - Add a small Astro integration that runs openapi-to-postmanv2 at config:setup, writing public/schemas/threat-intel-beta.postman_collection.json on every build. The generated collection is gitignored — the OpenAPI YAML stays the single source of truth. - Expand beta.md with a "Use with Postman / Insomnia / Bruno / Hoppscotch" section including a Run in Postman button, a Claude Code / LLM assistants section, and openapi-generator snippets for SDK generation. Only the Threat Intelligence Beta API is wired up for now. Additional APIs can drop a YAML into public/schemas/ and add an entry to the postmanCollections array and starlightOpenAPI config. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 2 + astro.config.mjs | 43 +- package-lock.json | 889 ++++++++++++++++++ package.json | 3 + .../schemas}/threat-intel-beta.yaml | 3 +- .../Threat Intelligence API/beta.md | 47 +- 6 files changed, 983 insertions(+), 4 deletions(-) rename {schemas => public/schemas}/threat-intel-beta.yaml (99%) diff --git a/.gitignore b/.gitignore index 6240da8..538d25e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ dist/ # generated types .astro/ +# generated Postman collections (built from schemas/*.yaml on every build) +public/schemas/*.postman_collection.json # dependencies node_modules/ diff --git a/astro.config.mjs b/astro.config.mjs index 05e8819..b8ae640 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,12 +1,52 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; import { defineConfig, passthroughImageService } from 'astro/config'; import starlight from '@astrojs/starlight'; import starlightLlmsTxt from 'starlight-llms-txt' import starlightOpenAPI, { openAPISidebarGroups } from 'starlight-openapi' +import Converter from 'openapi-to-postmanv2' const site_url = process.env.URL; const site = site_url || 'http://localhost:4321'; + +const postmanCollections = [ + { + openapi: './public/schemas/threat-intel-beta.yaml', + output: './public/schemas/threat-intel-beta.postman_collection.json', + }, +]; + +function postmanFromOpenAPI() { + return { + name: 'postman-from-openapi', + hooks: { + 'astro:config:setup': async ({ logger }) => { + for (const { openapi, output } of postmanCollections) { + const spec = await readFile(fileURLToPath(new URL(openapi, import.meta.url)), 'utf8'); + await new Promise((resolve, reject) => { + Converter.convert( + { type: 'string', data: spec }, + { requestParametersResolution: 'Example' }, + async (err, result) => { + if (err || !result.result) { + return reject(err ?? new Error(result.reason ?? 'Postman conversion failed')); + } + await writeFile( + fileURLToPath(new URL(output, import.meta.url)), + JSON.stringify(result.output[0].data, null, 2), + ); + logger.info(`generated ${output}`); + resolve(); + }, + ); + }); + } + }, + }, + }; +} // https://astro.build/config export default defineConfig({ site: site, @@ -19,6 +59,7 @@ export default defineConfig({ }, integrations: [ + postmanFromOpenAPI(), starlight({ plugins: [ starlightLlmsTxt(), @@ -26,7 +67,7 @@ export default defineConfig({ { base: 'api-reference/threat-intelligence-beta', label: 'Threat Intelligence API (Beta)', - schema: './schemas/threat-intel-beta.yaml', + schema: './public/schemas/threat-intel-beta.yaml', }, ]), ], diff --git a/package-lock.json b/package-lock.json index 1d5f021..56d2b58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,9 @@ "sharp": "^0.33.4", "starlight-llms-txt": "^0.5.1", "starlight-openapi": "^0.22.1" + }, + "devDependencies": { + "openapi-to-postmanv2": "^6.0.1" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -657,6 +660,13 @@ "node": ">=18" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "dev": true, + "license": "MIT" + }, "node_modules/@expressive-code/core": { "version": "0.41.3", "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.3.tgz", @@ -698,6 +708,14 @@ "@expressive-code/core": "^0.41.3" } }, + "node_modules/@faker-js/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", + "deprecated": "Please update to a newer version.", + "dev": true, + "license": "MIT" + }, "node_modules/@humanwhocodes/momoa": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-2.0.4.tgz", @@ -1711,6 +1729,24 @@ } } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -1947,6 +1983,13 @@ "astro": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2075,6 +2118,13 @@ "base64-js": "^1.1.2" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/camelcase": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", @@ -2150,6 +2200,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2189,6 +2249,100 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -2260,11 +2414,41 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/common-ancestor-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==" }, + "node_modules/compute-gcd": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", + "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", + "dev": true, + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, + "node_modules/compute-lcm": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz", + "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", + "dev": true, + "dependencies": { + "compute-gcd": "^1.2.1", + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, "node_modules/cookie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", @@ -2473,6 +2657,13 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "dev": true, + "license": "MIT" + }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -2543,6 +2734,16 @@ "@esbuild/win32-x64": "0.25.6" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -2664,6 +2865,13 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -2693,6 +2901,16 @@ } } }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2737,6 +2955,13 @@ "unicode-trie": "^2.0.0" } }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "dev": true, + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2750,6 +2975,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-east-asian-width": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", @@ -2766,6 +3001,16 @@ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/h3": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.3.tgz", @@ -3189,6 +3434,20 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" }, + "node_modules/http-reasons": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", + "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "dev": true, + "license": "MIT" + }, "node_modules/i18next": { "version": "23.16.8", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", @@ -3211,6 +3470,19 @@ "@babel/runtime": "^7.23.2" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -3375,6 +3647,41 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "foreach": "^2.0.4" + } + }, + "node_modules/json-schema-compare": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.4" + } + }, + "node_modules/json-schema-merge-allof": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz", + "integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "compute-lcm": "^1.1.2", + "json-schema-compare": "^0.2.2", + "lodash": "^4.17.20" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3415,6 +3722,23 @@ "node": ">=6" } }, + "node_modules/liquid-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", + "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -4500,6 +4824,29 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-format": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.2.tgz", + "integrity": "sha512-Y5ERWVcyh3sby9Fx2U5F1yatiTFjNsqF5NltihTWI9QgNtr5o3dbCZdcKa1l2wyfhnwwoP9HGNxga7LqZLA6gw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "charset": "^1.0.0" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -4569,6 +4916,19 @@ } } }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-fetch-native": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", @@ -4579,6 +4939,16 @@ "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.1.tgz", "integrity": "sha512-0gJJgENizp4ghds/Ywu2FCmcRsgBTmRQzYPZm61wy+Em2sBarSka0OhQS5huLBg6od1zkNpnWMCZloQDFVvOMQ==" }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^3.2.1" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4598,6 +4968,152 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-linter/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver-browser": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver-browser/-/oas-resolver-browser-2.5.6.tgz", + "integrity": "sha512-Jw5elT/kwUJrnGaVuRWe1D7hmnYWB8rfDDjBnpQ+RYY/dzAewGXeTexXzt4fGEo6PUE4eqKqPWF79MZxxvMppA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "path-browserify": "^1.0.1", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver-browser/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/oas-resolver/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/ofetch": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", @@ -4628,6 +5144,59 @@ "regex-recursion": "^6.0.2" } }, + "node_modules/openapi-to-postmanv2": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-6.0.1.tgz", + "integrity": "sha512-zAjaTwXo07az6jjvZTw4d26QMQsFxZBxTqjj3LQQMDCCuO6+peATQc9bSmAq3QbzvikP+h2WEjTphMcIrcSurg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.11.0", + "ajv-draft-04": "1.0.0", + "ajv-formats": "2.1.1", + "async": "3.2.6", + "commander": "2.20.3", + "graphlib": "2.1.8", + "js-yaml": "4.1.0", + "json-pointer": "0.6.2", + "json-schema-merge-allof": "0.8.1", + "lodash": "4.17.21", + "neotraverse": "0.6.15", + "oas-resolver-browser": "2.5.6", + "object-hash": "3.0.0", + "openapi-types": "^12.1.3", + "path-browserify": "1.0.1", + "postman-collection": "^5.0.0", + "swagger2openapi": "7.0.8", + "yaml": "1.10.2" + }, + "bin": { + "openapi2postmanv2": "bin/openapi2postmanv2.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/openapi-to-postmanv2/node_modules/neotraverse": { + "version": "0.6.15", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.15.tgz", + "integrity": "sha512-HZpdkco+JeXq0G+WWpMJ4NsX3pqb5O7eR9uGz3FfoFt+LYzU8iRWp49nJtud6hsDoywM8tIrDo3gjgmOqJA8LA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/openapi-to-postmanv2/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/openapi-types": { "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", @@ -4762,6 +5331,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4842,6 +5418,62 @@ "node": ">=4" } }, + "node_modules/postman-collection": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-5.3.0.tgz", + "integrity": "sha512-PMa5vRheqDFfS1bkRg8WBidWxunRA80sT5YNLP27YC5+ycyfiLMCwPnqQd1zfvxkGk04Pr9UronWmmgsbpsVyQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.23", + "mime": "3.0.0", + "mime-format": "2.0.2", + "postman-url-encoder": "3.0.8", + "semver": "7.7.1", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postman-collection/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postman-collection/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-url-encoder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.8.tgz", + "integrity": "sha512-EOgUMBazo7JNP4TDrd64TsooCiWzzo4143Ws8E8WYGEpn2PKpq+S4XRTDhuRTYHm3VKOpUZs7ZYZq7zSDuesqA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4879,6 +5511,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/radix3": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", @@ -4956,6 +5598,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", @@ -5202,6 +5854,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5312,6 +5974,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -5381,6 +6050,66 @@ "@types/hast": "^3.0.4" } }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true, + "license": "MIT" + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -5562,6 +6291,44 @@ "inline-style-parser": "0.2.4" } }, + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/swagger2openapi/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -5989,6 +6756,54 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate.io-array": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate.io-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==", + "dev": true + }, + "node_modules/validate.io-integer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz", + "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", + "dev": true, + "dependencies": { + "validate.io-number": "^1.0.3" + } + }, + "node_modules/validate.io-integer-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", + "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", + "dev": true, + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-integer": "^1.0.4" + } + }, + "node_modules/validate.io-number": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz", + "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==", + "dev": true + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -6186,6 +7001,35 @@ "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", @@ -6194,6 +7038,51 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", diff --git a/package.json b/package.json index 89124ba..606f7ea 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,8 @@ "sharp": "^0.33.4", "starlight-llms-txt": "^0.5.1", "starlight-openapi": "^0.22.1" + }, + "devDependencies": { + "openapi-to-postmanv2": "^6.0.1" } } diff --git a/schemas/threat-intel-beta.yaml b/public/schemas/threat-intel-beta.yaml similarity index 99% rename from schemas/threat-intel-beta.yaml rename to public/schemas/threat-intel-beta.yaml index 23a28b0..c1749db 100644 --- a/schemas/threat-intel-beta.yaml +++ b/public/schemas/threat-intel-beta.yaml @@ -65,7 +65,8 @@ info: ## Related pages - Narrative guide with code samples: [Beta tier API](/api-solutions/threat-intelligence-api/beta/) - - OpenAPI spec (import into Postman / Insomnia / Bruno / Hoppscotch): [`schemas/threat-intel-beta.yaml`](https://github.com/patchstack/documentation/blob/main/schemas/threat-intel-beta.yaml) + - OpenAPI spec (import into Postman / Insomnia / Bruno / Hoppscotch): + - Postman collection (pre-imported): Integration questions: . contact: diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index ab60228..2f7c2d4 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -14,10 +14,53 @@ sidebar: _The Beta endpoints live alongside the v2 API and add npm coverage, an optional full advisory body, a consistent nested response shape, and cursor pagination. They are the recommended endpoints for new integrations; v2 remains available for backwards compatibility._ -> **Interactive reference:** Every endpoint, parameter, request body and response shape is documented in the [Threat Intelligence API (Beta) reference](/api-reference/threat-intelligence-beta/). The raw OpenAPI spec is at [`/schemas/threat-intel-beta.yaml`](https://github.com/patchstack/documentation/blob/main/schemas/threat-intel-beta.yaml) — import it into Postman, Insomnia, Bruno or Hoppscotch directly. +> **Interactive reference:** Every endpoint, parameter, request body and response shape is documented in the [Threat Intelligence API (Beta) reference](/api-reference/threat-intelligence-beta/). This page covers the concepts you need to use the API effectively — authentication, platforms, pagination, rate limiting, and migration from v2. Use it alongside the interactive reference. +## Use with Postman, Insomnia, Bruno or Hoppscotch + +We publish the API as an [OpenAPI 3.1 spec](https://docs.patchstack.com/schemas/threat-intel-beta.yaml) and a pre-built [Postman collection](https://docs.patchstack.com/schemas/threat-intel-beta.postman_collection.json). Every endpoint, parameter, request body and example is preconfigured — set your `PSKey` once and the whole collection authenticates. + +[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/import?collection=https%3A%2F%2Fdocs.patchstack.com%2Fschemas%2Fthreat-intel-beta.postman_collection.json) + +| Tool | How to import | +|---|---| +| **Postman** | Click the button above, or `File → Import → URL` and paste the collection URL. | +| **Insomnia** | `Create → Import From → URL` → paste the OpenAPI URL. | +| **Bruno** | `Collection → Import → OpenAPI V3 Spec` → paste the OpenAPI URL. | +| **Hoppscotch** | `Collections → Import/Export → OpenAPI` → paste the OpenAPI URL. | + +**Authentication:** in Postman set the collection `Authorization` to **API Key**, key `PSKey`, value `{{PSKEY}}`, and add `PSKEY` as a collection variable with your real key as the **Current value** (leave Initial blank so it doesn't sync to teammates). Other tools work the same way — set `PSKey` as a collection header once. + +## Use with Claude Code or other LLM coding assistants + +Point your assistant at the spec. LLMs parse OpenAPI cleanly and will generate clients that match the real field names instead of hallucinating. + +- **Ad hoc:** paste the spec URL into your prompt. Example: *"Write a Python client for `https://docs.patchstack.com/schemas/threat-intel-beta.yaml`. I need cursor-mode iteration over `/all` for npm."* +- **In your repo:** download the spec to `docs/vendor/patchstack-threat-intel-beta.yaml` and reference it from your `CLAUDE.md` / `AGENTS.md`. Your assistant can then grep the YAML for specific fields without refetching. +- **Plain-text fallback:** for tools that don't parse YAML, our [`llms-full.txt`](/llms-full.txt) contains the full reference as flat markdown. + +## SDK generation + +Generate a client in any language from the same spec: + +```bash +# TypeScript +npx @openapitools/openapi-generator-cli generate \ + -i https://docs.patchstack.com/schemas/threat-intel-beta.yaml \ + -g typescript-fetch -o ./patchstack-client + +# Python +npx @openapitools/openapi-generator-cli generate \ + -i https://docs.patchstack.com/schemas/threat-intel-beta.yaml \ + -g python -o ./patchstack-client-py +``` + +Speakeasy and Fern also consume the same spec and produce more idiomatic SDKs if you need a polished client library. + +> **Spec stability:** the Beta spec may change without a version bump while the API is in beta. Pin a commit of the YAML in production integrations, or wait for the GA release when we'll publish versioned URLs. + ## Base URL ``` @@ -106,7 +149,7 @@ curl 'https://patchstack.com/database/api/beta/product/npm/axios/0.21.4/exists' ### Postman / Insomnia / Bruno / Hoppscotch -Import the OpenAPI spec directly from [`schemas/threat-intel-beta.yaml`](https://github.com/patchstack/documentation/blob/main/schemas/threat-intel-beta.yaml) — authentication, parameters and example payloads are preconfigured. Set the `PSKey` security value to your API key once and every request in the collection will use it. +Import the OpenAPI spec directly from [`threat-intel-beta.yaml`](https://docs.patchstack.com/schemas/threat-intel-beta.yaml) — authentication, parameters and example payloads are preconfigured. Set the `PSKey` security value to your API key once and every request in the collection will use it. ### Cursor iteration (JavaScript / Node) From 733c24cbb277833579b8386013a04a32e7cdf65c Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 11:53:22 +0100 Subject: [PATCH 05/13] Consolidate API sidebar and pin Beta to top MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Merge the standalone "API reference" top-level sidebar group into "API solutions" → "Threat Intelligence API", immediately below the Beta narrative. Users now have a single entry point to find anything API-related; the narrative and interactive reference sit side-by-side under the same parent. - Convert "API solutions" from autogenerate to a manual items list so the generated OpenAPI pages (openAPISidebarGroups) can be interleaved with the .md files. - Pin Beta to the top of the Threat Intelligence group via sidebar.order: 0 and add a "New" tip badge so it's discoverable. - Rename the OpenAPI reference label from "Threat Intelligence API (Beta)" to "Interactive reference (Beta)" to avoid redundancy with the parent group name. Trade-off: new pages dropped into API solutions subfolders no longer auto-appear in the sidebar — astro.config.mjs needs a manual entry. Acceptable for the current 6 pages across 2 APIs. Co-Authored-By: Claude Opus 4.7 (1M context) --- astro.config.mjs | 27 ++++++++++++++----- .../Threat Intelligence API/beta.md | 5 +++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index b8ae640..8ba4679 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -66,7 +66,7 @@ export default defineConfig({ starlightOpenAPI([ { base: 'api-reference/threat-intelligence-beta', - label: 'Threat Intelligence API (Beta)', + label: 'Interactive reference (Beta)', schema: './public/schemas/threat-intel-beta.yaml', }, ]), @@ -114,12 +114,25 @@ export default defineConfig({ { label: 'API solutions', collapsed: true, - autogenerate: { directory: 'API solutions', collapsed: true }, - }, - { - label: 'API reference', - collapsed: true, - items: openAPISidebarGroups, + items: [ + { + label: 'App API', + collapsed: true, + autogenerate: { directory: 'API solutions/App API', collapsed: true }, + }, + { + label: 'Threat Intelligence API', + collapsed: true, + items: [ + { slug: 'api-solutions/threat-intelligence-api/beta' }, + ...openAPISidebarGroups, + { slug: 'api-solutions/threat-intelligence-api/overview' }, + { slug: 'api-solutions/threat-intelligence-api/standard' }, + { slug: 'api-solutions/threat-intelligence-api/extended' }, + { slug: 'api-solutions/threat-intelligence-api/api-properties' }, + ], + }, + ], }, { label: 'Vulnerability Disclosure Program', diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index 2f7c2d4..eaa484f 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -8,8 +8,11 @@ metadata: createdAt: "Tue Apr 21 2026 00:00:00 GMT+0000 (Coordinated Universal Time)" updatedAt: "Tue Apr 21 2026 00:00:00 GMT+0000 (Coordinated Universal Time)" sidebar: - order: 4 + order: 0 label: "Beta tier API" + badge: + text: New + variant: tip --- _The Beta endpoints live alongside the v2 API and add npm coverage, an optional full advisory body, a consistent nested response shape, and cursor pagination. They are the recommended endpoints for new integrations; v2 remains available for backwards compatibility._ From f019af5cdda1bdeabaa42c51712f39c0f7cb9e55 Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 11:56:50 +0100 Subject: [PATCH 06/13] Restore API solutions Introduction page in sidebar The manual items list dropped the index.mdx entry that autogenerate was picking up. Add { slug: 'api-solutions' } at the top so the Introduction page appears before App API and Threat Intelligence API, preserving the original order. Co-Authored-By: Claude Opus 4.7 (1M context) --- astro.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/astro.config.mjs b/astro.config.mjs index 8ba4679..309c8c2 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -115,6 +115,7 @@ export default defineConfig({ label: 'API solutions', collapsed: true, items: [ + { slug: 'api-solutions' }, { label: 'App API', collapsed: true, From 32d3ac013be0cf4bd5e0ab29ec8ed6cf11edbdef Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 12:00:36 +0100 Subject: [PATCH 07/13] Group Beta narrative + reference under one sidebar item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the Beta entries to the bottom of the Threat Intelligence API group and nest them under a single expandable "Beta tier API" group (with the "New" badge on the group itself). The narrative is now labelled "Guide" and the OpenAPI-generated pages are labelled "Reference" — both together inside the Beta parent, so everything Beta is one click and one glance. Co-Authored-By: Claude Opus 4.7 (1M context) --- astro.config.mjs | 13 ++++++++++--- .../API solutions/Threat Intelligence API/beta.md | 6 +----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 309c8c2..cf0f201 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -66,7 +66,7 @@ export default defineConfig({ starlightOpenAPI([ { base: 'api-reference/threat-intelligence-beta', - label: 'Interactive reference (Beta)', + label: 'Reference', schema: './public/schemas/threat-intel-beta.yaml', }, ]), @@ -125,12 +125,19 @@ export default defineConfig({ label: 'Threat Intelligence API', collapsed: true, items: [ - { slug: 'api-solutions/threat-intelligence-api/beta' }, - ...openAPISidebarGroups, { slug: 'api-solutions/threat-intelligence-api/overview' }, { slug: 'api-solutions/threat-intelligence-api/standard' }, { slug: 'api-solutions/threat-intelligence-api/extended' }, { slug: 'api-solutions/threat-intelligence-api/api-properties' }, + { + label: 'Beta tier API', + badge: { text: 'New', variant: 'tip' }, + collapsed: true, + items: [ + { slug: 'api-solutions/threat-intelligence-api/beta' }, + ...openAPISidebarGroups, + ], + }, ], }, ], diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index eaa484f..b1f2843 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -8,11 +8,7 @@ metadata: createdAt: "Tue Apr 21 2026 00:00:00 GMT+0000 (Coordinated Universal Time)" updatedAt: "Tue Apr 21 2026 00:00:00 GMT+0000 (Coordinated Universal Time)" sidebar: - order: 0 - label: "Beta tier API" - badge: - text: New - variant: tip + label: "Guide" --- _The Beta endpoints live alongside the v2 API and add npm coverage, an optional full advisory body, a consistent nested response shape, and cursor pagination. They are the recommended endpoints for new integrations; v2 remains available for backwards compatibility._ From 87dd41414d9c06615f426b3d61bcfb0095bb1421 Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Wed, 22 Apr 2026 12:06:29 +0100 Subject: [PATCH 08/13] Make Postman collection deterministic and commit it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strip `id` and `_postman_id` UUIDs from the generated collection (both optional in Collection v2.1 — Postman assigns new ones on import) and add example values to schemas where openapi-to-postmanv2 was otherwise synthesising random data (OffsetPagination, CursorPagination, ProductType/Name/Version path params). Two consecutive builds now produce byte-identical output. Commit the collection so it's reachable via GitHub raw URL the moment this PR merges, instead of waiting on a docs.patchstack.com deploy. Build-time regeneration stays in place so the JSON can never drift from the YAML. Update the Run in Postman button to use the GitHub raw URL on main and add a direct-download fallback link for testing locally and on deploy previews — Postman's cloud cannot fetch a localhost: URL, which was producing the "blank Run in Postman modal" symptom. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 2 - astro.config.mjs | 19 +- .../threat-intel-beta.postman_collection.json | 1782 +++++++++++++++++ public/schemas/threat-intel-beta.yaml | 24 +- .../Threat Intelligence API/beta.md | 4 +- 5 files changed, 1819 insertions(+), 12 deletions(-) create mode 100644 public/schemas/threat-intel-beta.postman_collection.json diff --git a/.gitignore b/.gitignore index 538d25e..6240da8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,6 @@ dist/ # generated types .astro/ -# generated Postman collections (built from schemas/*.yaml on every build) -public/schemas/*.postman_collection.json # dependencies node_modules/ diff --git a/astro.config.mjs b/astro.config.mjs index cf0f201..eb3841e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -18,6 +18,23 @@ const postmanCollections = [ }, ]; +// Recursively drop `id` and `_postman_id` keys so the output is deterministic. +// openapi-to-postmanv2 inserts fresh UUIDs on every run, which would cause +// churn in git. Both fields are optional in Collection v2.1 — Postman assigns +// new ids on import. +function stripIds(value) { + if (Array.isArray(value)) return value.map(stripIds); + if (value && typeof value === 'object') { + const out = {}; + for (const [key, val] of Object.entries(value)) { + if (key === 'id' || key === '_postman_id') continue; + out[key] = stripIds(val); + } + return out; + } + return value; +} + function postmanFromOpenAPI() { return { name: 'postman-from-openapi', @@ -35,7 +52,7 @@ function postmanFromOpenAPI() { } await writeFile( fileURLToPath(new URL(output, import.meta.url)), - JSON.stringify(result.output[0].data, null, 2), + JSON.stringify(stripIds(result.output[0].data), null, 2), ); logger.info(`generated ${output}`); resolve(); diff --git a/public/schemas/threat-intel-beta.postman_collection.json b/public/schemas/threat-intel-beta.postman_collection.json new file mode 100644 index 0000000..8a6d981 --- /dev/null +++ b/public/schemas/threat-intel-beta.postman_collection.json @@ -0,0 +1,1782 @@ +{ + "item": [ + { + "name": "List all vulnerabilities", + "request": { + "name": "List all vulnerabilities", + "description": { + "content": "Paginated listing of every published vulnerability for the given\nplatform, ordered by descending `id`.\n\nSupports **two independent pagination strategies**:\n\n- **Offset** (`?page=&per_page=`) — returns a `pagination` block with\n totals. Easy to jump to a specific page; slower at depth and\n susceptible to row-shift when new vulnerabilities land while paging.\n- **Cursor** (`?cursor=`) — returns a `cursor` block with `next_cursor`,\n `has_more`, `per_page`. Stable under concurrent inserts and faster at\n any depth. No `total` count.\n\n`cursor` and `page` are mutually exclusive; passing both returns\n`422 Unprocessable Entity`.\n", + "type": "text/plain" + }, + "url": { + "path": [ + "all" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "disabled": false, + "key": "platform", + "value": "wordpress", + "description": "Platform to query. Case-insensitive." + }, + { + "disabled": false, + "key": "page", + "value": "1", + "description": "Offset-pagination page (1-indexed). Mutually exclusive with `cursor`." + }, + { + "disabled": false, + "key": "per_page", + "value": "100", + "description": "Page size." + }, + { + "disabled": false, + "key": "cursor", + "value": "string", + "description": "Opaque cursor. Presence of the param switches to cursor mode (send an\nempty value to bootstrap). Mutually exclusive with `page`.\n" + }, + { + "disabled": false, + "key": "include", + "value": "details", + "description": "Pass `details` to include the full advisory body (`advisory_details`) per item." + } + ], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "auth": null + }, + "response": [ + { + "name": "Paginated vulnerability listing.", + "originalRequest": { + "url": { + "path": [ + "all" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"vulnerabilities\": [],\n \"pagination\": {\n \"current_page\": 1,\n \"per_page\": 25,\n \"total\": 6115,\n \"total_pages\": 245,\n \"has_next_page\": true,\n \"has_previous_page\": false,\n \"next_page\": 2,\n \"previous_page\": null,\n \"from\": 1,\n \"to\": 25\n }\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid `PSKey` header.", + "originalRequest": { + "url": { + "path": [ + "all" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unauthorized", + "code": 401, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "API key not authorised for the requested endpoint.", + "originalRequest": { + "url": { + "path": [ + "all" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Forbidden", + "code": 403, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Invalid parameter combination (e.g. `cursor` + `page`), invalid `platform`, or `per_page > 500`.", + "originalRequest": { + "url": { + "path": [ + "all" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Rate limit exceeded.", + "originalRequest": { + "url": { + "path": [ + "all" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Too Many Requests", + "code": 429, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + } + ], + "event": [], + "protocolProfileBehavior": { + "disableBodyPruning": true + } + }, + { + "name": "Latest vulnerabilities (last 24 hours)", + "request": { + "name": "Latest vulnerabilities (last 24 hours)", + "description": { + "content": "Returns vulnerabilities whose row was inserted into the Patchstack\ndatabase in the last 24 hours. The filter is on `created_at`\n(insertion time), **not** `disclosure_date`.\n\nAccepts the same query parameters as `/all`.\n", + "type": "text/plain" + }, + "url": { + "path": [ + "latest" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "disabled": false, + "key": "platform", + "value": "wordpress", + "description": "Platform to query. Case-insensitive." + }, + { + "disabled": false, + "key": "page", + "value": "1", + "description": "Offset-pagination page (1-indexed). Mutually exclusive with `cursor`." + }, + { + "disabled": false, + "key": "per_page", + "value": "100", + "description": "Page size." + }, + { + "disabled": false, + "key": "cursor", + "value": "string", + "description": "Opaque cursor. Presence of the param switches to cursor mode (send an\nempty value to bootstrap). Mutually exclusive with `page`.\n" + }, + { + "disabled": false, + "key": "include", + "value": "details", + "description": "Pass `details` to include the full advisory body (`advisory_details`) per item." + } + ], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "auth": null + }, + "response": [ + { + "name": "Paginated 24h vulnerability listing.", + "originalRequest": { + "url": { + "path": [ + "latest" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"vulnerabilities\": [\n {\n \"id\": 46500,\n \"title\": \"NPM: OpenClaw: ...\",\n \"disclosed_at\": \"2026-04-03T03:15:56+00:00\",\n \"created_at\": \"2026-04-21T08:38:34+00:00\",\n \"url\": \"https://patchstack.com/database/npm/npm/openclaw/vulnerability/...\",\n \"vuln_type\": \"Other Vulnerability Type\",\n \"cve\": \"2026-41331\",\n \"is_exploited\": false,\n \"patch_priority\": 2,\n \"advisory_details\": \"## Summary\\n...\",\n \"product\": {\n \"id\": 23595,\n \"name\": \"openclaw\",\n \"slug\": \"openclaw\"\n },\n \"cvss\": {\n \"score\": 6.9,\n \"vector\": \"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N\"\n },\n \"cwe\": {\n \"id\": 770,\n \"name\": \"Allocation of Resources Without Limits or Throttling\"\n },\n \"capec\": {\n \"id\": null,\n \"name\": null\n },\n \"references\": [\n \"https://github.com/openclaw/openclaw/security/advisories/GHSA-m6fx-m8hc-572m\",\n \"https://github.com/openclaw/openclaw/releases/tag/v2026.3.31\"\n ],\n \"ghsa\": \"GHSA-m6fx-m8hc-572m\",\n \"version_info\": {\n \"affected\": \"<= 2026.3.28\",\n \"fixed\": \"2026.3.31\"\n }\n },\n {\n \"id\": 46500,\n \"title\": \"NPM: OpenClaw: ...\",\n \"disclosed_at\": \"2026-04-03T03:15:56+00:00\",\n \"created_at\": \"2026-04-21T08:38:34+00:00\",\n \"url\": \"https://patchstack.com/database/npm/npm/openclaw/vulnerability/...\",\n \"vuln_type\": \"Other Vulnerability Type\",\n \"cve\": \"2026-41331\",\n \"is_exploited\": false,\n \"patch_priority\": 2,\n \"advisory_details\": \"## Summary\\n...\",\n \"product\": {\n \"id\": 23595,\n \"name\": \"openclaw\",\n \"slug\": \"openclaw\"\n },\n \"cvss\": {\n \"score\": 6.9,\n \"vector\": \"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N\"\n },\n \"cwe\": {\n \"id\": 770,\n \"name\": \"Allocation of Resources Without Limits or Throttling\"\n },\n \"capec\": {\n \"id\": null,\n \"name\": null\n },\n \"references\": [\n \"https://github.com/openclaw/openclaw/security/advisories/GHSA-m6fx-m8hc-572m\",\n \"https://github.com/openclaw/openclaw/releases/tag/v2026.3.31\"\n ],\n \"ghsa\": \"GHSA-m6fx-m8hc-572m\",\n \"version_info\": {\n \"affected\": \"<= 2026.3.28\",\n \"fixed\": \"2026.3.31\"\n }\n }\n ],\n \"pagination\": {\n \"current_page\": 1,\n \"per_page\": 25,\n \"total\": 6115,\n \"total_pages\": 245,\n \"has_next_page\": true,\n \"has_previous_page\": false,\n \"from\": 1,\n \"to\": 25,\n \"next_page\": 2,\n \"previous_page\": null\n }\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid `PSKey` header.", + "originalRequest": { + "url": { + "path": [ + "latest" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unauthorized", + "code": 401, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "API key not authorised for the requested endpoint.", + "originalRequest": { + "url": { + "path": [ + "latest" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Forbidden", + "code": 403, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Invalid parameter combination (e.g. `cursor` + `page`), invalid `platform`, or `per_page > 500`.", + "originalRequest": { + "url": { + "path": [ + "latest" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Rate limit exceeded.", + "originalRequest": { + "url": { + "path": [ + "latest" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "platform", + "value": "wordpress" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "per_page", + "value": "100" + }, + { + "key": "cursor", + "value": "string" + }, + { + "key": "include", + "value": "details" + } + ], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Too Many Requests", + "code": 429, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + } + ], + "event": [], + "protocolProfileBehavior": { + "disableBodyPruning": true + } + }, + { + "name": "product/{type}/{name}/{version}", + "item": [ + { + "name": "Find vulnerabilities for a product", + "request": { + "name": "Find vulnerabilities for a product", + "description": { + "content": "Match a specific product + version against the vulnerability database\nand return every applicable advisory.\n\nnpm package slugs that include a `/` (e.g. `@scope/pkg`) conflict with\nthe route separator. URL-encode the `/` as `%2F`.\n", + "type": "text/plain" + }, + "url": { + "path": [ + "product", + ":type", + ":name", + ":version" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "disabled": false, + "key": "include", + "value": "details", + "description": "Pass `details` to include the full advisory body (`advisory_details`) per item." + } + ], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "auth": null + }, + "response": [ + { + "name": "Matched advisories (possibly empty).", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "include", + "value": "details" + } + ], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"vulnerabilities\": [\n {\n \"id\": 46500,\n \"title\": \"NPM: OpenClaw: ...\",\n \"disclosed_at\": \"2026-04-03T03:15:56+00:00\",\n \"created_at\": \"2026-04-21T08:38:34+00:00\",\n \"url\": \"https://patchstack.com/database/npm/npm/openclaw/vulnerability/...\",\n \"vuln_type\": \"Other Vulnerability Type\",\n \"cve\": \"2026-41331\",\n \"is_exploited\": false,\n \"patch_priority\": 2,\n \"advisory_details\": \"## Summary\\n...\",\n \"product\": {\n \"id\": 23595,\n \"name\": \"openclaw\",\n \"slug\": \"openclaw\"\n },\n \"cvss\": {\n \"score\": 6.9,\n \"vector\": \"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N\"\n },\n \"cwe\": {\n \"id\": 770,\n \"name\": \"Allocation of Resources Without Limits or Throttling\"\n },\n \"capec\": {\n \"id\": null,\n \"name\": null\n },\n \"references\": [\n \"https://github.com/openclaw/openclaw/security/advisories/GHSA-m6fx-m8hc-572m\",\n \"https://github.com/openclaw/openclaw/releases/tag/v2026.3.31\"\n ],\n \"ghsa\": \"GHSA-m6fx-m8hc-572m\",\n \"version_info\": {\n \"affected\": \"<= 2026.3.28\",\n \"fixed\": \"2026.3.31\"\n }\n },\n {\n \"id\": 46500,\n \"title\": \"NPM: OpenClaw: ...\",\n \"disclosed_at\": \"2026-04-03T03:15:56+00:00\",\n \"created_at\": \"2026-04-21T08:38:34+00:00\",\n \"url\": \"https://patchstack.com/database/npm/npm/openclaw/vulnerability/...\",\n \"vuln_type\": \"Other Vulnerability Type\",\n \"cve\": \"2026-41331\",\n \"is_exploited\": false,\n \"patch_priority\": 2,\n \"advisory_details\": \"## Summary\\n...\",\n \"product\": {\n \"id\": 23595,\n \"name\": \"openclaw\",\n \"slug\": \"openclaw\"\n },\n \"cvss\": {\n \"score\": 6.9,\n \"vector\": \"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N\"\n },\n \"cwe\": {\n \"id\": 770,\n \"name\": \"Allocation of Resources Without Limits or Throttling\"\n },\n \"capec\": {\n \"id\": null,\n \"name\": null\n },\n \"references\": [\n \"https://github.com/openclaw/openclaw/security/advisories/GHSA-m6fx-m8hc-572m\",\n \"https://github.com/openclaw/openclaw/releases/tag/v2026.3.31\"\n ],\n \"ghsa\": \"GHSA-m6fx-m8hc-572m\",\n \"version_info\": {\n \"affected\": \"<= 2026.3.28\",\n \"fixed\": \"2026.3.31\"\n }\n }\n ]\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid `PSKey` header.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "include", + "value": "details" + } + ], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unauthorized", + "code": 401, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "API key not authorised for the requested endpoint.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "include", + "value": "details" + } + ], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Forbidden", + "code": 403, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Invalid parameter combination (e.g. `cursor` + `page`), invalid `platform`, or `per_page > 500`.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "include", + "value": "details" + } + ], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Rate limit exceeded.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "key": "include", + "value": "details" + } + ], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Too Many Requests", + "code": 429, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + } + ], + "event": [], + "protocolProfileBehavior": { + "disableBodyPruning": true + } + }, + { + "name": "Boolean exists check for a product", + "request": { + "name": "Boolean exists check for a product", + "description": { + "content": "Boolean-only variant of the product lookup. Returns `{ \"vulnerable\": true }`\nor `{ \"vulnerable\": false }` without the advisory payload — useful for\nlightweight health checks and dashboard tiles where the advisory body\nisn't needed.\n", + "type": "text/plain" + }, + "url": { + "path": [ + "product", + ":type", + ":name", + ":version", + "exists" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "auth": null + }, + "response": [ + { + "name": "Boolean result.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version", + "exists" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"vulnerable\": true\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid `PSKey` header.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version", + "exists" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unauthorized", + "code": 401, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "API key not authorised for the requested endpoint.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version", + "exists" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Forbidden", + "code": 403, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Rate limit exceeded.", + "originalRequest": { + "url": { + "path": [ + "product", + ":type", + ":name", + ":version", + "exists" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [ + { + "disabled": false, + "type": "any", + "value": "npm", + "key": "type", + "description": "(Required) Product ecosystem." + }, + { + "disabled": false, + "type": "any", + "value": "axios", + "key": "name", + "description": "(Required) npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`." + }, + { + "disabled": false, + "type": "any", + "value": "0.21.4", + "key": "version", + "description": "(Required) Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product." + } + ] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Too Many Requests", + "code": 429, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + } + ], + "event": [], + "protocolProfileBehavior": { + "disableBodyPruning": true + } + } + ], + "event": [] + }, + { + "name": "Bulk product check", + "request": { + "name": "Bulk product check", + "description": { + "content": "Check up to 50 products in a single request. Mirrors the format\ndocumented for the Extended tier's `/batch` endpoint.\n\nThe payload is a raw JSON array (not wrapped in an object). Each item\nis `{ \"type\", \"name\", \"version\", \"exists\"? }` — when `exists: true`\nthe result for that item is boolean-only; otherwise it's the full\nadvisory list for the product.\n", + "type": "text/plain" + }, + "url": { + "path": [ + "batch" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "auth": null, + "body": { + "mode": "raw", + "raw": "[\n {\n \"type\": \"npm\",\n \"name\": \"axios\",\n \"version\": \"0.21.4\",\n \"exists\": false\n },\n {\n \"type\": \"plugin\",\n \"name\": \"tutor\",\n \"version\": \"1.5.2\",\n \"exists\": true\n },\n {\n \"type\": \"wordpress\",\n \"name\": \"wordpress\",\n \"version\": \"6.0.0\",\n \"exists\": true\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [ + { + "name": "Per-item results, in request order.", + "originalRequest": { + "url": { + "path": [ + "batch" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "body": { + "mode": "raw", + "raw": "[\n {\n \"type\": \"npm\",\n \"name\": \"axios\",\n \"version\": \"0.21.4\",\n \"exists\": false\n },\n {\n \"type\": \"plugin\",\n \"name\": \"tutor\",\n \"version\": \"1.5.2\",\n \"exists\": true\n },\n {\n \"type\": \"wordpress\",\n \"name\": \"wordpress\",\n \"version\": \"6.0.0\",\n \"exists\": true\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "[]", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid `PSKey` header.", + "originalRequest": { + "url": { + "path": [ + "batch" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "POST", + "body": { + "mode": "raw", + "raw": "[\n {\n \"type\": \"npm\",\n \"name\": \"axios\",\n \"version\": \"0.21.4\",\n \"exists\": false\n },\n {\n \"type\": \"plugin\",\n \"name\": \"tutor\",\n \"version\": \"1.5.2\",\n \"exists\": true\n },\n {\n \"type\": \"wordpress\",\n \"name\": \"wordpress\",\n \"version\": \"6.0.0\",\n \"exists\": true\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "Unauthorized", + "code": 401, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "API key not authorised for the requested endpoint.", + "originalRequest": { + "url": { + "path": [ + "batch" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "POST", + "body": { + "mode": "raw", + "raw": "[\n {\n \"type\": \"npm\",\n \"name\": \"axios\",\n \"version\": \"0.21.4\",\n \"exists\": false\n },\n {\n \"type\": \"plugin\",\n \"name\": \"tutor\",\n \"version\": \"1.5.2\",\n \"exists\": true\n },\n {\n \"type\": \"wordpress\",\n \"name\": \"wordpress\",\n \"version\": \"6.0.0\",\n \"exists\": true\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "Forbidden", + "code": 403, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Invalid parameter combination (e.g. `cursor` + `page`), invalid `platform`, or `per_page > 500`.", + "originalRequest": { + "url": { + "path": [ + "batch" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "POST", + "body": { + "mode": "raw", + "raw": "[\n {\n \"type\": \"npm\",\n \"name\": \"axios\",\n \"version\": \"0.21.4\",\n \"exists\": false\n },\n {\n \"type\": \"plugin\",\n \"name\": \"tutor\",\n \"version\": \"1.5.2\",\n \"exists\": true\n },\n {\n \"type\": \"wordpress\",\n \"name\": \"wordpress\",\n \"version\": \"6.0.0\",\n \"exists\": true\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "Unprocessable Entity (WebDAV) (RFC 4918)", + "code": 422, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + }, + { + "name": "Rate limit exceeded.", + "originalRequest": { + "url": { + "path": [ + "batch" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "PSKey", + "value": "" + } + ], + "method": "POST", + "body": { + "mode": "raw", + "raw": "[\n {\n \"type\": \"npm\",\n \"name\": \"axios\",\n \"version\": \"0.21.4\",\n \"exists\": false\n },\n {\n \"type\": \"plugin\",\n \"name\": \"tutor\",\n \"version\": \"1.5.2\",\n \"exists\": true\n },\n {\n \"type\": \"wordpress\",\n \"name\": \"wordpress\",\n \"version\": \"6.0.0\",\n \"exists\": true\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "status": "Too Many Requests", + "code": 429, + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": "", + "cookie": [], + "_postman_previewlanguage": "text" + } + ], + "event": [], + "protocolProfileBehavior": { + "disableBodyPruning": true + } + } + ], + "event": [], + "variable": [ + { + "type": "string", + "value": "https://patchstack.com/database/api/beta", + "key": "baseUrl" + } + ], + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "key", + "value": "PSKey" + }, + { + "key": "value", + "value": "{{apiKey}}" + }, + { + "key": "in", + "value": "header" + } + ] + }, + "info": { + "name": "Patchstack Threat Intelligence API — Beta", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": { + "content": "The Beta endpoints live alongside the v2 API and add npm coverage, an optional\nfull advisory body, a consistent nested response shape, and cursor pagination.\nThey are the recommended endpoints for new integrations; v2 remains available\nfor backwards compatibility.\n\n## Authentication\n\nEvery request must include your API key in the `PSKey` HTTP request header.\nRequest a key via .\n\n## Platforms\n\nPass `?platform=npm` (or `?platform=wordpress` — the default) on list\nendpoints. Platform names are case-insensitive.\n\n## Pagination\n\n`/all` and `/latest` support two independent strategies, selected by which\nquery parameter you pass:\n\n- **Offset** (`?page=&per_page=`) — returns totals; easy to jump to a\n specific page; susceptible to row-shift when new rows land while paging.\n- **Cursor** (`?cursor=`) — stable under concurrent inserts, faster at\n depth, no `total` count. Bootstrap with an empty value: `?cursor=`.\n\n`cursor` and `page` are mutually exclusive; passing both returns `422`.\n\n## Including full advisory bodies\n\nPass `?include=details` on any list endpoint to add an `advisory_details`\nmarkdown field to each item.\n\n## Scoped npm packages\n\nnpm package slugs that include a `/` (e.g. `@scope/pkg`) conflict with the\nroute separator. URL-encode the `/` as `%2F`.\n\n## Rate limiting\n\nSame policy as the Extended Threat Intelligence API. Contact\n if you need an elevated quota.\n\n## Errors\n\n| Status | Meaning |\n|---|---|\n| `401` | Missing or invalid `PSKey` header. |\n| `403` | API key not authorised for the requested endpoint. |\n| `422` | Invalid parameter combination or value (e.g. `cursor` + `page`, invalid `platform`, `per_page > 500`). |\n| `429` | Rate limit exceeded. |\n| `500` | Server error — include the request id in any bug report. |\n\nA malformed cursor returns `200` with an empty page rather than an error:\n\n```json\n{ \"vulnerabilities\": [], \"cursor\": { \"next_cursor\": null, \"has_more\": false, \"per_page\": 100 } }\n```\n\n## Related pages\n\n- Narrative guide with code samples: [Beta tier API](/api-solutions/threat-intelligence-api/beta/)\n- OpenAPI spec (import into Postman / Insomnia / Bruno / Hoppscotch): \n- Postman collection (pre-imported): \n\nIntegration questions: .\n\n\nContact Support:\n Name: Patchstack\n Email: dave.jong@patchstack.com", + "type": "text/plain" + } + } +} \ No newline at end of file diff --git a/public/schemas/threat-intel-beta.yaml b/public/schemas/threat-intel-beta.yaml index c1749db..5b77a43 100644 --- a/public/schemas/threat-intel-beta.yaml +++ b/public/schemas/threat-intel-beta.yaml @@ -381,6 +381,7 @@ components: schema: type: string enum: [npm, plugin, theme, wordpress] + example: npm ProductName: name: name in: path @@ -388,6 +389,7 @@ components: description: npm package slug or WordPress plugin/theme slug. Use `wordpress` when `type=wordpress`. schema: type: string + example: axios ProductVersion: name: version in: path @@ -395,6 +397,7 @@ components: description: Concrete version (e.g. `0.21.4`) or `*` to return every advisory for the product. schema: type: string + example: 0.21.4 responses: Unauthorized: @@ -578,20 +581,22 @@ components: - from - to properties: - current_page: { type: integer } - per_page: { type: integer } - total: { type: integer } - total_pages: { type: integer } - has_next_page: { type: boolean } - has_previous_page: { type: boolean } + current_page: { type: integer, example: 1 } + per_page: { type: integer, example: 25 } + total: { type: integer, example: 6115 } + total_pages: { type: integer, example: 245 } + has_next_page: { type: boolean, example: true } + has_previous_page: { type: boolean, example: false } next_page: type: integer nullable: true + example: 2 previous_page: type: integer nullable: true - from: { type: integer } - to: { type: integer } + example: null + from: { type: integer, example: 1 } + to: { type: integer, example: 25 } CursorPagination: type: object @@ -601,10 +606,13 @@ components: type: string nullable: true description: Opaque cursor for the next page. `null` when there are no more pages. + example: djE6NDYzMzk has_more: type: boolean + example: true per_page: type: integer + example: 25 OffsetVulnerabilityList: type: object diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index b1f2843..49eaab0 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -21,7 +21,9 @@ This page covers the concepts you need to use the API effectively — authentica We publish the API as an [OpenAPI 3.1 spec](https://docs.patchstack.com/schemas/threat-intel-beta.yaml) and a pre-built [Postman collection](https://docs.patchstack.com/schemas/threat-intel-beta.postman_collection.json). Every endpoint, parameter, request body and example is preconfigured — set your `PSKey` once and the whole collection authenticates. -[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/import?collection=https%3A%2F%2Fdocs.patchstack.com%2Fschemas%2Fthreat-intel-beta.postman_collection.json) +[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/?collection=https%3A%2F%2Fraw.githubusercontent.com%2Fpatchstack%2Fdocumentation%2Frefs%2Fheads%2Fmain%2Fpublic%2Fschemas%2Fthreat-intel-beta.postman_collection.json) + +Or [download the collection](/schemas/threat-intel-beta.postman_collection.json) directly and drag it into Postman/Insomnia/Bruno/Hoppscotch. | Tool | How to import | |---|---| From fe4d16c93712dc24271604a619a3d4e727b3e8f4 Mon Sep 17 00:00:00 2001 From: Mario Tarosso Date: Wed, 22 Apr 2026 13:28:16 +0100 Subject: [PATCH 09/13] Remove broken Run in Postman button from Beta guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Run in Postman" button relied on the deprecated `app.getpostman.com/run-collection/?collection=` endpoint, which no longer accepts raw collection URLs — it only works with collections published to Postman's Public API Network and referenced by UID. The button silently did nothing when clicked. Replacing it with a plain download link plus a `File → Import → Link` instruction is honest about the flow and actually works today. Can be re-added later if/when the collection is published to Postman. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../docs/API solutions/Threat Intelligence API/beta.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index 49eaab0..7b69274 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -21,13 +21,11 @@ This page covers the concepts you need to use the API effectively — authentica We publish the API as an [OpenAPI 3.1 spec](https://docs.patchstack.com/schemas/threat-intel-beta.yaml) and a pre-built [Postman collection](https://docs.patchstack.com/schemas/threat-intel-beta.postman_collection.json). Every endpoint, parameter, request body and example is preconfigured — set your `PSKey` once and the whole collection authenticates. -[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/?collection=https%3A%2F%2Fraw.githubusercontent.com%2Fpatchstack%2Fdocumentation%2Frefs%2Fheads%2Fmain%2Fpublic%2Fschemas%2Fthreat-intel-beta.postman_collection.json) - -Or [download the collection](/schemas/threat-intel-beta.postman_collection.json) directly and drag it into Postman/Insomnia/Bruno/Hoppscotch. +[Download the collection](/schemas/threat-intel-beta.postman_collection.json) and drag it into Postman/Insomnia/Bruno/Hoppscotch, or import it by URL from inside the tool. | Tool | How to import | |---|---| -| **Postman** | Click the button above, or `File → Import → URL` and paste the collection URL. | +| **Postman** | `File → Import → Link` and paste the collection URL. | | **Insomnia** | `Create → Import From → URL` → paste the OpenAPI URL. | | **Bruno** | `Collection → Import → OpenAPI V3 Spec` → paste the OpenAPI URL. | | **Hoppscotch** | `Collections → Import/Export → OpenAPI` → paste the OpenAPI URL. | From ee89b1d8c4c9ac842f0ea8c12420e15d8fe562a0 Mon Sep 17 00:00:00 2001 From: Mario Tarosso Date: Wed, 22 Apr 2026 13:31:58 +0100 Subject: [PATCH 10/13] Revert "Remove broken Run in Postman button from Beta guide" This reverts commit fe4d16c93712dc24271604a619a3d4e727b3e8f4. --- .../docs/API solutions/Threat Intelligence API/beta.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index 7b69274..49eaab0 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -21,11 +21,13 @@ This page covers the concepts you need to use the API effectively — authentica We publish the API as an [OpenAPI 3.1 spec](https://docs.patchstack.com/schemas/threat-intel-beta.yaml) and a pre-built [Postman collection](https://docs.patchstack.com/schemas/threat-intel-beta.postman_collection.json). Every endpoint, parameter, request body and example is preconfigured — set your `PSKey` once and the whole collection authenticates. -[Download the collection](/schemas/threat-intel-beta.postman_collection.json) and drag it into Postman/Insomnia/Bruno/Hoppscotch, or import it by URL from inside the tool. +[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/?collection=https%3A%2F%2Fraw.githubusercontent.com%2Fpatchstack%2Fdocumentation%2Frefs%2Fheads%2Fmain%2Fpublic%2Fschemas%2Fthreat-intel-beta.postman_collection.json) + +Or [download the collection](/schemas/threat-intel-beta.postman_collection.json) directly and drag it into Postman/Insomnia/Bruno/Hoppscotch. | Tool | How to import | |---|---| -| **Postman** | `File → Import → Link` and paste the collection URL. | +| **Postman** | Click the button above, or `File → Import → URL` and paste the collection URL. | | **Insomnia** | `Create → Import From → URL` → paste the OpenAPI URL. | | **Bruno** | `Collection → Import → OpenAPI V3 Spec` → paste the OpenAPI URL. | | **Hoppscotch** | `Collections → Import/Export → OpenAPI` → paste the OpenAPI URL. | From fdae0a8321fce20cd37405a5331ab75050c8131b Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Thu, 23 Apr 2026 06:50:27 +0100 Subject: [PATCH 11/13] Remove broken Run in Postman button from Beta guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Mario's review on PR #22: the app.getpostman.com/run-collection/?collection= endpoint was deprecated. Postman now only accepts published collections referenced by UID through the Public API Network — raw JSON URLs fail silently, which is what produced the blank-modal symptom. Replace the button with the honest flow: download + File → Import → Link. Can be re-added later without a breaking change if someone publishes the collection to Postman. Co-Authored-By: Mario Tarosso Co-Authored-By: Claude Opus 4.7 (1M context) --- .../docs/API solutions/Threat Intelligence API/beta.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index 49eaab0..7b69274 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -21,13 +21,11 @@ This page covers the concepts you need to use the API effectively — authentica We publish the API as an [OpenAPI 3.1 spec](https://docs.patchstack.com/schemas/threat-intel-beta.yaml) and a pre-built [Postman collection](https://docs.patchstack.com/schemas/threat-intel-beta.postman_collection.json). Every endpoint, parameter, request body and example is preconfigured — set your `PSKey` once and the whole collection authenticates. -[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/?collection=https%3A%2F%2Fraw.githubusercontent.com%2Fpatchstack%2Fdocumentation%2Frefs%2Fheads%2Fmain%2Fpublic%2Fschemas%2Fthreat-intel-beta.postman_collection.json) - -Or [download the collection](/schemas/threat-intel-beta.postman_collection.json) directly and drag it into Postman/Insomnia/Bruno/Hoppscotch. +[Download the collection](/schemas/threat-intel-beta.postman_collection.json) and drag it into Postman/Insomnia/Bruno/Hoppscotch, or import it by URL from inside the tool. | Tool | How to import | |---|---| -| **Postman** | Click the button above, or `File → Import → URL` and paste the collection URL. | +| **Postman** | `File → Import → Link` and paste the collection URL. | | **Insomnia** | `Create → Import From → URL` → paste the OpenAPI URL. | | **Bruno** | `Collection → Import → OpenAPI V3 Spec` → paste the OpenAPI URL. | | **Hoppscotch** | `Collections → Import/Export → OpenAPI` → paste the OpenAPI URL. | From 154df7d3a7fa157adf422e0106e9e2de1176c3a5 Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Thu, 23 Apr 2026 07:00:37 +0100 Subject: [PATCH 12/13] =?UTF-8?q?Rename=20Beta=20tier=20=E2=86=92=20Beta?= =?UTF-8?q?=20API=20and=20add=20partner-only=20guidance=20to=20Overview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the sidebar group and page title from "Beta tier API" to "Beta API" — Beta isn't an access tier alongside Standard / Extended, it's a new generation of the API. The "tier" naming invited the wrong mental model. Add a "Which API should I use?" comparison table to Overview and clearly flag Beta as available to selected partners working directly with Patchstack only. Update the Beta guide intro and Overview's Beta blurb to match. Co-Authored-By: Claude Opus 4.7 (1M context) --- astro.config.mjs | 2 +- .../Threat Intelligence API/beta.md | 6 ++--- .../Threat Intelligence API/overview.md | 27 ++++++++++++++++--- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index eb3841e..3a8ddef 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -147,7 +147,7 @@ export default defineConfig({ { slug: 'api-solutions/threat-intelligence-api/extended' }, { slug: 'api-solutions/threat-intelligence-api/api-properties' }, { - label: 'Beta tier API', + label: 'Beta API', badge: { text: 'New', variant: 'tip' }, collapsed: true, items: [ diff --git a/src/content/docs/API solutions/Threat Intelligence API/beta.md b/src/content/docs/API solutions/Threat Intelligence API/beta.md index 7b69274..489f160 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/beta.md +++ b/src/content/docs/API solutions/Threat Intelligence API/beta.md @@ -1,17 +1,17 @@ --- -title: "Beta tier API" +title: "Beta API" excerpt: "Beta vulnerability endpoints — npm support, Resource-based shape, offset and cursor pagination, include=details." hidden: false metadata: image: [] robots: "index" createdAt: "Tue Apr 21 2026 00:00:00 GMT+0000 (Coordinated Universal Time)" -updatedAt: "Tue Apr 21 2026 00:00:00 GMT+0000 (Coordinated Universal Time)" +updatedAt: "Wed Apr 22 2026 00:00:00 GMT+0000 (Coordinated Universal Time)" sidebar: label: "Guide" --- -_The Beta endpoints live alongside the v2 API and add npm coverage, an optional full advisory body, a consistent nested response shape, and cursor pagination. They are the recommended endpoints for new integrations; v2 remains available for backwards compatibility._ +_The Beta API is a new generation of the Threat Intelligence API, currently available to **selected partners working directly with Patchstack**. It lives alongside the v2 API (Standard / Extended) and adds npm coverage, an optional full advisory body, a consistent nested response shape, and cursor pagination. If you'd like access to run an integration on Beta, [contact us](https://patchstack.com/for-hosts/)._ > **Interactive reference:** Every endpoint, parameter, request body and response shape is documented in the [Threat Intelligence API (Beta) reference](/api-reference/threat-intelligence-beta/). diff --git a/src/content/docs/API solutions/Threat Intelligence API/overview.md b/src/content/docs/API solutions/Threat Intelligence API/overview.md index f3ffd06..e80fa54 100644 --- a/src/content/docs/API solutions/Threat Intelligence API/overview.md +++ b/src/content/docs/API solutions/Threat Intelligence API/overview.md @@ -12,18 +12,39 @@ sidebar: hidden: false --- +Patchstack publishes three ways to consume its vulnerability database. Standard and Extended are the stable tiers of the v2 API; the Beta API is a new generation available to selected partners only. + +## Which API should I use? + +| | **Standard** | **Extended** | **Beta** | +|---|---|---|---| +| **Status** | Stable (v2) | Stable (v2) | Beta — selected partners only | +| **Access** | Purchase via [Patchstack App](https://app.patchstack.com/billing/subscription) | Custom pricing, activated on request — [contact us](https://patchstack.com/for-hosts/) | By invitation. If you're working with Patchstack on an integration, [get in touch](https://patchstack.com/for-hosts/) | +| **Rate limit** | 5,000 calls / 24 hours | Custom | Custom | +| **Ecosystems** | WordPress plugins, themes, core | WordPress plugins, themes, core | WordPress + **npm** | +| **Lookups** | Single plugin/theme/version | Single + bulk | Single + bulk | +| **Pagination** | Offset | Offset | Offset **and** cursor | +| **Response shape** | Flat | Flat | Nested (`product`, `cvss`, `cwe`, `version_info`…) | +| **Full advisory body** | No | Yes | Opt-in via `?include=details` | +| **OpenAPI spec / Postman collection** | — | — | [Yes](/api-solutions/threat-intelligence-api/beta/#use-with-postman-insomnia-bruno-or-hoppscotch) | + +**New integration?** If you have Beta access, use Beta — it's the direction we're heading, and Standard/Extended will eventually adopt the same shape. If not, start with Standard or Extended depending on your data needs. + +**Existing integration?** Standard and Extended remain supported; no migration is required. + +--- + ### Standard Threat Intelligence API -Fetch the latest vulnerability information for a single version of a particular plugin, theme or WordPress core. API is limited for 5000 calls / 24 hours. Access to this API can be purchased through the [Patchstack App](https://app.patchsatck.com/billing/subscription). +Fetch the latest vulnerability information for a single version of a particular plugin, theme or WordPress core. API is limited for 5000 calls / 24 hours. Access to this API can be purchased through the [Patchstack App](https://app.patchstack.com/billing/subscription). [Standard Threat Intelligence API Documentation](/api-solutions/threat-intelligence-api/standard/) ### Extended Threat Intelligence API Access everything included in the Standard tier, bulk-request data for multiple plugins with one API call, and have additional endpoints with more information about vulnerabilities. Extended tier has custom pricing and is activated on request only. For access to these API endpoints, please [contact us here](https://patchstack.com/for-hosts/). - [Extended Threat Intelligence API Documentation](/api-solutions/threat-intelligence-api/extended/) ### Beta Threat Intelligence API -Adds npm ecosystem coverage, an opt-in full advisory body (`?include=details`), a consistent nested response shape, and cursor-based pagination alongside the existing offset pagination. These are the recommended endpoints for new integrations. +A new generation of the Threat Intelligence API, currently available to **selected partners working directly with Patchstack**. Adds npm ecosystem coverage, an opt-in full advisory body (`?include=details`), a consistent nested response shape, and cursor-based pagination alongside the existing offset pagination. If you're already working with us on an integration and would like access, [contact us](https://patchstack.com/for-hosts/). [Beta Threat Intelligence API Documentation](/api-solutions/threat-intelligence-api/beta/) From 98590667840deead14756188333f18606d7930af Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Thu, 23 Apr 2026 07:09:46 +0100 Subject: [PATCH 13/13] Un-bold the Beta API sidebar group label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starlight applies .large (heavier weight + larger size) to sidebar group labels. In a sub-group where Beta API sits next to leaf items (Standard tier API, Extended tier API, API properties) the bold treatment made Beta look visually promoted, even though its "New" badge already carries the emphasis. Scope the override via :has() so it only fires on group labels that carry a badge — today that's Beta API, and any future badged group will get the same treatment automatically without affecting plain groups like App API or Threat Intelligence API. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/styles/custom.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/styles/custom.css b/src/styles/custom.css index 2d59027..3d4c0c5 100644 --- a/src/styles/custom.css +++ b/src/styles/custom.css @@ -135,4 +135,12 @@ header button { .main-frame { margin-bottom: 30px; +} + +/* Match sidebar group labels that carry a badge (e.g. Beta API [New]) to the + weight/size of sibling leaf links, so the badge carries the emphasis + instead of the label doubling up. */ +.group-label .large:has(+ .sl-badge) { + font-size: var(--sl-text-sm); + font-weight: normal; } \ No newline at end of file