Skip to content

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

Draft
Pijukatel wants to merge 1 commit into
masterfrom
claude/openapi-store-validation-errors-3ginx5
Draft

docs(openapi): Autofix OpenAPI spec validation errors#2953
Pijukatel wants to merge 1 commit into
masterfrom
claude/openapi-store-validation-errors-3ginx5

Conversation

@Pijukatel

@Pijukatel Pijukatel commented Sep 7, 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: not applicable for this iteration - no api.log was generated. The input was a production SOFT_FAIL log excerpt (16 lines, Sep 6 11:01 - Sep 7 09:36) supplied directly, and the reporter confirmed the errors are not reproducible in the PR test environment. All 16 lines deduplicate to a single unique error.

apify-core version: https://github.com/apify/apify-core/tree/912cf52953c2b5ef841ebdfbacce20a0bf0b5cfe

Stop reason: No new fixes are possible - the supplied log contains exactly one unique error, and it is fixed here.

Reference links below use the abbreviated form 912cf52953c2 of that same commit, because URLs longer than ~150 characters get mangled into inline code when this description is posted.

Follow-up now exists in apify-core. The underlying inconsistency is being fixed at the source (see Potential API bugs below). Once that ships, this schema can be narrowed back to type: array. Land the apify-core change first - narrowing the spec before the API stops emitting null would just keep the soft-fails coming. This PR is safe to merge in the meantime: a nullable type still accepts arrays, so it stays correct either way.

Detailed changes description

Error fixes

categories can be null in the store search response

Files: apify-api/openapi/components/schemas/store/StoreListActor.yaml:29

Error:

SOFT_FAIL {"errors":[{"errorCode":"type.openapi.validation","message":"must be array","path":"/response/data/items/<N>/categories"}],"method":"GET","msg":"Response OpenAPI validation error","statusCode":200.0,"url":"/v2/store?limit=1000&offset=<OFFSET>&sortBy=lastUpdate"}

All 16 log lines are the same error on GET /v2/store, differing only in item index, offset and search parameters. They span every sortBy value, both responseFormat variants and both includeUnrunnableActors settings, which is consistent with a per-Actor data property rather than a query-shape problem.

Root cause: categories is nullable in stored Actor documents, and GET /v2/store returns it unchanged.

The write side already documents null as an accepted value - CreateActorRequest.categories and UpdateActorRequest.categories are both type: [array, "null"]. updateActor persists the modifier with a plain $set, so a submitted null is stored verbatim: the Act2Schema categories: { type: Array, optional: true } check does not reject it, because simpl-schema skips type checks for null on optional keys (verified against the pinned simpl-schema@3.2.0: null validates, while a string or number is rejected with expectedType).

From there the null propagates untouched to the response:

  1. The Algolia consolidation daemon copies the field with _.pick(actor, ACTOR_INDEX_FIELDS), which preserves a null value.
  2. The store search route returns each hit unchanged through toPublicSearchItem's ...item spread - the full response format applies no normalization to categories.

Every other consumer of the field coalesces it defensively - actor.categories ?? [] in the Recombee sync, and $ifNull: ['$categories', []] in the console search service, whose inline comment records that the stored data is not clean - but the public API route does not. So the raw null reaches the response and fails the type: array assertion.

Widening the response schema to [array, "null"] documents the values the endpoint actually returns. It does not weaken the assertion beyond null: verified with ajv 2020 (the engine express-openapi-validator uses) against the bundled schema, null and string arrays now pass while a bare string and a number are still rejected.

Reference:

Refactoring

None. The fix is confined to the one field named in the error.

Unfixed errors

Potential API bugs

GET /v2/store returns null for categories where every other surface returns []

Status: fix written in apify-core, not yet reviewed or deployed.

Root cause: The Recombee sync and the console search service both normalize a missing or null categories to []; the public store search route was the only surface passing the raw value through, so clients had to handle two representations of "no categories".

The source-level fix now exists on the apify-core branch claude/openapi-store-validation-errors-3ginx5 (commit fe01e5e8). It normalizes in all three response shapers - toPublicSearchItem plus both agent-format shapers, since those emit categories too and would otherwise keep the bug reachable via responseFormat=agent - and widens StoreActor.categories / PublicApiStoreActor.categories to include null so the types match what the backends return.

