Commit 83a597c
docs(openapi): Autofix OpenAPI spec validation errors (#2390)
## 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/26280/api-340d97f6d1b466610d748a7b06d60012959b360e.html
**apify-core version:**
apify/apify-core@340d97f
**Stop reason:** 20 total errors fixed.
## Detailed changes description
### Error fixes
#### Add 200 response to POST /v2/datasets
**Files:** `apify-api/openapi/paths/datasets/datasets.yaml:83`
**Error:**
`{"url":"/v2/datasets?name=...","method":"POST","errors":[{"message":"no
schema defined for status code '200' in the openapi spec"}]}`
**Root cause:** The dataset creation endpoint uses get-or-create
semantics. When a dataset with the given name already exists, the API
returns HTTP 200 with the existing dataset object instead of 201. The
spec only documented the 201 (newly created) response.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/api/src/routes/datasets/dataset_list.ts#L114
#### Add 200 response to POST /v2/key-value-stores
**Files:**
`apify-api/openapi/paths/key-value-stores/key-value-stores.yaml:90`
**Error:**
`{"url":"/v2/key-value-stores?name=...","method":"POST","errors":[{"message":"no
schema defined for status code '200' in the openapi spec"}]}`
**Root cause:** Same get-or-create semantics as datasets. The spec was
missing the 200 response for the case where the named store already
exists.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/api/src/routes/key_value_stores/store_list.ts#L108
#### Add 200 response to POST /v2/request-queues
**Files:**
`apify-api/openapi/paths/request-queues/request-queues.yaml:86`
**Error:**
`{"url":"/v2/request-queues?name=...","method":"POST","errors":[{"message":"no
schema defined for status code '200' in the openapi spec"}]}`
**Root cause:** Same get-or-create semantics. The spec was missing the
200 response for the case where the named queue already exists.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/api/src/routes/request_queues/queue_list.ts#L113
#### Add 402 response to POST /v2/acts/{actorId}/runs
**Files:** `apify-api/openapi/paths/actors/acts@{actorId}@runs.yaml:144`
**Error:**
`{"url":"/v2/acts/.../runs?maxItems=100","method":"POST","errors":[{"message":"no
schema defined for status code '402' in the openapi spec"}]}`
**Root cause:** Starting an Actor run can fail with 402 when the user
exceeds memory limits, concurrent run limits, or does not have enough
usage credits (`memoryLimitExceededFreeUser`,
`memoryLimitExceededPayingUser`, `maxConcurrentRunsExceeded*`,
`notEnoughUsageToRunPaidActor`). The spec was missing the 402 response.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/errors/src/errors/actor.ts#L5
#### Add 409 response to POST /v2/actor-tasks
**Files:** `apify-api/openapi/paths/actor-tasks/actor-tasks.yaml:145`
**Error:**
`{"url":"/v2/actor-tasks","method":"POST","errors":[{"message":"no
schema defined for status code '409' in the openapi spec"}]}`
**Root cause:** Creating a task with a name that already exists throws
`actorTaskNameExists` which returns HTTP 409. The spec was missing the
409 Conflict response.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/errors/src/errors/actor_task.ts#L3
#### Add 415 response to POST /v2/acts/{actorId}/run-sync
**Files:**
`apify-api/openapi/paths/actors/acts@{actorId}@run-sync.yaml:76`
**Error:**
`{"url":"/v2/acts/.../run-sync?token=[REDACTED]","method":"POST","errors":[{"message":"no
schema defined for status code '415' in the openapi spec"}]}`
**Root cause:** A global API middleware converts `encoding.unsupported`
body-parser errors into `unsupportedContentEncoding` (415). Since
run-sync accepts a `requestBody`, it is subject to this middleware. The
spec was missing the 415 Unsupported Media Type response.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/errors/src/errors/api.ts#L228
#### Add 403 response to GET
/v2/acts/{actorId}/builds/{buildId}/openapi.json
**Files:**
`apify-api/openapi/paths/actors/acts@{actorId}@builds@{buildId}@openapi.json.yaml:38`
**Error:**
`{"url":"/v2/acts/.../builds/default/openapi.json","method":"GET","errors":[{"message":"no
schema defined for status code '403' in the openapi spec"}]}`
**Root cause:** `getAct2BuildFromVersionOrTag` throws 403 when the build
tag is unknown (`unknownBuildTag`) or the build version is not found
(`buildNotFound`). This is intentionally 403 rather than 404 for
backwards compatibility. The spec was missing the 403 response.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/actor-server/src/actor_jobs/actor_builds.server.ts#L48
#### Fix Run.yaml buildNumber to allow null
**Files:**
`apify-api/openapi/components/schemas/actor-runs/Run.yaml:134`
**Error:**
`{"url":"/v2/actor-runs/.../abort","method":"POST","errors":[{"message":"must
be
string","errorCode":"type.openapi.validation","path":"/response/data/buildNumber"}]}`
**Root cause:** `buildNumber` is typed as `string | null` in
`TransformedActorRunFields`. It is null when
`buildOrVersionNumberIntToStr(run.buildNumberInt)` cannot convert the
integer. The spec defined it as `type: string` without allowing null.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/types/src/actor.ts#L707
#### Fix RunStats.yaml inputBodyLen to allow null
**Files:**
`apify-api/openapi/components/schemas/actor-runs/RunStats.yaml:8`
**Error:**
`{"url":"/v2/actor-runs/.../abort","method":"POST","errors":[{"message":"must
be
integer","errorCode":"type.openapi.validation","path":"/response/data/stats/inputBodyLen"}]}`
**Root cause:** `inputBodyLen` is typed as `number | null`. It is
explicitly set to `null` when there is no input body (`effectiveInput &&
effectiveInput.body ? effectiveInput.body.length : null`). The spec
defined it as `type: integer` without allowing null.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/types/src/actor.ts#L369
#### Add body schema to POST /v2/actor-runs/{runId}/charge 201 response
**Files:**
`apify-api/openapi/paths/actor-runs/actor-runs@{runId}@charge.yaml:37`
**Error:**
`{"url":"/v2/actor-runs/.../charge?token=[REDACTED]","method":"POST","errors":[{"message":"response
should NOT have a body","path":"/response"}]}`
**Root cause:** The charge endpoint returns
`res.status(statusCode).json({})` — an empty JSON object. The spec
defined the 201 response with no `content`, causing the validator to
flag the empty body as unexpected. Added `content: application/json:
schema: type: object` to match the actual response.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/api/src/routes/actor_runs/run_charge.ts#L40
#### Fix actor endpoint 404 responses to use correct error types
**Files:** `apify-api/openapi/paths/actors/acts@{actorId}.yaml:83`
(GET), `:265` (PUT), `:310` (DELETE); `acts@{actorId}@builds.yaml:36`;
`acts@{actorId}@runs.yaml:43`; `acts@{actorId}@versions.yaml:54`;
`acts@{actorId}@versions@{versionNumber}.yaml:25`;
`acts@{actorId}@webhooks.yaml:34`;
`acts@{actorId}@builds@default.yaml:35`
**Error:**
`{"url":"/v2/acts/.../runs","method":"GET","errors":[{"message":"must be
equal to one of the allowed values:
actor-not-found","errorCode":"enum.openapi.validation","path":"/response/error/type"}]}`
**Root cause:** All actor endpoints used `ActorNotFoundError` (enum:
`actor-not-found`) for 404 responses. However the API returns
`record-or-token-not-found` (via `getPublicActor` →
`recordNotFoundOrAccessDenied`) for direct 16-char IDs and
`record-not-found` (via `convertActorNameToId` →
`common.recordNotFound`) for name-format IDs. The generic
`NotFound.yaml` (no enum constraint on `type`) covers both cases.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/api/src/lib/actors.ts#L132
#### Fix EnvVar.yaml: make value field optional
**Files:** `apify-api/openapi/components/schemas/actors/EnvVar.yaml:2`
**Error:**
`{"url":"/v2/acts/.../versions/0.0","method":"GET","errors":[{"message":"must
have required property
'value'","errorCode":"required.openapi.validation","path":"/response/data/envVars/1/value"}]}`
**Root cause:** `ActorEnvVar.value` is typed as `value?: string`
(optional) because the value is removed from the response for secret env
vars. The spec incorrectly marked `value` as required.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/types/src/actor.ts#L59
### Refactoring
No refactoring changes in this batch.
## Unfixed errors
### False positives
#### taggedBuilds null/anyOf errors on GET/PUT /v2/acts/{actorId}
**Error:**
`{"url":"/v2/acts/{actorId}","method":"GET","errors":[{"message":"must
be
null","errorCode":"type.openapi.validation","path":"/response/data/taggedBuilds"},{"message":"must
match a schema in
anyOf","errorCode":"anyOf.openapi.validation","path":"/response/data/taggedBuilds"}]}`
**Root cause:** The `taggedBuilds` field is defined as `anyOf: [{$ref:
TaggedBuilds}, {type: "null"}]` which is valid OpenAPI 3.1. The
express-openapi-validator incorrectly raises errors when the value is
null for fields using multi-type definitions. This is a known validator
false positive.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/types/src/actor.ts#L672
#### lastDispatch null/anyOf errors on PUT /v2/webhooks/{webhookId}
**Error:**
`{"url":"/v2/webhooks/{webhookId}","method":"PUT","errors":[{"message":"must
be
null","errorCode":"type.openapi.validation","path":"/response/data/lastDispatch"},{"message":"must
match a schema in
anyOf","errorCode":"anyOf.openapi.validation","path":"/response/data/lastDispatch"}]}`
**Root cause:** Same false positive pattern — `lastDispatch` uses
`anyOf: [{$ref: ExampleWebhookDispatch}, {type: "null"}]`. The validator
incorrectly errors on null values for anyOf nullable fields.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/types/src/webhooks.ts#L103
#### pricingInfos/pricingInfo missing required properties on actor and
actor-run endpoints
**Error:**
`{"url":"/v2/acts/.../","method":"GET","errors":[{"message":"must have
required property
'pricePerUnitUsd'","errorCode":"required.openapi.validation","path":"/response/data/pricingInfos/0/pricePerUnitUsd"}]}`
**Root cause:** `pricingInfos` uses `oneOf` with a `discriminator` on
`pricingModel`. When `pricingModel` is `FREE`, only
`FreeActorPricingInfo` should be matched, but express-openapi-validator
does not correctly handle the discriminator and reports missing required
fields from other `oneOf` branches (`pricePerUnitUsd` from
`PricePerDatasetItemActorPricingInfo`, `pricingPerEvent` from
`PayPerEventActorPricingInfo`). This is a known validator false positive
with discriminator + oneOf.
**Reference:**
https://github.com/apify/apify-core/blob/340d97f6d1b466610d748a7b06d60012959b360e/src/packages/types/src/actor.ts#L158
## Issues
Partially implements: #2286
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d434637 commit 83a597c
File tree
22 files changed
+60
-53
lines changed- apify-api/openapi
- components
- objects/actors
- responses
- schemas
- actor-runs
- actors
- paths
- actor-runs
- actor-tasks
- actors
- datasets
- key-value-stores
- request-queues
22 files changed
+60
-53
lines changedLines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
Lines changed: 9 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
| 135 | + | |
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | 4 | | |
6 | 5 | | |
7 | 6 | | |
8 | 7 | | |
9 | 8 | | |
10 | 9 | | |
11 | 10 | | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
39 | 44 | | |
40 | 45 | | |
41 | 46 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
142 | 142 | | |
143 | 143 | | |
144 | 144 | | |
| 145 | + | |
| 146 | + | |
145 | 147 | | |
146 | 148 | | |
147 | 149 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
| 84 | + | |
89 | 85 | | |
90 | 86 | | |
91 | 87 | | |
| |||
269 | 265 | | |
270 | 266 | | |
271 | 267 | | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
| 268 | + | |
277 | 269 | | |
278 | 270 | | |
279 | 271 | | |
| |||
318 | 310 | | |
319 | 311 | | |
320 | 312 | | |
321 | | - | |
322 | | - | |
323 | | - | |
324 | | - | |
325 | | - | |
| 313 | + | |
326 | 314 | | |
327 | 315 | | |
328 | 316 | | |
| |||
0 commit comments