docs(openapi): Autofix OpenAPI spec validation errors#2517
Draft
Pijukatel wants to merge 4 commits into
Draft
Conversation
Error: Response OpenAPI validation error {"url":"/v2/acts/{actorId}/runs","method":"POST","statusCode":403,"errors":[{"message":"must be equal to one of the allowed values: ...","errorCode":"enum.openapi.validation","path":"/response/error/type"}]}
Files: apify-api/openapi/components/schemas/common/ErrorType.yaml:129
Root cause: Error type "full-permission-actor-not-approved" is returned by the API for actors that require full account access but were not yet approved (HTTP 403), but the type was missing from the ErrorType enum.
Reference: https://github.com/apify/apify-core/tree/19160bcbc2da9e8e242227fea757c953a37ee797/src/packages/errors/src/errors/actor.ts#L61
Error: Response OpenAPI validation error {"url":"/v2/schedules","method":"POST","statusCode":400,"errors":[{"message":"must be equal to one of the allowed values: ...","errorCode":"enum.openapi.validation","path":"/response/error/type"}]}
Files: apify-api/openapi/components/schemas/common/ErrorType.yaml:301
Root cause: Error type "run-input-body-not-valid-json" is returned by the API when the schedule run input body contains invalid JSON (HTTP 400), but the type was missing from the ErrorType enum.
Reference: https://github.com/apify/apify-core/tree/19160bcbc2da9e8e242227fea757c953a37ee797/src/packages/errors/src/errors/scheduling.ts#L40
Error: Response OpenAPI validation error {"url":"/v2/acts/{actorId}/builds/default","method":"GET","statusCode":403,"errors":[{"message":"must be equal to constant","errorCode":"const.openapi.validation","path":"/response/error/type"}]}
Files: apify-api/openapi/paths/actors/acts@{actorId}@builds@default.yaml:28
Root cause: The 403 response schema only allowed "unknown-build-tag" as a const, but the endpoint can also return "insufficient-permissions" from IAM access checks plus "build-not-found", "build-outdated" and "invalid-build" from the build lookup. Replaced the narrow custom schema with the standard Forbidden response that uses the full ErrorType enum.
Reference: https://github.com/apify/apify-core/tree/19160bcbc2da9e8e242227fea757c953a37ee797/src/api/src/routes/actors/build_default.ts#L30
Error: Request OpenAPI validation error {"url":"/v2/actor-tasks/{actorTaskId}/runs/last/abort","method":"POST","errors":[{"message":"not found","path":"/actor-runs/{runId}/abort/"}]}
Files: apify-api/openapi/components/objects/actor-runs/abort.yaml, apify-api/openapi/paths/actors/acts@{actorId}@Runs@last@abort.yaml, apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@Runs@last@abort.yaml, apify-api/openapi/openapi.yaml
Root cause: The runs/last/abort endpoint was missing from the OpenAPI spec for both Actors and Actor tasks. The API rewrites runs/last/{action} to /v2/actor-runs/{runId}/{action}/ via middleware, so the validator did not match either the original URL or the rewritten one.
Reference: https://github.com/apify/apify-core/tree/19160bcbc2da9e8e242227fea757c953a37ee797/src/api/src/lib/controllers.ts#L38
Error: Request OpenAPI validation error {"url":"/v2/actor-tasks/{actorTaskId}/runs/last/metamorph","method":"POST","errors":[{"message":"not found","path":"/actor-runs/{runId}/metamorph/"}]}
Files: apify-api/openapi/components/objects/actor-runs/metamorph.yaml, apify-api/openapi/paths/actors/acts@{actorId}@Runs@last@metamorph.yaml, apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@Runs@last@metamorph.yaml, apify-api/openapi/openapi.yaml
Root cause: The runs/last/metamorph endpoint was missing from the OpenAPI spec for both Actors and Actor tasks. The API rewrites runs/last/{action} to /v2/actor-runs/{runId}/{action}/ via middleware, so the validator did not match either the original URL or the rewritten one.
Reference: https://github.com/apify/apify-core/tree/19160bcbc2da9e8e242227fea757c953a37ee797/src/api/src/lib/controllers.ts#L39
Error: Request OpenAPI validation error {"url":"/v2/acts/{actorId}/runs/last/reboot","method":"POST","errors":[{"message":"not found","path":"/actor-runs/{runId}/reboot/"}]}
Files: apify-api/openapi/components/objects/actor-runs/reboot.yaml, apify-api/openapi/paths/actors/acts@{actorId}@Runs@last@reboot.yaml, apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@Runs@last@reboot.yaml, apify-api/openapi/openapi.yaml
Root cause: The runs/last/reboot endpoint was missing from the OpenAPI spec for both Actors and Actor tasks. The API rewrites runs/last/{action} to /v2/actor-runs/{runId}/{action}/ via middleware, so the validator did not match either the original URL or the rewritten one.
Reference: https://github.com/apify/apify-core/tree/19160bcbc2da9e8e242227fea757c953a37ee797/src/api/src/lib/controllers.ts#L40
The new endpoints follow the same shared-component pattern as components/objects/logs/log.yaml: a YAML anchor for the common response shape and named operations merged via "<<: *anchor", referenced from each path file.
…t} endpoints The previous commit introduced operation tags "Actors/Last run" and "Actor tasks/Last run" without declaring them in components/tags.yaml, which triggered the spectral "operation-tag-defined" warning. Switched to the existing "Last Actor run's X" / "Last Actor task run's X" naming convention used by all other runs/last/* convenience endpoints, added six new top-level tag definitions in components/tags.yaml, and listed them in components/x-tag-groups.yaml under "Convenience endpoints" right next to the matching "log", "default dataset", "default key-value store", and "default request queue" tags. No spec semantics change; this only fixes the lint warning.
… components
Refactored actor-runs@{runId}@{abort,metamorph,reboot,resurrect,charge}.yaml
and acts@{actorId}@Runs@{runId}@{abort,metamorph,resurrect}.yaml path files
into thin "$ref" wrappers that point to operations defined in
components/objects/actor-runs/{abort,metamorph,reboot,resurrect,charge}.yaml.
Pattern follows the existing components/objects/logs/log.yaml example:
- Each shared file declares a "&sharedX" YAML anchor for the common
response shape.
- Named operations ("byRunId", "byActorRunId", "lastRunByActor",
"lastRunByActorTask") merge the anchor and add their own metadata.
- charge.yaml has only "byRunId" because /v2/actor-runs/{runId}/charge
is the only path the API exposes for charging.
actor-tasks has no equivalent /actor-tasks/{actorTaskId}/runs/{runId}/X
endpoints in apify-core (only the runs/last router), so nothing to
refactor there.
Net change: 437 lines deleted, 159 lines added; 278 lines of duplication
removed. Bundle output is functionally unchanged for all 13 affected
operations - only one cosmetic typo fix (missing space in the deprecated
metamorph description) and one schema-shape consistency tweak (resurrect
200 now uses allOf wrapping in both variants).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Autogenerated OpenAPI fixes suggestions based on validation errors generated from running API integration tests with OpenAPI validator turned on.
Error log: https://apify-pr-test-env-logs.s3.us-east-1.amazonaws.com/apify/apify-core/27741/api-56afaaf4cd4c0b4255dbf6603b799eeb96d3a5d8.log
apify-core version: https://github.com/apify/apify-core/commit/19160bcbc2da9e8e242227fea757c953a37ee797
Stop reason: Iteration in progress; pending verification of the third commit (refactoring of existing actor-runs and acts run-action paths into the new shared components).
Detailed changes description
Error fixes
Add
full-permission-actor-not-approvedto ErrorType enumapify-api/openapi/components/schemas/common/ErrorType.yaml:129Response OpenAPI validation error {"url":"/v2/acts/{actorId}/runs","method":"POST","statusCode":403,"errors":[{"message":"must be equal to one of the allowed values: ...","errorCode":"enum.openapi.validation","path":"/response/error/type"}]}full-permission-actor-not-approved(HTTP 403) when an Actor requires full account access but the user has not approved the permissions, but the type was missing from theErrorTypeenum used by all error responses.Add
run-input-body-not-valid-jsonto ErrorType enumapify-api/openapi/components/schemas/common/ErrorType.yaml:301Response OpenAPI validation error {"url":"/v2/schedules","method":"POST","statusCode":400,"errors":[{"message":"must be equal to one of the allowed values: ...","errorCode":"enum.openapi.validation","path":"/response/error/type"}]}run-input-body-not-valid-json(HTTP 400) when a schedule's run input body contains invalid JSON, but the type was missing from theErrorTypeenum used by all error responses.Broaden 403 response of GET /v2/acts/{actorId}/builds/default
apify-api/openapi/paths/actors/acts@{actorId}@builds@default.yaml:28Response OpenAPI validation error {"url":"/v2/acts/{actorId}/builds/default","method":"GET","statusCode":403,"errors":[{"message":"must be equal to constant","errorCode":"const.openapi.validation","path":"/response/error/type"}]}unknown-build-tagerror type as a const, but the endpoint can also returninsufficient-permissionsfrom IAM access checks plusbuild-not-found,build-outdatedandinvalid-buildfrom the build lookup. Replaced the narrow custom schema with the standardForbiddenresponse that uses the fullErrorTypeenum, so all valid error types for this endpoint validate correctly.Document POST /v2/acts/{actorId}/runs/last/abort and POST /v2/actor-tasks/{actorTaskId}/runs/last/abort
apify-api/openapi/components/objects/actor-runs/abort.yaml,apify-api/openapi/paths/actors/acts@{actorId}@runs@last@abort.yaml,apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@runs@last@abort.yaml,apify-api/openapi/openapi.yaml:550,583Request OpenAPI validation error {"url":"/v2/actor-tasks/{actorTaskId}/runs/last/abort","method":"POST","errors":[{"message":"not found","path":"/actor-runs/{runId}/abort/"}]}runs/last/{action}via middleware to/v2/actor-runs/{runId}/{action}/, so the validator did not match either the original URL or the rewritten one. Added documentation following the shared-component pattern fromcomponents/objects/logs/log.yaml(YAML anchor for shared response shape, named operations referenced from each path file).Document POST /v2/acts/{actorId}/runs/last/metamorph and POST /v2/actor-tasks/{actorTaskId}/runs/last/metamorph
apify-api/openapi/components/objects/actor-runs/metamorph.yaml,apify-api/openapi/paths/actors/acts@{actorId}@runs@last@metamorph.yaml,apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@runs@last@metamorph.yaml,apify-api/openapi/openapi.yaml:552,585Request OpenAPI validation error {"url":"/v2/actor-tasks/{actorTaskId}/runs/last/metamorph","method":"POST","errors":[{"message":"not found","path":"/actor-runs/{runId}/metamorph/"}]}Document POST /v2/acts/{actorId}/runs/last/reboot and POST /v2/actor-tasks/{actorTaskId}/runs/last/reboot
apify-api/openapi/components/objects/actor-runs/reboot.yaml,apify-api/openapi/paths/actors/acts@{actorId}@runs@last@reboot.yaml,apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@runs@last@reboot.yaml,apify-api/openapi/openapi.yaml:554,587Request OpenAPI validation error {"url":"/v2/acts/{actorId}/runs/last/reboot","method":"POST","errors":[{"message":"not found","path":"/actor-runs/{runId}/reboot/"}]}Refactoring
apify-api/openapi/components/objects/actor-runs/{abort,metamorph,reboot,resurrect,charge}.yamlshared-component files. Each file uses a YAML anchor (e.g.&sharedAbort) for the common response shape and exposes named operations (byRunId,byActorRunId,lastRunByActor,lastRunByActorTask) reused via<<: *anchor.chargeonly hasbyRunIdbecause/v2/actor-runs/{runId}/chargeis the only path the API exposes for charging.actor-runs@{runId}@{abort,metamorph,reboot,resurrect,charge}.yaml,acts@{actorId}@runs@{runId}@{abort,metamorph,resurrect}.yaml) into thin$refwrappers pointing at the shared operations. Net diff: 278 lines of duplication removed (437 lines deleted, 159 lines added across the refactor commit). Bundled spec output is functionally unchanged for all 13 affected operations.Last Actor run's {abort,metamorph,reboot},Last Actor task run's {abort,metamorph,reboot}) inapify-api/openapi/components/tags.yamland listed them inapify-api/openapi/components/x-tag-groups.yamlunder the existing Convenience endpoints group, alongside the analogouslog/default dataset/default key-value store/default request queuetags.actor-taskswas reviewed for the same refactoring strategy: there are no/v2/actor-tasks/{actorTaskId}/runs/{runId}/{action}direct endpoints inapify-core/src/api/src/routes/routes_config.ts(onlyruns/last*via the lastRunRouter), so no further refactor is needed there.Unfixed errors
False positives
The following errors were not fixed because they are known false positives of the validator that incorrectly raises errors when a
string, format: date-timefield is allowed to benull(documented intest-OpenAPI-autofixes/CLAUDE.md).Nullable
taggedBuilds[*].finishedAtResponse OpenAPI validation error {"url":"/v2/acts/{actorId}",...,"errors":[{"message":"must be string,null","errorCode":"type.openapi.validation","path":"/response/data/taggedBuilds/{tag}/finishedAt"}]}nullfor a date-time string even when the schema declarestype: [string, "null"]withformat: date-time. Cascading errors on the parenttaggedBuilds.{tag}andtaggedBuildsanyOfare caused by the same root.Nullable
lastDispatch.finishedAt(webhooks)Response OpenAPI validation error {"url":"/v2/webhooks/{webhookId}",...,"errors":[{"message":"must be string,null","errorCode":"type.openapi.validation","path":"/response/data/lastDispatch/finishedAt"}]}lastDispatchanyOfare caused by the same root.Nullable
nextRunAtandlastRunAt(schedules)Response OpenAPI validation error {"url":"/v2/schedules/{scheduleId}",...,"errors":[{"message":"must be string,null","errorCode":"type.openapi.validation","path":"/response/data/nextRunAt"}]}Nullable
finishedAt(build abort and run abort)Response OpenAPI validation error {"url":"/v2/actor-builds/{buildId}/abort","method":"POST","statusCode":200,"errors":[{"message":"must be string,null","errorCode":"type.openapi.validation","path":"/response/data/finishedAt"}]}andResponse OpenAPI validation error {"url":"/v2/actor-tasks/{actorTaskId}/runs/last/abort","method":"POST","statusCode":200,"errors":[{"message":"must be string,null","errorCode":"type.openapi.validation","path":"/response/data/finishedAt"}]}runs/last/abortonly surfaced after the endpoint was added in this PR — previously the validator could not match the URL at all and skipped response validation; now it validates and reports the same false positive that already affects theactor-builds/{buildId}/abortendpoint.Nullable
calls[*].startedAtandcalls[*].finishedAt(webhook dispatches)Response OpenAPI validation error {"url":"/v2/webhook-dispatches/{dispatchId}","method":"GET","statusCode":200,"errors":[{"message":"must be string,null","errorCode":"type.openapi.validation","path":"/response/data/calls/0/startedAt"},{"message":"must be string,null","errorCode":"type.openapi.validation","path":"/response/data/calls/0/finishedAt"}]}Out of scope errors
methodquery parameter validation noiseRequest OpenAPI validation error {"url":"/v2/...?...&method=...",...,"errors":[{"message":"Unknown query parameter 'method'","path":"/query/method"}]}methodquery parameter for HTTP method override (documented in the spec description and implemented as middleware), but it is not declared as a parameter on individual paths. Adding a global parameter to all paths is a structural refactor outside the scope of a per-error fix.Tests that intentionally send malformed requests
Request OpenAPI validation errorfor endpoints where tests deliberately send invalidgeneralAccess, malformedhandledAt, missing required fields, unsupportedContent-Type, invalid filter values, invalid event types, OPTIONS method, etc.Issues
Partially implements: #2286