Sequencing: land and deploy the apify-core change first, then narrow this schema to type: array in a follow-up (it could also become required, since the field is then always present). This PR remains correct in the meantime, because a nullable type still accepts arrays.

Reference: src/api/src/routes/store/search.ts#L179

Actor.categories has the same latent gap

Root cause: apify-api/openapi/components/schemas/actors/Actor.yaml:118 also declares categories: type: array, while the same nullable stored value is served by the Actor read endpoints via getPublicActor, which returns the projected document unchanged. No error for those endpoints appears in the supplied log, so it is left unchanged here per the minimum-fix rule, but it will surface the same must be array error whenever an Actor with a stored null is fetched. The apify-core fix above is deliberately scoped to the store search route and does not cover this path.

Reference: actors.both.ts#L571

False positives

None encountered. No nullable date-time field and no multi-type definition was touched, and the change is a genuine semantic widening rather than a syntax swap: null previously failed validation and now passes.

Validation

The errors are not reproducible in the PR test environment, so the regression check was done against the bundled spec instead:

  • pnpm openapi:lint (Redocly + Spectral + YAML) passes with no errors. The 6 Redocly warnings are pre-existing and all concern single-schema allOf in actor-builds paths, untouched here.
  • Diffing the fully bundled openapi.json before and after shows exactly 4 leaf changes, all inside StoreListActor.properties.categories (the type widening plus the added description). Nothing else in the spec changed.
  • StoreListActor is referenced only by ListOfStoreActorsListOfActorsInStoreResponse → the GET /v2/store 200 response, so no other endpoint's validation is affected.
  • No request-side schema was touched, so no new request validation errors are possible. And because the change only widens a response type into a superset of the original, it cannot make a previously passing response fail - a response-type widening can only remove validation errors, never add them.

Issues

Partially implements: #2286

🤖 Generated with Claude Code

https://claude.ai/code/session_01TWepzDer9U43JgyXjACWYv

Error: SOFT_FAIL {"errors":[{"errorCode":"type.openapi.validation","message":"must be array","path":"/response/data/items/<N>/categories"}],"method":"GET","msg":"Response OpenAPI validation error","statusCode":200.0,"url":"/v2/store?limit=1000&offset=<OFFSET>&sortBy=lastUpdate"}
Files: apify-api/openapi/components/schemas/store/StoreListActor.yaml:29
Root cause: `categories` is nullable in stored Actor documents, and `GET /v2/store` returns it
unchanged. The write API explicitly accepts `null` for the field - both `CreateActorRequest.categories`
and `UpdateActorRequest.categories` are already documented as `type: [array, "null"]` - and
`updateActor` persists the modifier with a plain `$set`, so a submitted `null` is stored verbatim
(the `Act2Schema` `type: Array` check does not reject it, because simpl-schema skips type checks for
null values on optional keys). The Algolia consolidation daemon copies the field with
`_.pick(actor, ACTOR_INDEX_FIELDS)`, preserving the `null`, and the store search route returns the
hit unchanged via `toPublicSearchItem`'s spread. Every other consumer normalizes the field
defensively (`actor.categories ?? []` in the Recombee sync, `$ifNull: ['$categories', []]` in the
console search service, whose comment records that the stored data is not clean), but the public API
route does not, so the raw `null` reaches the response and fails the `type: array` assertion.
Widening the response schema to `[array, "null"]` documents the values the endpoint actually returns;
it still rejects any other non-array type, so the assertion is not weakened beyond null.
Reference: https://github.com/apify/apify-core/tree/912cf52953c2b5ef841ebdfbacce20a0bf0b5cfe/src/api/src/routes/store/search.ts#L179

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TWepzDer9U43JgyXjACWYv
@github-actions github-actions Bot added this to the 149th sprint - Tooling team milestone Sep 7, 2026
@github-actions github-actions Bot added the t-tooling Issues with this label are in the ownership of the tooling team. label Sep 7, 2026
@apify-service-account

apify-service-account commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

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

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

Labels

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.

3 participants