Skip to content

docs(openapi): Autofix OpenAPI spec validation errors#2785

Merged
Pijukatel merged 2 commits into
masterfrom
claude/openapi-errors-fix-xjikyy
Jul 20, 2026
Merged

docs(openapi): Autofix OpenAPI spec validation errors#2785
Pijukatel merged 2 commits into
masterfrom
claude/openapi-errors-fix-xjikyy

Conversation

@Pijukatel

@Pijukatel Pijukatel commented Jul 20, 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: User-provided production api.log excerpt (SOFT_FAIL response validation errors, Jul 18–20, 2026). The log is not available at a public URL, so the normalized log lines are included below:

Jul 18 00:34:05 apify-api SOFT_FAIL {"errors":[{"message":"no schema defined for status code '402' in the openapi spec","path":"/v2/actors/{actorId}/builds?version=1.0&useCache=false&waitForFinish=180"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":402.0,"url":"/v2/actors/{actorId}/builds?version=1.0&useCache=false&waitForFinish=180"}
Jul 18 02:50:38 apify-api SOFT_FAIL {"errors":[{"message":"no schema defined for status code '404' in the openapi spec","path":"/v2/actor-tasks"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":404.0,"url":"/v2/actor-tasks"}
Jul 20 08:15:38 apify-api SOFT_FAIL {"errors":[{"errorCode":"type.openapi.validation","message":"must be string","path":"/response/data/taggedBuilds/{tag}/buildNumber"},{"errorCode":"type.openapi.validation","message":"must be null","path":"/response/data/taggedBuilds/{tag}"},{"errorCode":"anyOf.openapi.validation","message":"must match a schema in anyOf","path":"/response/data/taggedBuilds/{tag}"},{"errorCode":"type.openapi.validation","message":"must be null","path":"/response/data/taggedBuilds"},{"errorCode":"anyOf.openapi.validation","message":"must match a schema in anyOf","path":"/response/data/taggedBuilds"}],"method":"GET","msg":"Response OpenAPI validation error","statusCode":200.0,"url":"/v2/actors/{actorId}"}
Jul 20 08:47:37 apify-api SOFT_FAIL {"errors":[{"message":"no schema defined for status code '409' in the openapi spec","path":"/v2/actor-tasks/{actorTaskId}"}],"method":"PUT","msg":"Response OpenAPI validation error","statusCode":409.0,"url":"/v2/actor-tasks/{actorTaskId}"}

A follow-up regression run of the integration tests with the fixed spec (validator run results) surfaced one additional response validation error, fixed as the fifth fix below.

apify-core version: https://github.com/apify/apify-core/commit/48a50ff1c30b82ade5283e5465ef63a7ec09095b

Stop reason: All 4 unique errors from the provided log are fixed, plus 1 new response validation error found in the regression run. The original 4 errors cannot be reproduced in the PR test environment (they come from production traffic and legacy data), so they were verified against apify-core source instead. The regression run with the fixed spec passed (status SUCCESS) and its api.log contains no response validation errors caused by these changes; the remaining request validation errors in that log are deliberate malformed-request tests or pre-existing issues unrelated to this diff (see Unfixed errors).

Detailed changes description

Error fixes

Add 402 (Payment Required) response to Build Actor endpoint

  • Files: apify-api/openapi/paths/actors/acts@{actorId}@builds.yaml:123
  • Error: {"errors":[{"message":"no schema defined for status code '402' in the openapi spec","path":"/v2/actors/{actorId}/builds?version=1.0&useCache=false&waitForFinish=180"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":402}
  • Root cause: POST /v2/actors/{actorId}/builds enforces the account memory and concurrent-runs limits before creating the build and throws memory-limit-exceeded/concurrent-runs-limit-exceeded errors with HTTP 402, but the spec did not document a 402 response. Reused the PaymentRequired response component, consistent with the run Actor endpoints that document 402 for the same limit checks.
  • Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/packages/actor-server/src/actor_jobs/actor_jobs.server.ts#L1269

Add 404 (Not Found) response to Create task endpoint

Allow null buildNumber in tagged builds of the Actor object

  • Files: apify-api/openapi/components/schemas/actors/TaggedBuildInfo.yaml:10
  • Error: {"errors":[{"errorCode":"type.openapi.validation","message":"must be string","path":"/response/data/taggedBuilds/{tag}/buildNumber"},{"errorCode":"anyOf.openapi.validation","message":"must match a schema in anyOf","path":"/response/data/taggedBuilds/{tag}"}],"method":"GET","msg":"Response OpenAPI validation error","statusCode":200,"url":"/v2/actors/{actorId}"}
  • Root cause: GET /v2/actors/{actorId} builds the taggedBuilds map via transformActor(), which computes buildNumber as buildOrVersionNumberIntToStr(buildNumberInt) with return type string | null; for legacy builds without a valid buildNumberInt the value is null, while the spec allowed only a string. Allowed null for buildNumber (the pattern keyword only applies to string values, so it keeps validating real build numbers).
  • Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/packages/actor/src/actors/actors.both.ts#L113

Add 409 (Conflict) response to Update task endpoint

  • Files: apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}.yaml:104
  • Error: {"errors":[{"message":"no schema defined for status code '409' in the openapi spec","path":"/v2/actor-tasks/{actorTaskId}"}],"method":"PUT","msg":"Response OpenAPI validation error","statusCode":409}
  • Root cause: PUT /v2/actor-tasks/{actorTaskId} throws actor-task-name-not-unique with HTTP 409 when renaming a task to a name already used by another task of the same user (Mongo duplicate key on userId+nameLowerCase), but the spec did not document a 409 response. Reused the Conflict response component, matching POST /v2/actor-tasks which already documents 409 for the same uniqueness constraint.
  • Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/api/src/routes/actor_tasks/actor_task.ts#L151

Add cannot-monetize-without-payout-billing-info to ErrorType enum

  • Files: apify-api/openapi/components/schemas/common/ErrorType.yaml:68
  • Error: {"url":"/v2/actors/{actorId}","method":"PUT","statusCode":400,"errors":[{"message":"must be equal to one of the allowed values: 3d-secure-auth-failed, access-right-already-exists, ...","errorCode":"enum.openapi.validation","path":"/response/error/type"}]} (from the regression run log)
  • Root cause: PUT /v2/actors/{actorId} with a paid pricing model rejects the update with HTTP 400 and error type cannot-monetize-without-payout-billing-info when the Actor owner has no payout billing info set (thrown by checkAndSanitizePricingInfosModifier), but this error type was missing from the ErrorType enum shared by all error responses.
  • Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/api/src/lib/paid_actors_helpers.ts#L364

Refactoring

None - only the minimum necessary response/schema additions were made.

Unfixed errors

Out of scope errors

Unknown query parameter standbyDeployment on run Actor endpoint

  • Error: {"url":"/v2/actors/{actorId}/runs?standbyDeployment=SINGLE_TENANT&memory=1024&build=latest","method":"POST","errors":[{"message":"Unknown query parameter 'standbyDeployment'","path":"/query/standbyDeployment"}]} (request validation, regression run log)
  • Root cause: ApifyClient 2.23.4 sends a real standbyDeployment query parameter that is not yet documented on POST /v2/actors/{actorId}/runs. Documenting it requires product input on the parameter's contract, which is beyond this autofix PR.

Undocumented endpoint GET /v2/actors/{actorId}/oauth-connections

  • Error: {"url":"/v2/actors/{actorId}/oauth-connections","method":"GET","errors":[{"message":"not found","path":"/actors/{actorId}/oauth-connections"}]} (request validation, regression run log)
  • Root cause: The endpoint exists in apify-core (src/api/src/routes/actors/oauth_connection_list.ts) but has no path in the OpenAPI spec. Adding a whole new documented endpoint is beyond this autofix PR.

Deliberate malformed-request tests and internal-only paths

  • Error: Remaining request validation errors in the regression run log: invalid input/options/generalAccess/eventTypes/filter/handledAt values, unsupported media types, invalid HTTP methods, missing required parameters, and probes of internal paths (/v2/meta/_telemetry-test, /v2/openapi.json, /v2/browser-info?method=...).
  • Root cause: Integration tests intentionally send malformed requests to verify error handling, and some internal/telemetry paths are deliberately absent from the public spec. Per the workflow rules, specs must not be changed to accommodate intentionally invalid requests.

Issues

Partially implements: #2286

…ilds buildNumber

Error: {"errors":[{"message":"no schema defined for status code '402' in the openapi spec","path":"/v2/actors/{actorId}/builds?version=1.0&useCache=false&waitForFinish=180"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":402}
Files: apify-api/openapi/paths/actors/acts@{actorId}@builds.yaml:123
Root cause: POST /v2/actors/{actorId}/builds enforces account memory and concurrent-runs limits before creating the build and throws memory-limit-exceeded/concurrent-runs-limit-exceeded errors with HTTP 402, but the spec did not document a 402 response. Reused the PaymentRequired response component, consistent with the run Actor endpoints that document 402 for the same limit checks.
Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/packages/actor-server/src/actor_jobs/actor_jobs.server.ts#L1269

Error: {"errors":[{"message":"no schema defined for status code '404' in the openapi spec","path":"/v2/actor-tasks"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":404}
Files: apify-api/openapi/paths/actor-tasks/actor-tasks.yaml:142
Root cause: POST /v2/actor-tasks looks up the Actor referenced by actId in the request payload and throws record-not-found with HTTP 404 when that Actor does not exist or is removed, but the spec did not document a 404 response. Reused the NotFound response component.
Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/api/src/routes/actor_tasks/actor_task_list.ts#L190

Error: {"errors":[{"errorCode":"type.openapi.validation","message":"must be string","path":"/response/data/taggedBuilds/{tag}/buildNumber"},{"errorCode":"anyOf.openapi.validation","message":"must match a schema in anyOf","path":"/response/data/taggedBuilds/{tag}"}],"method":"GET","msg":"Response OpenAPI validation error","statusCode":200,"url":"/v2/actors/{actorId}"}
Files: apify-api/openapi/components/schemas/actors/TaggedBuildInfo.yaml:10
Root cause: GET /v2/actors/{actorId} builds the taggedBuilds map via transformActor(), which computes buildNumber as buildOrVersionNumberIntToStr(buildNumberInt) with return type string | null; for legacy builds without a valid buildNumberInt the value is null, while the spec allowed only a string. Allowed null for buildNumber (the pattern keyword only applies to string values in JSON Schema).
Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/packages/actor/src/actors/actors.both.ts#L113

Error: {"errors":[{"message":"no schema defined for status code '409' in the openapi spec","path":"/v2/actor-tasks/{actorTaskId}"}],"method":"PUT","msg":"Response OpenAPI validation error","statusCode":409}
Files: apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}.yaml:104
Root cause: PUT /v2/actor-tasks/{actorTaskId} throws actor-task-name-not-unique with HTTP 409 when renaming a task to a name already used by another task of the same user (Mongo duplicate key on userId+nameLowerCase), but the spec did not document a 409 response. Reused the Conflict response component, matching POST /v2/actor-tasks which already documents 409 for the same uniqueness constraint.
Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/api/src/routes/actor_tasks/actor_task.ts#L151

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

apify-service-account commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🗑️ Preview for this PR was deleted.

@apify-service-account

Copy link
Copy Markdown
Contributor

Important

Action required@Pijukatel please coordinate this docs PR with the Python API client PR linked below.

Because this PR modifies the OpenAPI specification, the generated models in apify-client-python must be regenerated to stay in sync. This has already been done automatically:

A companion PR has been opened in apify-client-python with the regenerated models: apify/apify-client-python#960

  • Please make sure to review and merge both PRs together to keep the OpenAPI spec and API clients in sync.
  • You can ask for review and help from the Tooling team if needed.

@Pijukatel Pijukatel added the adhoc Ad-hoc unplanned task added during the sprint. label Jul 20, 2026
@Pijukatel
Pijukatel requested a review from vdusek July 20, 2026 09:28
@Pijukatel
Pijukatel marked this pull request as ready for review July 20, 2026 09:28
…rType enum

Error: {"url":"/v2/actors/{actorId}","method":"PUT","statusCode":400,"errors":[{"message":"must be equal to one of the allowed values: 3d-secure-auth-failed, access-right-already-exists, ...","errorCode":"enum.openapi.validation","path":"/response/error/type"}]}
Files: apify-api/openapi/components/schemas/common/ErrorType.yaml:68
Root cause: PUT /v2/actors/{actorId} with a paid pricing model rejects the update with HTTP 400 and error type cannot-monetize-without-payout-billing-info when the owner has no payout billing info set (thrown by checkAndSanitizePricingInfosModifier), but this error type was missing from the ErrorType enum used by all error responses.
Reference: https://github.com/apify/apify-core/tree/48a50ff1c30b82ade5283e5465ef63a7ec09095b/src/api/src/lib/paid_actors_helpers.ts#L364

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011VY4n4eTrXwjwT4CMsHPJD
@apify-service-account

Copy link
Copy Markdown
Contributor

Important

Action required@Pijukatel please coordinate this docs PR with the Python API client PR linked below.

Because this PR modifies the OpenAPI specification, the generated models in apify-client-python must be regenerated to stay in sync. This has already been done automatically:

The companion apify-client-python PR has been updated with the latest spec changes: apify/apify-client-python#960

  • Please make sure to review and merge both PRs together to keep the OpenAPI spec and API clients in sync.
  • You can ask for review and help from the Tooling team if needed.

@Pijukatel
Pijukatel merged commit 7c2d71f into master Jul 20, 2026
18 checks passed
@Pijukatel
Pijukatel deleted the claude/openapi-errors-fix-xjikyy branch July 20, 2026 09:48
vdusek pushed a commit to apify/apify-client-python that referenced this pull request Jul 20, 2026
…de (#960)

- Updates the auto-generated Pydantic models and TypedDicts based on the
proposed OpenAPI specification changes.
- Based on apify-docs PR
[#2785](apify/apify-docs#2785).
Pijukatel added a commit that referenced this pull request Jul 22, 2026
## Summary
Autogenerated OpenAPI fixes suggestions based on validation errors
generated from running API integration tests with OpenAPI validator
turned on.

**Error log:** User-provided production `api.log` excerpt (SOFT_FAIL
response validation errors, Jul 16–21, 2026). The log is not available
at a public URL, so the normalized log lines are included below:

```text
Jul 16 10:34:11 apify-api SOFT_FAIL {"errors":[{"errorCode":"required.openapi.validation","message":"must have required property 'requestUrl'","path":"/response/data/requestUrl"}],"method":"GET","msg":"Response OpenAPI validation error","statusCode":200.0,"url":"/v2/webhooks/{webhookId}"}
Jul 20 15:56:30 apify-api SOFT_FAIL {"errors":[{"message":"no schema defined for status code '402' in the openapi spec","path":"/v2/actor-tasks/{actorTaskId}/run-sync?timeout=300&memory=1024&maxItems=20&build=latest&outputRecordKey=OUTPUT&webhooks="}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":402.0,"url":"/v2/actor-tasks/{actorTaskId}/run-sync?timeout=300&memory=1024&maxItems=20&build=latest&outputRecordKey=OUTPUT&webhooks="}
Jul 21 03:20:22 apify-api SOFT_FAIL {"errors":[{"errorCode":"type.openapi.validation","message":"must be string","path":"/response/data/webhook/requestUrl"},{"errorCode":"type.openapi.validation","message":"must be null","path":"/response/data/webhook"},{"errorCode":"anyOf.openapi.validation","message":"must match a schema in anyOf","path":"/response/data/webhook"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":201.0,"url":"/v2/webhooks/{webhookId}/test?token=[REDACTED]"}
Jul 21 18:01:10 apify-api SOFT_FAIL {"errors":[{"message":"no schema defined for status code '408' in the openapi spec","path":"/v2/actor-tasks/{actorTaskId}/run-sync?token=[REDACTED]&outputRecordKey=OUTPUT"}],"method":"POST","msg":"Response OpenAPI validation error","statusCode":408.0,"url":"/v2/actor-tasks/{actorTaskId}/run-sync?token=[REDACTED]&outputRecordKey=OUTPUT"}
```

**apify-core version:**
apify/apify-core@446d6fa

**Stop reason:** All 4 unique errors from the provided log that are
still present on `master` are fixed. The remaining 4 unique errors in
the log were already fixed on `master` by #2785 (the log lines predate
that merge, see Out of scope errors). The original errors cannot be
reproduced in the PR test environment (they come from production
traffic: non-HTTP webhooks, platform usage limits, sync-run timeouts),
so they were verified against apify-core source instead. A regression
run of the integration tests with the fixed spec completed with status
SUCCESS ([validator run
results](https://apify-pr-test-env-logs.s3.us-east-1.amazonaws.com/apify/apify-core/27741/results-44f0a8f7474fa5f462e4e79633eec56943d63a3a.html))
and its api.log contains zero response validation errors; the remaining
request validation errors in that log are deliberate malformed-request
tests or pre-existing request-side spec gaps unrelated to this diff.

## Detailed changes description
### Error fixes

#### Webhook object no longer requires `requestUrl`
- **Files:**
`apify-api/openapi/components/schemas/webhooks/Webhook.yaml:2`
- **Error:** `must have required property 'requestUrl'` at
`/response/data/requestUrl`, `GET /v2/webhooks/{webhookId}`, status 200
- **Root cause:** `requestUrl` is optional in apify-core and enforced
only for webhooks with actionType `HTTP_REQUEST`. Webhooks with other
action types (Slack message, Google Mail, Google Drive, GitHub issue)
are stored without `requestUrl`, and the API response projection (lodash
`pick`) omits missing keys, so the endpoint legitimately returns a
webhook object without `requestUrl`. A clarifying description was added
to the property.
- **Reference:**
https://github.com/apify/apify-core/tree/446d6faa5a503ddb64447c4b6b758a811b11b397/src/packages/schemas/src/webhooks.ts#L315

#### Webhook summary in webhook dispatch allows null `requestUrl`
- **Files:**
`apify-api/openapi/components/schemas/webhook-dispatches/WebhookDispatchWebhookSummary.yaml:10`
- **Error:** `must be string` at `/response/data/webhook/requestUrl`,
`POST /v2/webhooks/{webhookId}/test`, status 201
- **Root cause:** The test-webhook endpoint embeds the stored webhook
into the created dispatch and the response projection includes
`webhook.requestUrl`. Webhook create/update accepts `requestUrl` as
nullish (`z.string().nullish()`), so non-HTTP webhooks can be stored
with an explicit `null` value, which is then returned inside the
dispatch's webhook summary. The type was widened to `[string, "null"]`
and a clarifying description was added to the property.
- **Reference:**
https://github.com/apify/apify-core/tree/446d6faa5a503ddb64447c4b6b758a811b11b397/src/packages/zod-types/src/common/webhooks.ts#L57

#### Add 402 response to `POST /v2/actor-tasks/{actorTaskId}/run-sync`
- **Files:**
`apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@run-sync.yaml:144`
- **Error:** `no schema defined for status code '402' in the openapi
spec`, `POST /v2/actor-tasks/{actorTaskId}/run-sync`, status 402 (3
occurrences in the log)
- **Root cause:** Launching a task run enforces platform usage limits
which throw 402 Payment Required errors (Actor memory limit exceeded,
concurrent runs limit exceeded, not enough usage to run paid Actor).
Sibling run-sync endpoints already document 402 via the shared
`PaymentRequired` response component.
- **Reference:**
https://github.com/apify/apify-core/tree/446d6faa5a503ddb64447c4b6b758a811b11b397/src/packages/errors/src/errors/actor.ts#L5

#### Add 408 response to `POST /v2/actor-tasks/{actorTaskId}/run-sync`
- **Files:**
`apify-api/openapi/paths/actor-tasks/actor-tasks@{actorTaskId}@run-sync.yaml:152`
- **Error:** `no schema defined for status code '408' in the openapi
spec`, `POST /v2/actor-tasks/{actorTaskId}/run-sync`, status 408
- **Root cause:** The run-sync handler throws `run-timeout-exceeded`
with status 408 when the run does not reach a terminal status within the
maximum synchronous wait time. The GET operation of the same path and
all sibling run-sync endpoints already document 408 via the shared
`Timeout` response component.
- **Reference:**
https://github.com/apify/apify-core/tree/446d6faa5a503ddb64447c4b6b758a811b11b397/src/api/src/routes/actor_tasks/run_sync.ts#L68

### Refactoring
None. Only shared response components (`PaymentRequired.yaml`,
`Timeout.yaml`) are re-used; no structural changes.

## Unfixed errors
### Out of scope errors
#### `POST /v2/actors/{actorId}/builds` — no schema for status 402
- **Error:** `no schema defined for status code '402' in the openapi
spec`, status 402 (Jul 18 00:34:05)
- **Root cause:** Already fixed on `master` by #2785 (merged Jul 20);
the log line predates the merge.

#### `POST /v2/actor-tasks` — no schema for status 404
- **Error:** `no schema defined for status code '404' in the openapi
spec`, status 404 (Jul 18 02:50:38)
- **Root cause:** Already fixed on `master` by #2785; the log line
predates the merge.

#### `GET /v2/actors/{actorId}` — `taggedBuilds` `buildNumber` type
error
- **Error:** `must be string` at
`/response/data/taggedBuilds/{tag}/buildNumber`, status 200 (Jul 20
08:15:38)
- **Root cause:** Already fixed on `master` by #2785 (`buildNumber` made
nullable); the log line predates the merge.

#### `PUT /v2/actor-tasks/{actorTaskId}` — no schema for status 409
- **Error:** `no schema defined for status code '409' in the openapi
spec`, status 409 (Jul 20 08:47:37)
- **Root cause:** Already fixed on `master` by #2785; the log line
predates the merge.

## Issues
Partially implements: #2286

---------

Co-authored-by: Claude <noreply@anthropic.com>
pittmanbrandon20-design added a commit to pittmanbrandon20-design/apify-client-python that referenced this pull request Jul 26, 2026
* feat: Add request body compression with optional brotli (apify#927)

## Description

- https://github.com/apify/apify-core/pull/28971 added support for
brotli compression to BE.
- Pros: higher compression, cons: more CPU intensive.
- The brotli dependencies are defined as optional, one has to explicitly
enable it and choose one.
- JS client doesn't compress requests that are too small, this Python
client compresses just everything. No change done, I only noticed and
stating it.

## Issues

Closes: apify#942

## Related PRs

- https://github.com/apify/apify-core/pull/28971
- apify/apify-docs#2750
- apify/apify-client-js#962
- apify#927
- apify/apify-sdk-python#1031

---------

Co-authored-by: Vlada Dusek <v.dusek96@gmail.com>

* chore(release): Update changelog and package version [skip ci]

* chore: Automatic docs theme update [skip ci]

* test: Deflake test_schedule_list and test_task_list by polling eventually consistent listings (apify#951)

`test_schedule_list` and `test_task_list` failed in CI ([schedule
run](https://github.com/apify/apify-client-python/actions/runs/29497290807/job/87617167353),
[task
run](https://github.com/apify/apify-client-python/actions/runs/29498108478/job/87619855853))
because they assert read-your-write on listing endpoints: they list
resources immediately after creating them, and under load the listing
can serve a view that hasn't yet caught up with the creates, so the
fresh IDs are sometimes missing. The creates themselves succeeded in
both cases, so these are eventual-consistency flakes, not client bugs.

The fix wraps each list read in the existing `poll_until_condition`
helper (30 s ceiling), waiting until the created IDs appear in the
listing. The original assertions still run on the final page, so a real
regression still fails. Follows the same deflaking pattern as apify#824,
apify#831, apify#844, and apify#868.

* docs: fix input guide example to bound the wait with wait_duration (apify#955)

The "Pass input to an Actor" guide example claimed that
`timeout=timedelta(seconds=60)` makes `call()` wait up to 60 seconds for
the run to finish. It doesn't. On `call()`, `timeout` is the per-request
HTTP timeout forwarded to `start()`, while the wait cap is
`wait_duration` (default `None`, i.e. wait indefinitely). A user copying
the example would block indefinitely on a long or never-finishing run.
The sync and async examples now use
`wait_duration=timedelta(seconds=60)` and the comment is reworded
accordingly.

The stale copies under `website/versioned_docs/` are left untouched on
purpose. The versioning workflow deletes and re-snapshots `version-3.0`
from `docs/` on every 3.x release, so the fix propagates there
automatically.

* chore: Automatic docs theme update [skip ci]

* fix: offload async request body compression to a worker thread (apify#950)

The async `ImpitHttpClientAsync.call()` serialized and compressed
request bodies inline on the event loop. Compression is CPU-bound (and
brotli is becoming the default via the Apify SDK), so it blocked the
loop for the whole duration of the compression, stalling every other
concurrent task (response reads, the platform events websocket, other
in-flight requests).

Request preparation is now offloaded to a worker thread via
`asyncio.to_thread` whenever there is a body to compress. Bodyless
requests (the common polling, listing, and get path) stay inline to
avoid a needless thread-dispatch hop. The synchronous client is
unchanged, as it has no event loop to block.

* chore(release): Update changelog and package version [skip ci]

* fix: Propagate last_run status/origin filters to chained storage clients (apify#954)

The `status` and `origin` filters passed to `ActorClient.last_run(...)`
and `TaskClient.last_run(...)` were silently dropped by the chained
storage clients. For example,
`actor.last_run(status='SUCCEEDED').dataset().list_items()` queried the
most recent run's dataset regardless of status. This is a regression vs
1.x, where `_sub_resource_init_options` forwarded the parent's params to
every child client; the typed-client refactor (apify#604) lost that.

The fix passes the run client's default params to its four child-client
factories (`dataset()`, `key_value_store()`, `request_queue()`, `log()`)
in both `RunClient` and `RunClientAsync`, so the filters ride along on
the `runs/last` storage endpoints again, matching 1.x and the JS client.

Adds a parametrized regression test covering all four chained clients,
sync and async. All 8 cases fail before the fix and pass after.

* chore(release): Update changelog and package version [skip ci]

* chore: Automatic docs theme update [skip ci]

* fix: Propagate API token to custom HTTP clients (apify#956)

`ApifyClient.with_custom_http_client(token=...)` (and the async twin)
stored the token on the `ApifyClient` instance but never passed it to
the injected HTTP client, so no request carried an `Authorization`
header and every call failed with 401. The documented custom HTTP client
example inherited the bug.

- `with_custom_http_client` now sets `Authorization: Bearer <token>` on
the injected client's default headers, unless the client already has an
auth header configured (checked case-insensitively).
- `HttpClientBase._prepare_request_call` now merges the client's default
headers under the per-request headers, so any custom client using the
helper (including a pre-built `ImpitHttpClient` passed as the custom
client) actually sends them. For the default client the wire behavior is
unchanged, since impit request-level headers replace the identical
client-level ones.
- The HTTPX guide examples now merge `self._headers` before delegating,
and the `HttpClient` ABC docstring states that implementations must send
the default headers with every request.

Regression tests cover the token reaching the wire (custom sync/async
clients and a pre-built tokenless `ImpitHttpClient`) and the no-clobber
semantics for client-configured auth headers.

* chore(release): Update changelog and package version [skip ci]

* fix: Make batch_add_requests split batches by serialized payload size (apify#953)

The 9 MB payload guard in `batch_add_requests` was inert:
`constrained_batches` was called without `get_len`, so the default
`len()` measured each request dict's key count (~4) instead of its
serialized size. Batches were therefore split only by the 25-request
count limit, and large requests shipped as one oversized POST that the
API rejects with 413, failing the whole call.

The guard now measures each request as its UTF-8 JSON byte length, using
the same serialization flags as the HTTP client's request body path. It
also passes `strict=False`, which preserves the previous contract for an
individually oversized request: it's sent in its own batch and left for
the API to judge, instead of raising a client-side `ValueError`. Both
the size-based splitting and the oversized-singleton path are covered by
new sync/async regression tests.

*✍️ Drafted by Claude Code*

* chore(release): Update changelog and package version [skip ci]

* docs: Fix documentation mismatches with actual client behavior (apify#958)

Fixes 12 documentation issues found by an engineering audit, where docs,
examples, or docstrings contradict what the client actually does:

- The custom HTTP client guide example (`HttpxClient`) now raises
`ApifyApiError` for error responses instead of returning them raw, and
the guide documents this part of the `call` contract (resource clients
rely on it, e.g. to translate a 404 into a `None` return value).
- The streaming concepts page no longer claims all three streaming
methods yield a raw `impit.Response` — it now describes the actual
yielded value per method (`stream_record` yields a `dict` with the
response under `value`, `stream` and `stream_record` may yield `None`).
- The pagination concepts page no longer lists `ListOfRequests` among
page models exposing `total`/`offset`/`count`; it now explains its
cursor-based pagination via `next_cursor`.
- The logging formatter example no longer references `%(status_code)s`
(absent on most records, causing logging errors) and no longer attaches
a duplicate handler; the page notes which properties are present on
every record.
- The upgrading-to-v3 guide cross-links now include the site baseUrl
(`/api/client/python/...`), fixing 6 links that 404ed on the published
site.
- The conda instruction for the brotli extra installs `brotli-python`
(the Python bindings) instead of `brotli` (the C library).
- `RunClient.resurrect` docstrings cite the real `SUCCEEDED` status
instead of the nonexistent `FINISHED`.
- `wait_for_finish` docstrings in `RunClient` and `BuildClient` spell
the terminal status as `TIMED-OUT` (the real literal) instead of
`TIMED_OUT`.
- The quick-start page refers to the `Run` model's `default_dataset_id`
attribute instead of the v2-era run dictionary with `defaultDatasetId`.
- The README dataset example passes `fields` as `list[str]` per the
signature instead of a comma-separated string.
- The README quick-start examples handle the `Run | None` return of
`call()` instead of accessing attributes on a possible `None`.
- The timeouts concepts page states that `no_timeout` is capped at 24
hours by the default client instead of claiming it disables the timeout
entirely.

*✍️ Drafted by Claude Code*

* chore(release): Update changelog and package version [skip ci]

* docs: Version docs for v3.1.0 [skip ci]

* chore: Automatic docs theme update [skip ci]

* chore: Automatic docs theme update [skip ci]

* fix: Add missing cannot-monetize-without-payout-billing-info error code (apify#960)

- Updates the auto-generated Pydantic models and TypedDicts based on the
proposed OpenAPI specification changes.
- Based on apify-docs PR
[#2785](apify/apify-docs#2785).

* chore(release): Update changelog and package version [skip ci]

* chore(deps): update pnpm to v11.15.1 (apify#967)

* chore(deps): update dependency oxfmt to ^0.59.0 (apify#966)

* chore(deps): lock file maintenance (apify#968)

* fix: Normalize query params in dataset create_items_public_url (apify#963)

`DatasetClient.create_items_public_url` (and its async twin) passed
`_build_params` output straight to `urlencode`, bypassing the
`_parse_params` normalization that the real HTTP request path applies.
As a result, boolean and list query params ended up as Python reprs in
the signed URL — e.g. `create_items_public_url(clean=True,
fields=['title', 'url'])` produced
`clean=True&fields=%5B%27title%27%2C+%27url%27%5D` instead of
`clean=true&fields=title,url`. Consumers of the shared URL then got
unclean/unfiltered items or an API error.

The fix routes the params through `self._http_client._parse_params(...)`
before `urlencode`, so the public URL matches exactly what an actual API
request would send (bool→`true`/`false`, list→comma-joined, `None`
dropped). Added regression tests for both the sync and async clients.

*✍️ Drafted by Claude Code*

* chore(release): Update changelog and package version [skip ci]

* chore(deps): update actions/setup-node action to v7 (apify#969)

* chore(deps): update actions/setup-python action to v7 (apify#970)

* docs: Remove conda special instructions for brotli (apify#959)

It is now included by default:
https://github.com/conda-forge/apify-client-feedstock/pull/4/changes

* docs: Update description of `request_url` in  `Webhooks` (apify#974)

- Updates the auto-generated Pydantic models and TypedDicts based on the
proposed OpenAPI specification changes.
- Based on apify-docs PR
[#2794](apify/apify-docs#2794).

---------

Co-authored-by: Michal Turek <michal.turek@apify.com>
Co-authored-by: Vlada Dusek <v.dusek96@gmail.com>
Co-authored-by: Apify Service Account <64261774+apify-service-account@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Josef Procházka <josef.prochazka@apify.com>
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