Skip to content

docs(openapi): Autofix OpenAPI spec validation errors - #3125

Open
Pijukatel wants to merge 6 commits into
masterfrom
claude/laughing-brown-ttkhp9
Open

Pijukatel wants to merge 6 commits into
masterfrom
claude/laughing-brown-ttkhp9

Conversation

@Pijukatel

@Pijukatel Pijukatel commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

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/31898/api-333ad8a2a79accc8205a1f95a46afdd6efc8b675.html

apify-core version: https://github.com/apify/apify-core/commit/d4cb827d3d91729543185135b83797891a55f8c3 (the log was produced by apify/apify-core#31898, which adds unevaluatedProperties: false to the response validator on top of this commit)

Stop reason: No new fixes possible. The log contains 56 distinct undocumented-field errors. 42 of them are already fixed by #3118, so this PR covers only the remainder: 5 fixes.

Detailed changes description

Error fixes

error.data on error responses

lockByClient on request queue requests

Private user data returned by Get public user data

  • Files: apify-api/openapi/paths/users/users@{userId}.yaml:11, apify-api/openapi/paths/users/users@{userId}.yaml:31
  • Error: Response OpenAPI validation error {"url":"/v2/users/{id}","method":"GET","statusCode":200,"errors":[{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/id"},{...,"path":"/response/data/email"},{...,"path":"/response/data/proxy"},{...,"path":"/response/data/plan"},{...,"path":"/response/data/effectivePlatformFeatures"},{...,"path":"/response/data/createdAt"},{...,"path":"/response/data/isPaying"},{...,"path":"/response/data/organizationOwnerUserId"}]}
  • Root cause: When the caller is authenticated and may access the requested user (their own account, or an organization member), the route attaches the private user info to the public profile, which is the same shape as Get private user data. The spec described only the public shape, so a lookup of the caller's own id failed validation. The response schema is now anyOf the public and the private response, with the condition described on the endpoint.
  • Reference: https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/routes/users/user.ts#L77, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/lib/users.ts#L124

standby is null on runs that are not Actor Standby runs

plan.personalizedSupport and plan.monitoringLevel are null on some user plans

Refactoring

None.

Fields already covered by #3118

The remaining 42 distinct errors in the log are fixed by #3118 and are not repeated here: run options.isMaxTotalChargeUsdSetByUser, standby, accountedChargedEventCounts, eventUsage, externalChargedTotalUsd, createdByOrganizationMemberUserId, metamorphs[].buildTags, PROXY_UNBLOCKER_UNITS in usage and usageUsd; build exitCode, changelog; storage title; RequestResource.lockExpiresAt; actorStandby.tenancy; envVars[].valueHash; isPPEPlatformUsagePaidByUser; task nameLowerCase, stats.lastRunStartedAt; schedule uiState; webhook isEnabled, actionType, dispatch webhook._id; user organizationOwnerUserId, proxy.groups[].id, effectivePlatformFeatures.PROXY_UNBLOCKER, the plan.* fields and the unblocker-units limits.

Issues

Partially implements: #2286

@apify-service-account

apify-service-account commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

✅ Preview for this PR (commit c44dd025) is ready at https://pr-3125.preview.docs.apify.com (see action run).

@apify-service-account

Copy link
Copy Markdown
Contributor

API behavior and specification alignment:

  • RequestResource.lockByClient is described as "Present only while the request is locked", but it stays on the request after the lock expires. Only an explicit unlock or an update of the request removes it. Request + response:
    POST /v2/request-queues/{queueId}/head/lock?lockSecs=2&limit=1&clientKey=review-client, then wait 5 s, then GET /v2/request-queues/{queueId}/requests/EAaArVRs5qV39C9 →
    {"data":{"id":"EAaArVRs5qV39C9","uniqueKey":"https://example.com","lockByClient":"review-client","url":"https://example.com"}} (no lockExpiresAt, but lockByClient is still there; GET /v2/request-queues/{queueId}/requests returns the same)
  • GET /v2/users/{userId} description: the code never returns private data for "the account of a member of their organization". The only accounts that get it are the caller's own account and the account they are impersonating (userId === impersonatedUserId). Scoped (limited-permission) tokens never get private data, even for their own account, so "authenticated and has access" is not enough. See https://github.com/apify/apify-core/blob/v0.1587.0/src/api/src/lib/users.ts#L124-L137

Reviewed commit: 05c6185

Error: Response OpenAPI validation error {"url":"/v2/users/{id}","method":"GET","statusCode":429,"errors":[{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/error/data"}]}
Files: apify-api/openapi/components/schemas/common/ErrorDetail.yaml:9
Root cause: The API error handler responds with { error: { type, message, data } }, where data carries the public details of the error. The rate-limit error sets rateLimitType (ENDPOINT or USER) there, so 429 responses contain error.data, which ErrorDetail did not describe. Other error types leave data undefined, so the key is not serialized.
Reference: https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/middleware/error_handler.ts#L39, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/packages/errors/src/errors/api.ts#L68

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Pijukatel
Pijukatel force-pushed the claude/laughing-brown-ttkhp9 branch from 05c6185 to 7e24fe8 Compare October 27, 2026 12:39
Error: Response OpenAPI validation error {"url":"/v2/request-queues/{id}/requests/{requestId}","method":"GET","statusCode":200,"errors":[{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/lockByClient"}]}
Files: apify-api/openapi/components/schemas/request-queues/RequestResource.yaml:10
Root cause: Get request returns the stored request item as it is. lockByClient is an optional string attribute of the stored request that holds the key of the client that last locked it. Locking sets it and only an explicit unlock or an update of the request removes it, so it is also returned after the lock has expired.
Reference: https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/routes/request_queues/request.ts#L79, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/lib/request_queues/request_queue.ts#L1372, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/lib/request_queues/request_queue.ts#L1324, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/packages/schemas/src/request_queues.ts#L89

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Error: Response OpenAPI validation error {"url":"/v2/users/{id}","method":"GET","statusCode":200,"errors":[{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/id"},{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/email"},{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/proxy"},{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/plan"},{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/effectivePlatformFeatures"},{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/createdAt"},{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/isPaying"},{"message":"must NOT have unevaluated properties","errorCode":"unevaluatedProperties.openapi.validation","path":"/response/data/organizationOwnerUserId"}]}
Files: apify-api/openapi/paths/users/users@{userId}.yaml:11, apify-api/openapi/paths/users/users@{userId}.yaml:31
Root cause: When the request is authenticated with a token of the requested user, or with a token impersonating that user, the route attaches the private user info to the public profile, which is the same shape as Get private user data. Scoped tokens never get it. The spec described only the public shape, so a lookup of the caller's own id failed validation.
Reference: https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/routes/users/user.ts#L77, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/api/src/lib/users.ts#L124

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Pijukatel
Pijukatel force-pushed the claude/laughing-brown-ttkhp9 branch from 7e24fe8 to abec71a Compare October 27, 2026 12:40
@apify-service-account

Copy link
Copy Markdown
Contributor

API behavior and specification alignment:

  • RequestResource.lockByClient is described as "Present only while the request is locked", but it stays on the request after the lock expires. Request + response:
    POST /v2/request-queues/{queueId}/head/lock?lockSecs=2&limit=1&clientKey=review-client, wait 6 s, then GET /v2/request-queues/{queueId}/requests/EAaArVRs5qV39C9 →
    {"data":{"id":"EAaArVRs5qV39C9","uniqueKey":"https://example.com","lockByClient":"review-client","url":"https://example.com"}} (lock expired, no lockExpiresAt, lockByClient still present)
  • GET /v2/users/{userId} description: private data is never returned for "the account of a member of their organization". Only the caller's own account or the account being impersonated (userId === impersonatedUserId) gets it, and scoped (limited-permission) tokens never get it, even for their own account, so "authenticated and has access" is not enough — https://github.com/apify/apify-core/blob/v0.1587.0/src/api/src/lib/users.ts#L124-L137

Reviewed commit: 7e24fe8

@apify-service-account

apify-service-account commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

API behavior and specification alignment: Aligned.

Reviewed commit: c44dd02

@Pijukatel
Pijukatel marked this pull request as ready for review October 27, 2026 12:43
@Pijukatel
Pijukatel requested a review from vdusek October 27, 2026 12:43
@apify-service-account apify-service-account added the t-tooling Issues with this label are in the ownership of the tooling team. label Oct 27, 2026

@vdusek vdusek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Pijukatel Pijukatel added the adhoc Ad-hoc unplanned task added during the sprint. label Oct 27, 2026
Error: Response OpenAPI validation error {"errors":[{"errorCode":"type.openapi.validation","message":"must be object","path":"/response/data/standby"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":200,"url":"/v2/actor-runs/{id}/abort"}
Files: apify-api/openapi/components/schemas/actor-runs/Run.yaml:92, apify-api/openapi/components/schemas/actor-runs/RunListItem.yaml:61
Root cause: The run document stores standby from resolveRunStandbyOptions, which returns undefined for every run that is not an Actor Standby run, and the API serializes that as null. The spec declared standby as a RunStandby object only, so every non-Standby run failed with "must be object".
Reference: https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/packages/actor-server/src/actor_jobs/actor_jobs.server.ts#L765, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/packages/actor-server/src/actor_jobs/actor_jobs.server.ts#L1835, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/packages/types/src/actor.ts#L602

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ser plans

Error: Response OpenAPI validation error {"errors":[{"errorCode":"type.openapi.validation","message":"must be string","path":"/response/data/plan/personalizedSupport"}],"method":"GET","msg":"Response OpenAPI validation error","statusCode":200,"url":"/v2/users/me"}
Files: apify-api/openapi/components/schemas/users/UserPlan.yaml:120, apify-api/openapi/components/schemas/users/UserPlan.yaml:123
Root cause: Both fields are optional strings in the user plan schema. The admin custom plan form edits them as text inputs and its form cleaning turns an empty input into null, which the schema accepts for optional keys, so stored plans carry null and the API returns it. The spec allowed only a string. The other optional plan fields are not edited by any form and have no code path that sets null, so they stay as they are.
Reference: https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/packages/schemas/src/user_plans.ts#L338, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/packages/schemas/src/user_plans.ts#L351, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/console/frontend/src/ui/admin/admin_user_settings/custom_plan_view.jsx#L438, https://github.com/apify/apify-core/tree/d4cb827d3d91729543185135b83797891a55f8c3/src/console/frontend/src/ui/components/fast_form/form_hooks.js#L46

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants