From 16065d79910e4f489acfde29c4c80a62b1b382a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Wed, 28 Jan 2026 16:22:06 +0100 Subject: [PATCH 01/11] fix(api): Fix boolean query params and improve OpenAPI plugin - Fix boolean query parameters showing empty values in API tester (e.g., `?clean=` instead of `?clean=true`). The issue was in the `explode: true` handling where `Object.entries()` was called on primitive values parsed from JSON. - Move API clients box from decorator to direct React rendering, preventing "Clients" heading from appearing in search results. - Update docusaurus-theme-openapi-docs and docusaurus-plugin-openapi-docs to 4.7.1, and @redocly/cli to 2.15.0. - Skip apiKey auth schemes in API tester (only support httpBearer). - Improve code signing: skip silently when APIFY_SIGNING_TOKEN is missing instead of spamming logs. - Fix deprecation warning by moving onBrokenMarkdownLinks to markdown.hooks. - Style improvements for the API clients box. Co-Authored-By: Claude Opus 4.5 --- apify-api/plugins/apify.mjs | 2 - .../client-references-links-decorator.mjs | 75 -------- apify-docs-theme/src/roa-loader/index.js | 13 +- apify-docs-theme/src/theme/custom.css | 57 +++--- docusaurus.config.js | 6 +- package-lock.json | 76 +++++--- package.json | 4 +- ...ocusaurus-plugin-openapi-docs+4.7.1.patch} | 8 +- .../docusaurus-theme-openapi-docs+4.6.0.patch | 51 ----- .../docusaurus-theme-openapi-docs+4.7.1.patch | 178 ++++++++++++++++++ redocly.yaml | 1 - 11 files changed, 286 insertions(+), 185 deletions(-) delete mode 100644 apify-api/plugins/decorators/client-references-links-decorator.mjs rename patches/{docusaurus-plugin-openapi-docs+4.6.0.patch => docusaurus-plugin-openapi-docs+4.7.1.patch} (91%) delete mode 100644 patches/docusaurus-theme-openapi-docs+4.6.0.patch create mode 100644 patches/docusaurus-theme-openapi-docs+4.7.1.patch diff --git a/apify-api/plugins/apify.mjs b/apify-api/plugins/apify.mjs index ac5abcee7b..5af7ce84c0 100644 --- a/apify-api/plugins/apify.mjs +++ b/apify-api/plugins/apify.mjs @@ -1,4 +1,3 @@ -import ClientReferencesLinksDecorator from './decorators/client-references-links-decorator.mjs'; import CodeSamplesDecorator from './decorators/code-samples-decorator.mjs'; import LegacyDocUrlDecorator from './decorators/legacy-doc-url-decorator.mjs'; @@ -7,7 +6,6 @@ export default () => ({ decorators: { oas3: { 'legacy-doc-url-decorator': LegacyDocUrlDecorator, - 'client-references-links-decorator': ClientReferencesLinksDecorator, 'code-samples-decorator': CodeSamplesDecorator, }, }, diff --git a/apify-api/plugins/decorators/client-references-links-decorator.mjs b/apify-api/plugins/decorators/client-references-links-decorator.mjs deleted file mode 100644 index 888f841aa6..0000000000 --- a/apify-api/plugins/decorators/client-references-links-decorator.mjs +++ /dev/null @@ -1,75 +0,0 @@ -const X_PY_DOC_URLS_PROPERTY = 'x-py-doc-url'; -const X_JS_DOC_URLS_PROPERTY = 'x-js-doc-url'; - -/** - * This decorator adds links to the Apify API Client libraries Python and JS references. - * - * The Apify API OpenAPI specification has been enriched with Apify specific vendor extensions - * on `operation` and `tag` level to link the Apify Client functionality e.g. for `actorBuild_get`: - * ``` - * x-js-parent: BuildClient - * x-js-name: get - * x-js-doc-url: https://docs.apify.com/api/client/js/reference/class/BuildClient#get - * x-py-parent: BuildClientAsync - * x-py-name: get - * x-py-doc-url: https://docs.apify.com/api/client/python/reference/class/BuildClientAsync#get - * ``` - * - * The prepended HTML example: - * ``` - * - * - * Python doc - * - *  |  - * - * JS doc - * - * - * - * TODO: The HTML/CSS above will be subject of further design development, placeholder for now. - * ``` - */ -function ClientReferencesLinksDecorator(target) { - const pyLink = target[X_PY_DOC_URLS_PROPERTY]; - const jsLink = target[X_JS_DOC_URLS_PROPERTY]; - - const jsImgUrl = '/img/javascript-40x40.svg'; - const pyImgUrl = '/img/python-40x40.svg'; - - const jsAlt = 'Apify API JavaScript Client Reference'; - const pyAlt = 'Apify API Python Client Reference'; - - // Purposely using `span` element here instead of `div` - // Due to how redoc works, when `div` used, the markdown rendering in of `description` ceased to work. - let prepend = ``; - - if (pyLink || jsLink) { - prepend += `Clients`; - } - - if (pyLink) { - prepend += ` - ${pyAlt}Python`; - } - - if (jsLink) { - prepend += ` - ${jsAlt}JavaScript`; - } - - prepend += ``; - - target.description = `${prepend}${target.description || ''}`; -} - -export default () => ({ - // Redocly is using a visitor pattern. What the following code does is that whenever the traverser leaves a node of - // type Tag or Operation, it executes prependLegacyUrlAnchor on it. - Tag: { - leave: ClientReferencesLinksDecorator, - }, - Operation: { - leave: ClientReferencesLinksDecorator, - }, -}); diff --git a/apify-docs-theme/src/roa-loader/index.js b/apify-docs-theme/src/roa-loader/index.js index a57b1dc37b..9ab5557b10 100644 --- a/apify-docs-theme/src/roa-loader/index.js +++ b/apify-docs-theme/src/roa-loader/index.js @@ -3,11 +3,13 @@ const { inspect } = require('node:util'); const { urlToRequest } = require('loader-utils'); +const SIGNING_TOKEN = process.env.APIFY_SIGNING_TOKEN; const signingUrl = new URL('https://api.apify.com/v2/tools/encode-and-sign'); -signingUrl.searchParams.set('token', process.env.APIFY_SIGNING_TOKEN); +signingUrl.searchParams.set('token', SIGNING_TOKEN || ''); const queue = []; const cache = {}; let working = false; +let warnedAboutMissingToken = false; function hash(source) { return createHash('sha1').update(source).digest('hex'); @@ -80,6 +82,15 @@ module.exports = async function (code) { return { code, hash: 'fast' }; } + // Skip signing if token is not configured + if (!SIGNING_TOKEN) { + if (!warnedAboutMissingToken) { + console.warn('APIFY_SIGNING_TOKEN is not set, skipping code signing for runnable examples.'); + warnedAboutMissingToken = true; + } + return { code, hash: 'unsigned' }; + } + console.log(`Signing ${urlToRequest(this.resourcePath)}...`, { working, queue: queue.length }); const codeHash = await encodeAndSign(code); diff --git a/apify-docs-theme/src/theme/custom.css b/apify-docs-theme/src/theme/custom.css index b8dfb2933a..5ceb6ec559 100644 --- a/apify-docs-theme/src/theme/custom.css +++ b/apify-docs-theme/src/theme/custom.css @@ -1785,51 +1785,60 @@ iframe[src*="youtube"] { .theme-api-markdown .openapi-clients-box { display: block; float: right; - padding-left: 12px; - padding-right: 6px; + margin-top: 16px; + margin-left: 16px; + margin-bottom: 12px; + padding: 10px 16px; + min-width: 140px; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-toc-border-color); + border-radius: 8px; } -.client-docs-link img { - border: 1px solid var(--color-neutral-action-secondary); - border-radius: 8px; +.theme-api-markdown .openapi-clients-box-heading { + display: block; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.5px; + text-transform: uppercase; + color: var(--ifm-color-emphasis-600); + margin-bottom: 4px; } .client-docs-link { display: flex; flex-direction: row; align-items: center; - justify-content: left; - gap: 8px; - padding: 4px 8px 8px 0px; + gap: 6px; + padding: 2px 0; + text-decoration: none; + transition: opacity 0.15s ease; } -a.client-docs-link { - font-weight: 500; - color: var(--color-neutral-text-muted); +.client-docs-link:hover { + opacity: 0.8; + text-decoration: none; } -.theme-admonition { - clear: right; +a.client-docs-link { + font-size: 13px; + font-weight: 500; + color: var(--ifm-color-emphasis-800); } -.theme-api-markdown .openapi-clients-box-heading { - display: inline-block; - font-family: 'San Francisco', Helvetica, Arial, sans-serif; - color: var(--color-neutral-text-muted); - font-style: normal; - font-weight: 700; - font-size: 14px; - line-height: 20px; - text-transform: uppercase; - padding-bottom: 6px; +.client-docs-link img { + border-radius: 4px; } .theme-api-markdown .openapi-clients-box-icon { display: block; - padding: 2px; margin: 0 !important; } +.theme-admonition { + clear: right; +} + .theme-api-markdown .openapi__method-endpoint-path, .theme-api-markdown .openapi-security__summary-header { margin-top: 0; diff --git a/docusaurus.config.js b/docusaurus.config.js index 22309f94de..98d196effc 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -77,8 +77,6 @@ module.exports = { onBrokenLinks: /** @type {import('@docusaurus/types').ReportingSeverity} */ ('throw'), - onBrokenMarkdownLinks: - /** @type {import('@docusaurus/types').ReportingSeverity} */ ('throw'), onBrokenAnchors: /** @type {import('@docusaurus/types').ReportingSeverity} */ ('warn'), themes: [ @@ -379,6 +377,10 @@ module.exports = { ], markdown: { mermaid: true, + hooks: { + onBrokenMarkdownLinks: + /** @type {import('@docusaurus/types').ReportingSeverity} */ ('throw'), + }, parseFrontMatter: async (params) => { const result = await params.defaultParseFrontMatter(params); diff --git a/package-lock.json b/package-lock.json index 2befe27290..46e641c35e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8531,21 +8531,20 @@ } }, "node_modules/@redocly/cli": { - "version": "2.14.5", - "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.14.5.tgz", - "integrity": "sha512-02Zz7YS7UwfBpbHbF64ApUkspr8Ar2XytgZ7JUljVwz+VjzCRcxkGMGE82BVYYQNKkw/YwlNOIX+lYYNbowTcw==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.15.0.tgz", + "integrity": "sha512-PjqCE1HDSHgHOrNxixeJSrumy8an2O4rcjXCIfdiHAjB1HOlMEtdgrz5OCx9f0lzcOXrgi61ThFNvO5jG56tUw==", "license": "MIT", "dependencies": { "@opentelemetry/exporter-trace-otlp-http": "0.202.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "1.34.0", - "@redocly/openapi-core": "2.14.5", - "@redocly/respect-core": "2.14.5", + "@redocly/openapi-core": "2.15.0", + "@redocly/respect-core": "2.15.0", "abort-controller": "^3.0.0", "ajv": "npm:@redocly/ajv@8.17.1", "ajv-formats": "^3.0.1", - "chokidar": "^3.5.1", "colorette": "^1.2.0", "cookie": "^0.7.2", "dotenv": "16.4.7", @@ -8562,7 +8561,7 @@ "simple-websocket": "^9.0.0", "styled-components": "^6.0.7", "ulid": "^3.0.1", - "undici": "^6.21.3", + "undici": "^6.23.0", "yargs": "17.0.1" }, "bin": { @@ -8584,14 +8583,14 @@ } }, "node_modules/@redocly/openapi-core": { - "version": "2.14.5", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.14.5.tgz", - "integrity": "sha512-MQQR+RCG0V+jZV6msgKv1CNi/+TZUXmjMAAuTEktaTOYIsQWTCV9GYSD/2n94eMDZwxI4olr05OPzOZo9z0EMg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.15.0.tgz", + "integrity": "sha512-q/2UM5tA9mgk4zaIDUkKOG9KBUqIdSIhp5DugHmFOszOE+WMiL23BIV40K79jNF9aSU3zF1p1c/Zu0UoBmBsZg==", "license": "MIT", "dependencies": { - "@redocly/ajv": "^8.17.1", + "@redocly/ajv": "^8.17.2", "@redocly/config": "^0.41.2", - "ajv": "npm:@redocly/ajv@8.17.1", + "ajv": "npm:@redocly/ajv@8.17.2", "ajv-formats": "^3.0.1", "colorette": "^1.2.0", "js-levenshtein": "^1.1.6", @@ -8605,16 +8604,49 @@ "npm": ">=10" } }, + "node_modules/@redocly/openapi-core/node_modules/@redocly/ajv": { + "version": "8.17.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.2.tgz", + "integrity": "sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==", + "license": "MIT", + "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/@redocly/openapi-core/node_modules/ajv": { + "name": "@redocly/ajv", + "version": "8.17.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.2.tgz", + "integrity": "sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==", + "license": "MIT", + "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/@redocly/respect-core": { - "version": "2.14.5", - "resolved": "https://registry.npmjs.org/@redocly/respect-core/-/respect-core-2.14.5.tgz", - "integrity": "sha512-zZKYwBZYfRi4/Iv2V7hq9xOYhpO3+IuzYjk8/V0CZjoHCnoW8jgGGhvoXMn/BfedZS9/3fV9n4SEskIbmCPl8Q==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@redocly/respect-core/-/respect-core-2.15.0.tgz", + "integrity": "sha512-0LFtQXNUE+e9OdGDLS+yt4RBx0JTGhM7UAaBC4tzLqPvhtbdWo/WB/WdasGIt4vMM9K47KivxPHmX13sSLEO2Q==", "license": "MIT", "dependencies": { "@faker-js/faker": "^7.6.0", "@noble/hashes": "^1.8.0", "@redocly/ajv": "8.17.1", - "@redocly/openapi-core": "2.14.5", + "@redocly/openapi-core": "2.15.0", "ajv": "npm:@redocly/ajv@8.17.1", "better-ajv-errors": "^1.2.0", "colorette": "^2.0.20", @@ -15156,9 +15188,9 @@ } }, "node_modules/docusaurus-plugin-openapi-docs": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-4.6.0.tgz", - "integrity": "sha512-wcRUnZca9hRiuAcw2Iz+YUVO4dh01mV2FoAtomRMVlWZIEgw6TA5SqsfHWRd6on/ibvvVS9Lq6GjZTcSjwLcWQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-4.7.1.tgz", + "integrity": "sha512-RpqvTEnhIfdSuTn/Fa/8bmxeufijLL9HCRb//ELD33AKqEbCw147SKR/CqWu4H4gwi50FZLUbiHKZJbPtXLt9Q==", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.4", @@ -15236,9 +15268,9 @@ } }, "node_modules/docusaurus-theme-openapi-docs": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-4.6.0.tgz", - "integrity": "sha512-YCgYReVMcrKDTNvM4dh9+i+ies+sGbCwv12TRCPZZbeif7RqTc/5w4rhxEIfp/v0uOAQGL4iXfTSBAMExotbMQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-4.7.1.tgz", + "integrity": "sha512-OPydf11LoEY3fdxaoqCVO+qCk7LBo6l6s28UvHJ5mIN/2xu+dOOio9+xnKZ5FIPOlD+dx0gVSKzaVCi/UFTxlg==", "license": "MIT", "dependencies": { "@hookform/error-message": "^2.0.1", diff --git a/package.json b/package.json index 55a9138ef2..ef94d495d2 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "openapi:preview": "redocly preview-docs", "openapi:bundle": "redocly bundle apify-api/openapi/openapi.yaml -o apify-api", "openapi:build": "npm run openapi:build:yaml && npm run openapi:build:json", - "openapi:build:yaml": "redocly bundle apify-api/openapi/openapi.yaml --skip-decorator=apify/legacy-doc-url-decorator --skip-decorator=apify/client-references-links-decorator --skip-decorator=apify/code-samples-decorator -o static/api/openapi.yaml", - "openapi:build:json": "redocly bundle apify-api/openapi/openapi.yaml --skip-decorator=apify/legacy-doc-url-decorator --skip-decorator=apify/client-references-links-decorator --skip-decorator=apify/code-samples-decorator -o static/api/openapi.json", + "openapi:build:yaml": "redocly bundle apify-api/openapi/openapi.yaml --skip-decorator=apify/legacy-doc-url-decorator --skip-decorator=apify/code-samples-decorator -o static/api/openapi.yaml", + "openapi:build:json": "redocly bundle apify-api/openapi/openapi.yaml --skip-decorator=apify/legacy-doc-url-decorator --skip-decorator=apify/code-samples-decorator -o static/api/openapi.json", "openapi:lint": "npm run openapi:lint:redocly && npm run openapi:lint:spectral && npm run openapi:lint:yaml", "openapi:lint:redocly": "redocly lint apify-api/openapi/openapi.yaml", "openapi:lint:spectral": "spectral lint apify-api/openapi/openapi.yaml", diff --git a/patches/docusaurus-plugin-openapi-docs+4.6.0.patch b/patches/docusaurus-plugin-openapi-docs+4.7.1.patch similarity index 91% rename from patches/docusaurus-plugin-openapi-docs+4.6.0.patch rename to patches/docusaurus-plugin-openapi-docs+4.7.1.patch index 42697f1ae7..7709531125 100644 --- a/patches/docusaurus-plugin-openapi-docs+4.6.0.patch +++ b/patches/docusaurus-plugin-openapi-docs+4.7.1.patch @@ -1,17 +1,15 @@ diff --git a/node_modules/docusaurus-plugin-openapi-docs/lib/sidebars/index.js b/node_modules/docusaurus-plugin-openapi-docs/lib/sidebars/index.js -index 6f97824..075b91e 100644 +index 6f97824..7ff251a 100644 --- a/node_modules/docusaurus-plugin-openapi-docs/lib/sidebars/index.js +++ b/node_modules/docusaurus-plugin-openapi-docs/lib/sidebars/index.js -@@ -146,9 +146,16 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) { +@@ -146,9 +146,14 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) { } const taggedApiItems = apiItems.filter((item) => { var _a; return !!((_a = item.api.tags) === null || _a === void 0 ? void 0 : _a.includes(tag)); }); const taggedSchemaItems = schemaItems.filter((item) => { var _a; return !!((_a = item.schema["x-tags"]) === null || _a === void 0 ? void 0 : _a.includes(tag)); }); + const altids = []; -+ + if (tagObject?.['x-legacy-doc-urls']) { + altids.push(...tagObject['x-legacy-doc-urls']); + } -+ return { type: "category", label: (_a = tagObject === null || tagObject === void 0 ? void 0 : tagObject["x-displayName"]) !== null && _a !== void 0 ? _a : tag, @@ -19,7 +17,7 @@ index 6f97824..075b91e 100644 link: linkConfig, collapsible: sidebarCollapsible, collapsed: sidebarCollapsed, -@@ -208,11 +215,12 @@ function generateSidebarSlice(sidebarOptions, options, api, tags, docPath, tagGr +@@ -208,11 +213,12 @@ function generateSidebarSlice(sidebarOptions, options, api, tags, docPath, tagGr filteredTags.push(tag); } }); diff --git a/patches/docusaurus-theme-openapi-docs+4.6.0.patch b/patches/docusaurus-theme-openapi-docs+4.6.0.patch deleted file mode 100644 index 7f0e674793..0000000000 --- a/patches/docusaurus-theme-openapi-docs+4.6.0.patch +++ /dev/null @@ -1,51 +0,0 @@ -diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js -index 31bf239..6cc68a1 100644 ---- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js -+++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js -@@ -309,7 +309,7 @@ function CodeSnippets({ - CodeTab, - { - value: lang.language, -- label: lang.language, -+ label: lang.label ?? lang.language, - key: lang.language, - attributes: { - className: `openapi-tabs__code-item--${lang.logoClass}`, -diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js -index a2a4e40..f2d944d 100644 ---- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js -+++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js -@@ -30,7 +30,7 @@ function Export({ url, proxy }) { - react_1.default.createElement( - "button", - { className: "export-button button button--sm button--secondary" }, -- "Export" -+ "Download OpenAPI" - ), - react_1.default.createElement( - "ul", -@@ -48,7 +48,23 @@ function Export({ url, proxy }) { - className: "dropdown__link", - href: `${url}`, - }, -- "OpenAPI Spec" -+ "YAML" -+ ) -+ ), -+ react_1.default.createElement( -+ "li", -+ null, -+ react_1.default.createElement( -+ "a", -+ { -+ onClick: (e) => { -+ e.preventDefault(); -+ saveFile(`${url.replace(/\.yaml$/, '.json')}`); -+ }, -+ className: "dropdown__link", -+ href: `${url.replace(/\.yaml$/, '.json')}`, -+ }, -+ "JSON" - ) - ) - ) diff --git a/patches/docusaurus-theme-openapi-docs+4.7.1.patch b/patches/docusaurus-theme-openapi-docs+4.7.1.patch new file mode 100644 index 0000000000..7a617e9c03 --- /dev/null +++ b/patches/docusaurus-theme-openapi-docs+4.7.1.patch @@ -0,0 +1,178 @@ +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Authorization/slice.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Authorization/slice.js +index 47f1ec7..9ce8587 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Authorization/slice.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Authorization/slice.js +@@ -49,7 +49,8 @@ function createAuth({ security, securitySchemes, options: opts }) { + const id = Object.keys(option).join(" and "); + for (const [schemeID, scopes] of Object.entries(option)) { + const scheme = securitySchemes?.[schemeID]; +- if (scheme) { ++ // Apify: Skip apiKey schemes to only support httpBearer tokens ++ if (scheme && scheme.type !== "apiKey") { + if (options[id] === undefined) { + options[id] = []; + } +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js +index 31bf239..6cc68a1 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/CodeSnippets/index.js +@@ -309,7 +309,7 @@ function CodeSnippets({ + CodeTab, + { + value: lang.language, +- label: lang.language, ++ label: lang.label ?? lang.language, + key: lang.language, + attributes: { + className: `openapi-tabs__code-item--${lang.logoClass}`, +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js +index a2a4e40..f2d944d 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/Export/index.js +@@ -30,7 +30,7 @@ function Export({ url, proxy }) { + react_1.default.createElement( + "button", + { className: "export-button button button--sm button--secondary" }, +- "Export" ++ "Download OpenAPI" + ), + react_1.default.createElement( + "ul", +@@ -48,7 +48,23 @@ function Export({ url, proxy }) { + className: "dropdown__link", + href: `${url}`, + }, +- "OpenAPI Spec" ++ "YAML" ++ ) ++ ), ++ react_1.default.createElement( ++ "li", ++ null, ++ react_1.default.createElement( ++ "a", ++ { ++ onClick: (e) => { ++ e.preventDefault(); ++ saveFile(`${url.replace(/\.yaml$/, '.json')}`); ++ }, ++ className: "dropdown__link", ++ href: `${url.replace(/\.yaml$/, '.json')}`, ++ }, ++ "JSON" + ) + ) + ) +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js +index f8f722f..ec0c117 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js +@@ -44,6 +44,7 @@ function ParamBooleanFormItem({ param }) { + render: ({ field: { onChange } }) => + react_1.default.createElement(FormSelect_1.default, { + options: ["---", "true", "false"], ++ value: (param.value === undefined || param.value === null || param.value === "") ? "---" : String(param.value), + onChange: (e) => { + const val = e.target.value; + dispatch( +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/buildPostmanRequest.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/buildPostmanRequest.js +index bcebb1c..ac03ec1 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/buildPostmanRequest.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiExplorer/buildPostmanRequest.js +@@ -117,7 +117,9 @@ function setQueryParams(postman, queryParams) { + } + } else if (param.explode) { + const jsonResult = tryDecodeJsonParam(param.value); +- if (jsonResult && typeof jsonResult === "object") { ++ // Apify: Only use Object.entries for actual objects, not primitives (boolean, number, string) ++ // JSON.parse("true") returns boolean true, and Object.entries(true) returns [] which breaks the URL ++ if (jsonResult && typeof jsonResult === "object" && !Array.isArray(jsonResult)) { + return Object.entries(jsonResult).map( + ([key, val]) => + new sdk.QueryParam({ +@@ -125,14 +127,8 @@ function setQueryParams(postman, queryParams) { + value: String(val), + }) + ); +- } else { +- return new sdk.QueryParam({ +- key: param.name, +- value: Object.entries(jsonResult) +- .map(([key, val]) => `${key},${val}`) +- .join(","), +- }); + } ++ // For primitive values (including booleans parsed from "true"/"false"), fall through to default handling + } + // Parameter allows empty value: "/hello?extended" + if (param.allowEmptyValue) { +@@ -144,9 +140,14 @@ function setQueryParams(postman, queryParams) { + } + return undefined; + } ++ // Apify: Handle boolean values - convert to string for proper URL serialization ++ // This handles both actual booleans and ensures string values are passed correctly ++ const finalValue = typeof param.value === "boolean" ++ ? param.value.toString() ++ : String(param.value); + return new sdk.QueryParam({ + key: param.name, +- value: param.value, ++ value: finalValue, + }); + }) + .flat() // Flatten the array in case of nested arrays from map +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js +index f6052d1..319ebc5 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js +@@ -47,6 +47,41 @@ function base64ToUint8Array(base64) { + } + return bytes; + } ++// Apify: Render clients box with links to Python and JavaScript API client documentation ++function renderClientsBox(api) { ++ const pyUrl = api?.['x-py-doc-url']; ++ const jsUrl = api?.['x-js-doc-url']; ++ if (!pyUrl && !jsUrl) { ++ return null; ++ } ++ return react_1.default.createElement( ++ "div", ++ { className: "openapi-clients-box" }, ++ react_1.default.createElement("div", { className: "openapi-clients-box-heading" }, "API Clients"), ++ jsUrl && react_1.default.createElement( ++ "a", ++ { ++ href: jsUrl, ++ target: "_blank", ++ rel: "noopener noreferrer", ++ className: "client-docs-link" ++ }, ++ react_1.default.createElement("img", { src: "/img/javascript-40x40.svg", className: "openapi-clients-box-icon", width: "20px", height: "20px", alt: "JavaScript" }), ++ "JavaScript" ++ ), ++ pyUrl && react_1.default.createElement( ++ "a", ++ { ++ href: pyUrl, ++ target: "_blank", ++ rel: "noopener noreferrer", ++ className: "client-docs-link" ++ }, ++ react_1.default.createElement("img", { src: "/img/python-40x40.svg", className: "openapi-clients-box-icon", width: "20px", height: "20px", alt: "Python" }), ++ "Python" ++ ) ++ ); ++} + // @ts-ignore + function ApiItem(props) { + const docHtmlClassName = `docs-doc-id-${props.content.metadata.id}`; +@@ -158,6 +193,7 @@ function ApiItem(props) { + react_1.default.createElement( + "div", + { className: "col col--7 openapi-left-panel__container" }, ++ renderClientsBox(api), + react_1.default.createElement(MDXComponent, null) + ), + react_1.default.createElement( diff --git a/redocly.yaml b/redocly.yaml index 0e57f9938d..a94f47ef8b 100644 --- a/redocly.yaml +++ b/redocly.yaml @@ -28,5 +28,4 @@ plugins: decorators: apify/legacy-doc-url-decorator: on - apify/client-references-links-decorator: on apify/code-samples-decorator: on From fd435650ba943e00abd54476cd13a29c3994ad9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Wed, 28 Jan 2026 16:34:43 +0100 Subject: [PATCH 02/11] fix(api): Move LLMButtons to direct rendering to prevent search pollution Similar to the clients box fix, render LLMButtons directly via swizzled ApiItem component instead of injecting into markdown content. Co-Authored-By: Claude Opus 4.5 --- apify-docs-theme/src/theme/ApiItem/index.js | 44 +++++++++++++++++++ .../src/theme/ApiItem/styles.module.css | 3 ++ docusaurus.config.js | 15 ------- 3 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 apify-docs-theme/src/theme/ApiItem/index.js create mode 100644 apify-docs-theme/src/theme/ApiItem/styles.module.css diff --git a/apify-docs-theme/src/theme/ApiItem/index.js b/apify-docs-theme/src/theme/ApiItem/index.js new file mode 100644 index 0000000000..86689f4ea2 --- /dev/null +++ b/apify-docs-theme/src/theme/ApiItem/index.js @@ -0,0 +1,44 @@ +import ApiItem from '@theme-original/ApiItem'; +import LLMButtons from '@theme/LLMButtons'; +import React, { useEffect, useRef } from 'react'; + +import styles from './styles.module.css'; + +/** + * Wrapper component for the OpenAPI ApiItem that adds LLMButtons. + * This avoids injecting the buttons into the markdown content which would + * pollute search results. + * + * The buttons are rendered into a container that's positioned after the h1 heading + * using a DOM manipulation approach since we can't modify the ApiItem internals. + */ +export default function ApiItemWrapper(props) { + const buttonsRef = useRef(null); + + useEffect(() => { + // Find the h1 heading and insert the buttons container after it + const heading = document.querySelector('.theme-api-markdown .openapi__heading'); + const buttonsContainer = buttonsRef.current; + + if (heading && buttonsContainer) { + // Insert the buttons after the heading + heading.parentNode.insertBefore(buttonsContainer, heading.nextSibling); + } + + // Cleanup: move the buttons back to prevent React warnings + return () => { + if (buttonsContainer && buttonsContainer.parentNode) { + // The container will be removed with the component unmount + } + }; + }, []); + + return ( + <> +
+ +
+ + + ); +} diff --git a/apify-docs-theme/src/theme/ApiItem/styles.module.css b/apify-docs-theme/src/theme/ApiItem/styles.module.css new file mode 100644 index 0000000000..22c9d8a3da --- /dev/null +++ b/apify-docs-theme/src/theme/ApiItem/styles.module.css @@ -0,0 +1,3 @@ +.llmButtonsContainer { + /* Container is moved via DOM manipulation to after the h1 heading */ +} diff --git a/docusaurus.config.js b/docusaurus.config.js index 98d196effc..d57f22c963 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -200,21 +200,6 @@ module.exports = { md = md.replace('-->', '-->'); } - // Find the first Heading h1 and add LLMButtons after it - // eslint-disable-next-line max-len - const headingRegex = /(]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/; - md = md.replace(headingRegex, '$1\n\n\n'); - - return md; - }, - createInfoPageMD: (pageData) => { - let md = createInfoPageMD(pageData); - - // Find the first Heading h1 and add LLMButtons after it - // eslint-disable-next-line max-len - const headingRegex = /(]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/; - md = md.replace(headingRegex, '$1\n\n\n'); - return md; }, }, From 9c234d9e20273ab1c441472d612cec2123a1a9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Wed, 28 Jan 2026 16:34:50 +0100 Subject: [PATCH 03/11] fix(api): Add missing properties to OpenAPI schemas Add unevaluated properties that caused validation failures after @redocly/cli 2.15.0 update with stricter validation: - Run.yaml: metamorphs as array of Metamorph objects - RunMeta.yaml: clientIp, userAgent, scheduleId, scheduledAt - Version.yaml: gitRepoUrl, tarballUrl, gitHubGistUrl - CreateOrUpdateVersionRequest.yaml: same as Version.yaml - DatasetSchemaValidationError.yaml: restructured with type/message/data - RequestDraft.yaml: id property - StoreListActor.yaml: isWhiteListedForAgenticPayment - New Metamorph.yaml schema for metamorph event details Co-Authored-By: Claude Opus 4.5 --- .../schemas/actor-runs/Metamorph.yaml | 25 +++++++++++++++++++ .../components/schemas/actor-runs/Run.yaml | 7 ++++++ .../schemas/actor-runs/RunMeta.yaml | 13 ++++++++++ .../actors/CreateOrUpdateVersionRequest.yaml | 9 +++++++ .../components/schemas/actors/Version.yaml | 9 +++++++ .../DatasetSchemaValidationError.yaml | 12 +++++++-- .../schemas/request-queues/RequestDraft.yaml | 4 +++ .../schemas/store/StoreListActor.yaml | 3 +++ 8 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 apify-api/openapi/components/schemas/actor-runs/Metamorph.yaml diff --git a/apify-api/openapi/components/schemas/actor-runs/Metamorph.yaml b/apify-api/openapi/components/schemas/actor-runs/Metamorph.yaml new file mode 100644 index 0000000000..6f5e887c9a --- /dev/null +++ b/apify-api/openapi/components/schemas/actor-runs/Metamorph.yaml @@ -0,0 +1,25 @@ +title: Metamorph +description: Information about a metamorph event that occurred during the run. +type: object +required: + - createdAt + - actorId + - buildId +properties: + createdAt: + type: string + format: date-time + description: Time when the metamorph occurred. + examples: ["2019-11-30T07:39:24.202Z"] + actorId: + type: string + description: ID of the Actor that the run was metamorphed to. + examples: [nspoEjklmnsF2oosD] + buildId: + type: string + description: ID of the build used for the metamorphed Actor. + examples: [ME6oKecqy5kXDS4KQ] + inputKey: + type: [string, "null"] + description: Key of the input record in the key-value store. + examples: [INPUT-METAMORPH-1] diff --git a/apify-api/openapi/components/schemas/actor-runs/Run.yaml b/apify-api/openapi/components/schemas/actor-runs/Run.yaml index 017df74365..9b3c8fc65f 100644 --- a/apify-api/openapi/components/schemas/actor-runs/Run.yaml +++ b/apify-api/openapi/components/schemas/actor-runs/Run.yaml @@ -128,3 +128,10 @@ properties: anyOf: - $ref: ./RunUsageUsd.yaml - type: "null" + metamorphs: + description: List of metamorph events that occurred during the run. + anyOf: + - type: array + items: + $ref: ./Metamorph.yaml + - type: "null" diff --git a/apify-api/openapi/components/schemas/actor-runs/RunMeta.yaml b/apify-api/openapi/components/schemas/actor-runs/RunMeta.yaml index 5114049965..a23f070d65 100644 --- a/apify-api/openapi/components/schemas/actor-runs/RunMeta.yaml +++ b/apify-api/openapi/components/schemas/actor-runs/RunMeta.yaml @@ -5,3 +5,16 @@ type: object properties: origin: $ref: ./RunOrigin.yaml + clientIp: + type: [string, "null"] + description: IP address of the client that started the run. + userAgent: + type: [string, "null"] + description: User agent of the client that started the run. + scheduleId: + type: [string, "null"] + description: ID of the schedule that triggered the run. + scheduledAt: + type: [string, "null"] + format: date-time + description: Time when the run was scheduled. diff --git a/apify-api/openapi/components/schemas/actors/CreateOrUpdateVersionRequest.yaml b/apify-api/openapi/components/schemas/actors/CreateOrUpdateVersionRequest.yaml index df03340e27..820bf52b78 100644 --- a/apify-api/openapi/components/schemas/actors/CreateOrUpdateVersionRequest.yaml +++ b/apify-api/openapi/components/schemas/actors/CreateOrUpdateVersionRequest.yaml @@ -21,3 +21,12 @@ properties: examples: [latest] sourceFiles: $ref: ./VersionSourceFiles.yaml + gitRepoUrl: + type: [string, "null"] + description: URL of the Git repository when sourceType is GIT_REPO. + tarballUrl: + type: [string, "null"] + description: URL of the tarball when sourceType is TARBALL. + gitHubGistUrl: + type: [string, "null"] + description: URL of the GitHub Gist when sourceType is GITHUB_GIST. diff --git a/apify-api/openapi/components/schemas/actors/Version.yaml b/apify-api/openapi/components/schemas/actors/Version.yaml index 6012a52a1c..ba6044cdc9 100644 --- a/apify-api/openapi/components/schemas/actors/Version.yaml +++ b/apify-api/openapi/components/schemas/actors/Version.yaml @@ -24,3 +24,12 @@ properties: examples: [latest] sourceFiles: $ref: ./VersionSourceFiles.yaml + gitRepoUrl: + type: [string, "null"] + description: URL of the Git repository when sourceType is GIT_REPO. + tarballUrl: + type: [string, "null"] + description: URL of the tarball when sourceType is TARBALL. + gitHubGistUrl: + type: [string, "null"] + description: URL of the GitHub Gist when sourceType is GITHUB_GIST. diff --git a/apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationError.yaml b/apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationError.yaml index ab2bba21b1..a0cc7ff389 100644 --- a/apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationError.yaml +++ b/apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationError.yaml @@ -1,5 +1,13 @@ title: DatasetSchemaValidationError type: object properties: - error: - $ref: ./DatasetSchemaValidationErrorDetails.yaml + type: + type: string + description: The type of the error. + examples: [schema-validation-error] + message: + type: string + description: A human-readable message describing the error. + examples: [Schema validation failed] + data: + $ref: ./SchemaValidationErrorData.yaml diff --git a/apify-api/openapi/components/schemas/request-queues/RequestDraft.yaml b/apify-api/openapi/components/schemas/request-queues/RequestDraft.yaml index 936899728e..1ee0890a12 100644 --- a/apify-api/openapi/components/schemas/request-queues/RequestDraft.yaml +++ b/apify-api/openapi/components/schemas/request-queues/RequestDraft.yaml @@ -6,6 +6,10 @@ required: - method type: object properties: + id: + type: string + description: A unique identifier assigned to the request. + examples: [sbJ7klsdf7ujN9l] uniqueKey: type: string description: A unique key used for request de-duplication. Requests with the same unique key are considered identical. diff --git a/apify-api/openapi/components/schemas/store/StoreListActor.yaml b/apify-api/openapi/components/schemas/store/StoreListActor.yaml index be762bc69f..33c930c091 100644 --- a/apify-api/openapi/components/schemas/store/StoreListActor.yaml +++ b/apify-api/openapi/components/schemas/store/StoreListActor.yaml @@ -53,3 +53,6 @@ properties: $ref: ../actors/ActorStats.yaml currentPricingInfo: $ref: ./CurrentPricingInfo.yaml + isWhiteListedForAgenticPayment: + type: [boolean, "null"] + description: Whether the Actor is whitelisted for agentic payment processing. From b50631287787a048288c74a4e4b608f9f81affb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Wed, 28 Jan 2026 17:11:53 +0100 Subject: [PATCH 04/11] fix(api): Render LLMButtons directly in ApiItem patch Instead of swizzling ApiItem (which didn't work with the openapi theme), render LLMButtons via the existing patch using a React portal that positions it after the heading element. Co-Authored-By: Claude Opus 4.5 --- apify-docs-theme/src/theme/ApiItem/index.js | 44 -------------- .../src/theme/ApiItem/styles.module.css | 3 - docusaurus.config.js | 2 +- .../docusaurus-theme-openapi-docs+4.7.1.patch | 59 +++++++++++++++++-- 4 files changed, 56 insertions(+), 52 deletions(-) delete mode 100644 apify-docs-theme/src/theme/ApiItem/index.js delete mode 100644 apify-docs-theme/src/theme/ApiItem/styles.module.css diff --git a/apify-docs-theme/src/theme/ApiItem/index.js b/apify-docs-theme/src/theme/ApiItem/index.js deleted file mode 100644 index 86689f4ea2..0000000000 --- a/apify-docs-theme/src/theme/ApiItem/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import ApiItem from '@theme-original/ApiItem'; -import LLMButtons from '@theme/LLMButtons'; -import React, { useEffect, useRef } from 'react'; - -import styles from './styles.module.css'; - -/** - * Wrapper component for the OpenAPI ApiItem that adds LLMButtons. - * This avoids injecting the buttons into the markdown content which would - * pollute search results. - * - * The buttons are rendered into a container that's positioned after the h1 heading - * using a DOM manipulation approach since we can't modify the ApiItem internals. - */ -export default function ApiItemWrapper(props) { - const buttonsRef = useRef(null); - - useEffect(() => { - // Find the h1 heading and insert the buttons container after it - const heading = document.querySelector('.theme-api-markdown .openapi__heading'); - const buttonsContainer = buttonsRef.current; - - if (heading && buttonsContainer) { - // Insert the buttons after the heading - heading.parentNode.insertBefore(buttonsContainer, heading.nextSibling); - } - - // Cleanup: move the buttons back to prevent React warnings - return () => { - if (buttonsContainer && buttonsContainer.parentNode) { - // The container will be removed with the component unmount - } - }; - }, []); - - return ( - <> -
- -
- - - ); -} diff --git a/apify-docs-theme/src/theme/ApiItem/styles.module.css b/apify-docs-theme/src/theme/ApiItem/styles.module.css deleted file mode 100644 index 22c9d8a3da..0000000000 --- a/apify-docs-theme/src/theme/ApiItem/styles.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.llmButtonsContainer { - /* Container is moved via DOM manipulation to after the h1 heading */ -} diff --git a/docusaurus.config.js b/docusaurus.config.js index d57f22c963..3c389633c0 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -2,7 +2,7 @@ const { join, resolve } = require('node:path'); const { parse } = require('node:url'); const clsx = require('clsx'); -const { createApiPageMD, createInfoPageMD } = require('docusaurus-plugin-openapi-docs/lib/markdown'); +const { createApiPageMD } = require('docusaurus-plugin-openapi-docs/lib/markdown'); const { config } = require('./apify-docs-theme'); const { collectSlugs } = require('./tools/utils/collectSlugs'); diff --git a/patches/docusaurus-theme-openapi-docs+4.7.1.patch b/patches/docusaurus-theme-openapi-docs+4.7.1.patch index 7a617e9c03..d8117a8fa6 100644 --- a/patches/docusaurus-theme-openapi-docs+4.7.1.patch +++ b/patches/docusaurus-theme-openapi-docs+4.7.1.patch @@ -123,10 +123,18 @@ index bcebb1c..ac03ec1 100644 }) .flat() // Flatten the array in case of nested arrays from map diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js -index f6052d1..319ebc5 100644 +index f6052d1..15dc342 100644 --- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js +++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js -@@ -47,6 +47,41 @@ function base64ToUint8Array(base64) { +@@ -27,6 +27,7 @@ const slice_1 = require("@theme/ApiExplorer/Authorization/slice"); + const persistenceMiddleware_1 = require("@theme/ApiExplorer/persistenceMiddleware"); + const storage_utils_1 = require("@theme/ApiExplorer/storage-utils"); + const Layout_1 = __importDefault(require("@theme/ApiItem/Layout")); ++const LLMButtons_1 = __importDefault(require("@theme/LLMButtons")); + const CodeBlock_1 = __importDefault(require("@theme/CodeBlock")); + const Metadata_1 = __importDefault(require("@theme/DocItem/Metadata")); + const SkeletonLoader_1 = __importDefault(require("@theme/SkeletonLoader")); +@@ -47,6 +48,81 @@ function base64ToUint8Array(base64) { } return bytes; } @@ -164,15 +172,58 @@ index f6052d1..319ebc5 100644 + "Python" + ) + ); ++} ++// Apify: Component that renders LLMButtons and moves it after the heading ++function LLMButtonsPortal() { ++ const [container, setContainer] = react_1.default.useState(null); ++ react_1.default.useEffect(() => { ++ // Create a container div ++ const div = document.createElement('div'); ++ div.className = 'openapi-llm-buttons'; ++ ++ // Find the heading and insert after it ++ const attemptInsert = () => { ++ const heading = document.querySelector('.openapi__heading'); ++ if (heading && heading.parentNode) { ++ heading.parentNode.insertBefore(div, heading.nextSibling); ++ setContainer(div); ++ return true; ++ } ++ return false; ++ }; ++ ++ // Try immediately, then with delays ++ if (!attemptInsert()) { ++ const timers = [10, 50, 100, 250, 500, 1000].map(delay => ++ setTimeout(attemptInsert, delay) ++ ); ++ return () => timers.forEach(clearTimeout); ++ } ++ ++ return () => { ++ if (div.parentNode) { ++ div.parentNode.removeChild(div); ++ } ++ }; ++ }, []); ++ ++ if (!container) return null; ++ return require("react-dom").createPortal( ++ react_1.default.createElement(LLMButtons_1.default, { isApiReferencePage: true }), ++ container ++ ); +} // @ts-ignore function ApiItem(props) { const docHtmlClassName = `docs-doc-id-${props.content.metadata.id}`; -@@ -158,6 +193,7 @@ function ApiItem(props) { +@@ -158,7 +234,9 @@ function ApiItem(props) { react_1.default.createElement( "div", { className: "col col--7 openapi-left-panel__container" }, +- react_1.default.createElement(MDXComponent, null) + renderClientsBox(api), - react_1.default.createElement(MDXComponent, null) ++ react_1.default.createElement(MDXComponent, null), ++ react_1.default.createElement(LLMButtonsPortal, null) ), react_1.default.createElement( + "div", From 09d67d1d399169918db81c1e752054d49d121ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Wed, 28 Jan 2026 20:58:01 +0100 Subject: [PATCH 05/11] fix(api): Wrap ClientsBox in BrowserOnly to exclude from markdown export The API clients box was appearing in the .md versions of API pages. Wrapping it in BrowserOnly ensures it only renders client-side. Co-Authored-By: Claude Opus 4.5 --- apify-docs-theme/src/theme/custom.css | 5 +++++ patches/docusaurus-theme-openapi-docs+4.7.1.patch | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apify-docs-theme/src/theme/custom.css b/apify-docs-theme/src/theme/custom.css index 5ceb6ec559..ff3efeaa5f 100644 --- a/apify-docs-theme/src/theme/custom.css +++ b/apify-docs-theme/src/theme/custom.css @@ -1844,6 +1844,11 @@ a.client-docs-link { margin-top: 0; } +.theme-api-markdown .openapi-explorer__details-summary:before { + width: 1.6rem; + height: 1.6rem; +} + .theme-api-markdown .prism-code .token-line::before { display: none !important; } diff --git a/patches/docusaurus-theme-openapi-docs+4.7.1.patch b/patches/docusaurus-theme-openapi-docs+4.7.1.patch index d8117a8fa6..775becd78b 100644 --- a/patches/docusaurus-theme-openapi-docs+4.7.1.patch +++ b/patches/docusaurus-theme-openapi-docs+4.7.1.patch @@ -123,7 +123,7 @@ index bcebb1c..ac03ec1 100644 }) .flat() // Flatten the array in case of nested arrays from map diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js -index f6052d1..15dc342 100644 +index f6052d1..867bfd2 100644 --- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js +++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ApiItem/index.js @@ -27,6 +27,7 @@ const slice_1 = require("@theme/ApiExplorer/Authorization/slice"); @@ -221,7 +221,7 @@ index f6052d1..15dc342 100644 "div", { className: "col col--7 openapi-left-panel__container" }, - react_1.default.createElement(MDXComponent, null) -+ renderClientsBox(api), ++ react_1.default.createElement(BrowserOnly_1.default, { fallback: null }, () => renderClientsBox(api)), + react_1.default.createElement(MDXComponent, null), + react_1.default.createElement(LLMButtonsPortal, null) ), From e1cd9879d74979603ac33879df142f73e9fd5624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Thu, 29 Jan 2026 11:23:13 +0100 Subject: [PATCH 06/11] fix(api): Improve response examples display - Show Example tab first (before Schema) - Skip auto-generated example when explicit example exists - Expand first level of nested schema items by default Co-Authored-By: Claude Opus 4.5 --- .../docusaurus-theme-openapi-docs+4.7.1.patch | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/patches/docusaurus-theme-openapi-docs+4.7.1.patch b/patches/docusaurus-theme-openapi-docs+4.7.1.patch index 775becd78b..cc973c691b 100644 --- a/patches/docusaurus-theme-openapi-docs+4.7.1.patch +++ b/patches/docusaurus-theme-openapi-docs+4.7.1.patch @@ -227,3 +227,186 @@ index f6052d1..867bfd2 100644 ), react_1.default.createElement( "div", +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ResponseSchema/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ResponseSchema/index.js +index e84857b..ba445e8 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/ResponseSchema/index.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/ResponseSchema/index.js +@@ -64,6 +64,8 @@ const ResponseSchemaComponent = ({ title, body, style }) => { + ) + ); + } ++ // Apify: Check if there's an explicit example - if so, skip auto-generated ++ const hasExplicitExample = responseExample || responseExamples; + return ( + // @ts-ignore + react_1.default.createElement( +@@ -72,6 +74,25 @@ const ResponseSchemaComponent = ({ title, body, style }) => { + react_1.default.createElement( + SchemaTabs_1.default, + { className: "openapi-tabs__schema" }, ++ // Apify: Put examples first, before schema ++ // Show explicit examples if available ++ responseExample && ++ (0, ResponseExamples_1.ResponseExample)({ ++ responseExample, ++ mimeType, ++ }), ++ responseExamples && ++ (0, ResponseExamples_1.ResponseExamples)({ ++ responseExamples, ++ mimeType, ++ }), ++ // Only show auto-generated example if no explicit example exists ++ !hasExplicitExample && firstBody && ++ (0, ResponseExamples_1.ExampleFromSchema)({ ++ schema: firstBody, ++ mimeType: mimeType, ++ }), ++ // Schema tab comes after examples + react_1.default.createElement( + TabItem_1.default, + { key: title, label: title, value: title }, +@@ -132,22 +153,7 @@ const ResponseSchemaComponent = ({ title, body, style }) => { + }) + ) + ) +- ), +- firstBody && +- (0, ResponseExamples_1.ExampleFromSchema)({ +- schema: firstBody, +- mimeType: mimeType, +- }), +- responseExamples && +- (0, ResponseExamples_1.ResponseExamples)({ +- responseExamples, +- mimeType, +- }), +- responseExample && +- (0, ResponseExamples_1.ResponseExample)({ +- responseExample, +- mimeType, +- }) ++ ) + ) + ) + ); +diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js +index 37190c6..69553f8 100644 +--- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js ++++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js +@@ -304,7 +304,7 @@ const AnyOneOf = ({ schema, schemaType, schemaPath }) => { + ) + ); + }; +-const Properties = ({ schema, schemaType, schemaPath }) => { ++const Properties = ({ schema, schemaType, schemaPath, depth = 0 }) => { + const discriminator = schema.discriminator; + if (discriminator && !discriminator.mapping) { + const anyOneOf = schema.oneOf ?? schema.anyOf ?? {}; +@@ -344,6 +344,7 @@ const Properties = ({ schema, schemaType, schemaPath }) => { + discriminator: discriminator, + schemaType: schemaType, + schemaPath: schemaPath ? `${schemaPath}.${key}` : undefined, ++ depth: depth, // Apify: pass depth + }) + ) + ); +@@ -581,7 +582,10 @@ const SchemaNodeDetails = ({ + required, + schemaType, + schemaPath, ++ depth = 1, // Apify: track nesting depth, default to 1 (first level) + }) => { ++ // Apify: expand first level of nested items (depth 0 or 1) ++ const shouldBeOpen = depth <= 1; + return react_1.default.createElement( + SchemaItem_1.default, + { collapsible: true }, +@@ -589,6 +593,7 @@ const SchemaNodeDetails = ({ + Details_1.default, + { + className: "openapi-markdown__details", ++ open: shouldBeOpen, // Apify: expand first level + summary: react_1.default.createElement(Summary, { + name: name, + schemaName: schemaName, +@@ -611,6 +616,7 @@ const SchemaNodeDetails = ({ + schema: schema, + schemaType: schemaType, + schemaPath: schemaPath, ++ depth: depth + 1, // Apify: increment depth for nested items + }) + ) + ) +@@ -703,6 +709,7 @@ const SchemaEdge = ({ + discriminator, + schemaType, + schemaPath, ++ depth = 1, // Apify: track nesting depth + }) => { + if ( + (schemaType === "request" && schema.readOnly) || +@@ -731,6 +738,7 @@ const SchemaEdge = ({ + schema: schema, + nullable: schema.nullable, + schemaPath: schemaPath, ++ depth: depth, + }); + } + if (schema.properties) { +@@ -742,6 +750,7 @@ const SchemaEdge = ({ + schema: schema, + nullable: schema.nullable, + schemaPath: schemaPath, ++ depth: depth, + }); + } + if (schema.additionalProperties) { +@@ -753,6 +762,7 @@ const SchemaEdge = ({ + schema: schema, + nullable: schema.nullable, + schemaPath: schemaPath, ++ depth: depth, + }); + } + if (schema.items?.properties) { +@@ -764,6 +774,7 @@ const SchemaEdge = ({ + schema: schema, + schemaType: schemaType, + schemaPath: schemaPath, ++ depth: depth, + }); + } + if (schema.items?.anyOf || schema.items?.oneOf || schema.items?.allOf) { +@@ -775,6 +786,7 @@ const SchemaEdge = ({ + schema: schema, + schemaType: schemaType, + schemaPath: schemaPath, ++ depth: depth, + }); + } + if (schema.allOf) { +@@ -813,6 +825,7 @@ const SchemaEdge = ({ + schema: mergedSchemas, + schemaType: schemaType, + schemaPath: schemaPath, ++ depth: depth, + }); + } + if (mergedSchemas.properties !== undefined) { +@@ -824,6 +837,7 @@ const SchemaEdge = ({ + schema: mergedSchemas, + schemaType: schemaType, + schemaPath: schemaPath, ++ depth: depth, + }); + } + if (mergedSchemas.items?.properties) { +@@ -835,6 +849,7 @@ const SchemaEdge = ({ + schema: mergedSchemas, + schemaType: schemaType, + schemaPath: schemaPath, ++ depth: depth, + }); + } + return react_1.default.createElement(SchemaItem_1.default, { From 4956ca552d04eb91f167add3baa85c1a63522808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Thu, 29 Jan 2026 11:26:13 +0100 Subject: [PATCH 07/11] refactor: Use shorthand property syntax for depth Co-Authored-By: Claude Opus 4.5 --- .../docusaurus-theme-openapi-docs+4.7.1.patch | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/patches/docusaurus-theme-openapi-docs+4.7.1.patch b/patches/docusaurus-theme-openapi-docs+4.7.1.patch index cc973c691b..ba46312c37 100644 --- a/patches/docusaurus-theme-openapi-docs+4.7.1.patch +++ b/patches/docusaurus-theme-openapi-docs+4.7.1.patch @@ -291,7 +291,7 @@ index e84857b..ba445e8 100644 ) ); diff --git a/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js b/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js -index 37190c6..69553f8 100644 +index 37190c6..76766ef 100644 --- a/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js +++ b/node_modules/docusaurus-theme-openapi-docs/lib/theme/Schema/index.js @@ -304,7 +304,7 @@ const AnyOneOf = ({ schema, schemaType, schemaPath }) => { @@ -307,7 +307,7 @@ index 37190c6..69553f8 100644 discriminator: discriminator, schemaType: schemaType, schemaPath: schemaPath ? `${schemaPath}.${key}` : undefined, -+ depth: depth, // Apify: pass depth ++ depth, // Apify: pass depth }) ) ); @@ -334,7 +334,7 @@ index 37190c6..69553f8 100644 schema: schema, schemaType: schemaType, schemaPath: schemaPath, -+ depth: depth + 1, // Apify: increment depth for nested items ++ depth: depth + 1, }) ) ) @@ -350,7 +350,7 @@ index 37190c6..69553f8 100644 schema: schema, nullable: schema.nullable, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } if (schema.properties) { @@ -358,7 +358,7 @@ index 37190c6..69553f8 100644 schema: schema, nullable: schema.nullable, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } if (schema.additionalProperties) { @@ -366,7 +366,7 @@ index 37190c6..69553f8 100644 schema: schema, nullable: schema.nullable, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } if (schema.items?.properties) { @@ -374,7 +374,7 @@ index 37190c6..69553f8 100644 schema: schema, schemaType: schemaType, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } if (schema.items?.anyOf || schema.items?.oneOf || schema.items?.allOf) { @@ -382,7 +382,7 @@ index 37190c6..69553f8 100644 schema: schema, schemaType: schemaType, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } if (schema.allOf) { @@ -390,7 +390,7 @@ index 37190c6..69553f8 100644 schema: mergedSchemas, schemaType: schemaType, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } if (mergedSchemas.properties !== undefined) { @@ -398,7 +398,7 @@ index 37190c6..69553f8 100644 schema: mergedSchemas, schemaType: schemaType, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } if (mergedSchemas.items?.properties) { @@ -406,7 +406,7 @@ index 37190c6..69553f8 100644 schema: mergedSchemas, schemaType: schemaType, schemaPath: schemaPath, -+ depth: depth, ++ depth, }); } return react_1.default.createElement(SchemaItem_1.default, { From b04077259fe8e13ef1fc2ef71c1a192fe620f8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Thu, 29 Jan 2026 11:26:49 +0100 Subject: [PATCH 08/11] chore: Exclude patches directory from typos check Patch files contain git hashes that trigger false positives. Co-Authored-By: Claude Opus 4.5 --- typos.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/typos.toml b/typos.toml index 9a36a2d6b6..bf2696b1e2 100644 --- a/typos.toml +++ b/typos.toml @@ -13,6 +13,7 @@ extend-exclude = [ "*.min.js", "*.min.css", "CHANGELOG.md", + "patches/", ] # Add project-specific identifiers that should not be treated as typos From 617a7950c8621613e604b630b56e6bef8463cefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Thu, 29 Jan 2026 12:05:12 +0100 Subject: [PATCH 09/11] docs: Document OpenAPI + Redocly workflow in contributing guide Closes #1174 Co-Authored-By: Claude Opus 4.5 --- CONTRIBUTING.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 940073d2fc..f986feba31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,18 +190,22 @@ Content lives in the following locations: ### Overview -The API reference documentation at [docs.apify.com](https://docs.apify.com/) is built directly from our OpenAPI specification. The source of truth for the API specification lives in the `/apify-api/openapi` directory within [apify-docs](https://github.com/apify/apify-docs) repository. +The API reference documentation at [docs.apify.com/api/v2](https://docs.apify.com/api/v2) is built from our OpenAPI specification. The source of truth lives in the `/apify-api/openapi` directory. -### Setup requirements +### Tooling -1. Install Node.js -2. Clone the repository -3. Run `npm install` +We use the following tools for API documentation: + +- **[OpenAPI 3.0](https://spec.openapis.org/oas/v3.0.3)** - API specification format +- **[Redocly CLI](https://redocly.com/docs/cli/)** - Linting and validation of OpenAPI specs +- **[docusaurus-plugin-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs)** - Generates MDX docs from OpenAPI +- **[docusaurus-theme-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs)** - Renders API reference with interactive explorer ### Basic commands - `npm start` - Starts docs preview server including API reference -- `npm test` - Validates the definition +- `npm run openapi:lint:redocly` - Validates OpenAPI spec with Redocly CLI +- `npm run api:rebuild` - Regenerates API docs from OpenAPI specs ### Adding new documentation From 3d98d930cd230a405f827961ae5bd8ba71ef20cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Thu, 29 Jan 2026 12:10:00 +0100 Subject: [PATCH 10/11] fix: Use backticks for package names to satisfy Vale linter Co-Authored-By: Claude Opus 4.5 --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f986feba31..53fc713230 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -198,8 +198,8 @@ We use the following tools for API documentation: - **[OpenAPI 3.0](https://spec.openapis.org/oas/v3.0.3)** - API specification format - **[Redocly CLI](https://redocly.com/docs/cli/)** - Linting and validation of OpenAPI specs -- **[docusaurus-plugin-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs)** - Generates MDX docs from OpenAPI -- **[docusaurus-theme-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs)** - Renders API reference with interactive explorer +- **[`docusaurus-plugin-openapi-docs`](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs)** - Generates MDX docs from OpenAPI +- **[`docusaurus-theme-openapi-docs`](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs)** - Renders API reference with interactive explorer ### Basic commands From e0b175cb1c32be9bcddd16c5e0f35f2f35932e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Thu, 29 Jan 2026 12:31:02 +0100 Subject: [PATCH 11/11] chore: Remove orphaned DatasetSchemaValidationErrorDetails schema Co-Authored-By: Claude Opus 4.5 --- .../DatasetSchemaValidationErrorDetails.yaml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationErrorDetails.yaml diff --git a/apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationErrorDetails.yaml b/apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationErrorDetails.yaml deleted file mode 100644 index d130a0bd70..0000000000 --- a/apify-api/openapi/components/schemas/datasets/DatasetSchemaValidationErrorDetails.yaml +++ /dev/null @@ -1,17 +0,0 @@ -title: DatasetSchemaValidationErrorDetails -required: - - type - - message - - data -type: object -properties: - type: - type: string - description: The type of the error. - examples: [schema-validation-error] - message: - type: string - description: A human-readable message describing the error. - examples: [Schema validation failed] - data: - $ref: ./SchemaValidationErrorData.yaml