Skip to content

Commit 83a597c

Browse files
Pijukatelclaude
andauthored
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

22 files changed

+60
-53
lines changed

apify-api/openapi/components/objects/actors/acts@{actorId}@versions@{versionNumber}@env-vars@{envVarName}put.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ shared: &shared
1717
content:
1818
application/json:
1919
schema:
20-
$ref: ../../schemas/actors/EnvVar.yaml
20+
$ref: ../../schemas/actors/EnvVarRequest.yaml
2121
example:
2222
name: MY_ENV_VAR
2323
value: my-new-value
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
description: Payment required - the user has exceeded their usage limit or does not have enough credits.
2+
content:
3+
application/json:
4+
schema:
5+
$ref: ../schemas/common/ErrorResponse.yaml
6+
example:
7+
error:
8+
type: actor-memory-limit-exceeded
9+
message: You have exceeded your usage limit. Please consider upgrading your plan.

apify-api/openapi/components/schemas/actor-runs/Run.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ properties:
132132
additionalProperties:
133133
type: string
134134
buildNumber:
135-
type: string
135+
type: [string, "null"]
136136
examples: [0.0.36]
137137
description: Build number of the Actor build used for this run.
138138
containerUrl:

apify-api/openapi/components/schemas/actor-runs/RunStats.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ required:
66
type: object
77
properties:
88
inputBodyLen:
9-
type: integer
9+
type: [integer, "null"]
1010
minimum: 0
1111
examples: [240]
1212
migrationCount:

apify-api/openapi/components/schemas/actors/CreateOrUpdateVersionRequest.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ properties:
1111
envVars:
1212
type: [array, "null"]
1313
items:
14-
$ref: ./EnvVar.yaml
14+
$ref: ./EnvVarRequest.yaml
1515
description: ""
1616
applyEnvVarsToBuild:
1717
type: [boolean, "null"]

apify-api/openapi/components/schemas/actors/EnvVar.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
title: EnvVar
22
required:
33
- name
4-
- value
54
type: object
65
properties:
76
name:
87
type: string
98
examples: [MY_ENV_VAR]
109
value:
1110
type: string
11+
description: The environment variable value. This field is absent in responses when `isSecret` is `true`, as secret values are never returned by the API.
1212
examples: [my-value]
1313
isSecret:
1414
type: [boolean, "null"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
title: EnvVarRequest
2+
allOf:
3+
- $ref: ./EnvVar.yaml
4+
- required:
5+
- value

apify-api/openapi/paths/actor-runs/actor-runs@{runId}@charge.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ post:
3636
responses:
3737
"201":
3838
description: "The charge was successful. Note that you still have to make sure in your Actor that the total charge for the run respects the maximum value set by the user, as the API does not check this. Above the limit, the charges reported as successful in API will not be added to your payouts, but you will still bear the associated costs. Use the Apify charge manager or SDK to avoid having to deal with this manually."
39+
content:
40+
application/json:
41+
schema:
42+
type: object
43+
additionalProperties: false
3944
"400":
4045
$ref: ../../components/responses/BadRequest.yaml
4146
"401":

apify-api/openapi/paths/actor-tasks/actor-tasks.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ post:
142142
$ref: ../../components/responses/Forbidden.yaml
143143
"405":
144144
$ref: ../../components/responses/MethodNotAllowed.yaml
145+
"409":
146+
$ref: ../../components/responses/Conflict.yaml
145147
"413":
146148
$ref: ../../components/responses/PayloadTooLarge.yaml
147149
"415":

apify-api/openapi/paths/actors/acts@{actorId}.yaml

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,7 @@ get:
8181
"403":
8282
$ref: ../../components/responses/Forbidden.yaml
8383
"404":
84-
description: Not found - the requested resource was not found.
85-
content:
86-
application/json:
87-
schema:
88-
$ref: "../../components/schemas/common/errors/ActorErrors.yaml#/ActorNotFoundError"
84+
$ref: ../../components/responses/NotFound.yaml
8985
"405":
9086
$ref: ../../components/responses/MethodNotAllowed.yaml
9187
"429":
@@ -269,11 +265,7 @@ put:
269265
"403":
270266
$ref: ../../components/responses/Forbidden.yaml
271267
"404":
272-
description: Not found - the requested resource was not found.
273-
content:
274-
application/json:
275-
schema:
276-
$ref: "../../components/schemas/common/errors/ActorErrors.yaml#/ActorNotFoundError"
268+
$ref: ../../components/responses/NotFound.yaml
277269
"405":
278270
$ref: ../../components/responses/MethodNotAllowed.yaml
279271
"413":
@@ -318,11 +310,7 @@ delete:
318310
"403":
319311
$ref: ../../components/responses/Forbidden.yaml
320312
"404":
321-
description: Not found - the requested resource was not found.
322-
content:
323-
application/json:
324-
schema:
325-
$ref: "../../components/schemas/common/errors/ActorErrors.yaml#/ActorNotFoundError"
313+
$ref: ../../components/responses/NotFound.yaml
326314
"405":
327315
$ref: ../../components/responses/MethodNotAllowed.yaml
328316
"429":

0 commit comments

Comments
 (0)