<@U09R5BM808H> <@U02GMEZU18B> Is there a way to pause a flow run via the REST API. We see this `paus...
t
@Thang Le @Nate Is there a way to pause a flow run via the REST API. We see this
pause_flow_run
is available in the SDK, but see no equivalent functionality in the REST API docs. We've tried using the
set_state
endpoint on a running job, but it just changes the state label and doesn't actually stop execution. Do you have a recommendation here?
@Marvin thoughts?
m
thought for 9.1 seconds
Could you share a bit more context on what you want thoughts on? For example: - a Prefect architecture or deployment setup - an error message - some code - a workflow design decision - pricing / plan choice - a GitHub issue or discussion If you paste the snippet, config, screenshot text, or question, I can give a concrete opinion.
n
sorry i should make marvin always fetch the root of threads
@Marvin please explain how
pause_flow_run
in the SDK uses the API
m
thought for 118.3 seconds
t
we set the state to suspended and or paused but the logs are still logging
aka we just sleeps it for 100 seconds
m
Yep —
pause_flow_run()
is basically an SDK helper that proposes a
PAUSED
state to the API
, then waits until the API transitions the run out of that paused state. Here’s the mental model: 1. Your flow code calls
pause_flow_run(...)
2. The SDK builds a
Paused
state object 3. The SDK sends that state to the API via the flow-run state orchestration endpoint 4. The API records/orchestrates the run into
PAUSED
5. The SDK polls until the run is resumed 6. When resumed, execution continues, optionally with validated input Important assumption: this is for Prefect 3.x. --- ## High-level call path At a high level, the path is:
Copy code
text
pause_flow_run()
  -> creates a Paused state
  -> propose_state(...)
  -> client.set_flow_run_state(...)
  -> POST /flow_runs/{id}/set_state
If
wait_for_input=...
is used, there is also flow-run input storage involved:
Copy code
text
pause_flow_run(wait_for_input=MyModel)
  -> save expected input schema/metadata
  -> POST /flow_runs/{id}/input
  -> set PAUSED state
  -> later: POST /flow_runs/{id}/resume with input
  -> API validates input against saved schema
  -> stores response in flow run input
  -> SDK loads that response and returns it
--- ## What
pause_flow_run()
does in the SDK Internally,
pause_flow_run()
/
apause_flow_run()
creates a
Paused
state with details like: -
timeout_seconds
-
pause_key
- whether it should
reschedule
- optional
run_input_keyset
metadata if input is expected Conceptually, it looks like:
Copy code
text
Paused(
  timeout_seconds=...,
  pause_key=...,
  reschedule=False
)
This produces a state whose type is
PAUSED
, with important values stored in
state_details
. Those details include things like: -
pause_key
-
pause_timeout
-
pause_reschedule
-
run_input_keyset
if
wait_for_input
is set The
pause_key
is especially important because it gives the pause a stable identity, which helps prevent the same pause point from being treated ambiguously. --- ## How it uses the API to pause the run Once the SDK has built the
Paused
state, it sends it through the normal state orchestration mechanism. That goes through: -
propose_state()
/
propose_state_sync()
- then
client.set_flow_run_state(...)
Which results in an API call to:
Copy code
text
POST /flow_runs/{id}/set_state
with a payload conceptually like:
Copy code
json
{
  "state": {
    "type": "PAUSED",
    "state_details": {
      "pause_key": "...",
      "pause_timeout": "...",
      "pause_reschedule": false
    }
  },
  "force": false
}
The server endpoint then runs orchestration logic and returns an
OrchestrationResult
indicating whether the proposed state transition was accepted, rejected, aborted, or delayed. So the key point is:
pause_flow_run()
does not directly “flip a DB flag” — it uses the same orchestrated state-transition API as other flow-run state changes.
--- ## How the paused state is represented At the API/model level, the run is paused by putting the flow run into a
PAUSED
state. That state includes metadata in
state_details
, especially: - `pause_key`: identifies the pause instance - `pause_timeout`: when the pause should expire - `pause_reschedule`: whether resume should go to
RUNNING
or to a scheduled/resuming path - `run_input_keyset`: where input schema/description/response are stored So the “signal” that the run is paused is literally: - the flow run’s current state type is
PAUSED
- and the pause-specific metadata lives in
state_details
--- ## What happens if
wait_for_input
is used This is the most interesting part. If you call something like:
Copy code
python
user_input = pause_flow_run(wait_for_input=MyInputModel)
the SDK needs to tell the API what kind of input is expected later. It does that by creating a flow run input keyset and storing schema-related records using the flow-run input API. That uses:
Copy code
text
POST /flow_runs/{id}/input
The stored records include things like: - schema
- description - later, the actual response Conceptually, the keys are derived from the pause state / pause key, with entries like: -
...-schema
-
...-description
-
...-response
So before the run is paused, the SDK persists enough metadata for the API to validate the eventual resume payload. ### Why this matters This means the API can enforce that a resume request provides input matching the expected schema, instead of blindly accepting arbitrary JSON. --- ## What the SDK does while the run is paused After the API accepts the
PAUSED
state, the SDK does not “return immediately and forget everything”. Instead, it waits by polling the flow run state periodically. Conceptually:
Copy code
text
while flow run state is PAUSED:
    sleep(poll_interval)
    fetch current flow run state
When the run leaves the paused state, the SDK continues. If
wait_for_input
was used, it then retrieves the stored response and returns it from
pause_flow_run()
. So from the flow author’s perspective, it behaves like a blocking pause point:
Copy code
python
approval = pause_flow_run(wait_for_input=Approval)
# execution resumes here after API-side resume
--- ## How resume works at the API level Resume happens through a dedicated endpoint:
Copy code
text
POST /flow_runs/{id}/resume
This is separate from
set_state
. The server does roughly this: 1. Confirms the flow run is currently paused 2. If input is supplied, finds the saved schema via the
run_input_keyset
3. Validates the provided input against that schema 4. Stores the validated response in flow run input storage 5. Transitions the run out of
PAUSED
If
pause_reschedule=False
(normal pause), resume transitions the run to something like: -
Running()
If
pause_reschedule=True
(more like suspend/reschedule behavior), resume may transition it through: -
Scheduled(name="Resuming")
That distinction matters because some pauses are meant to continue in-place, while others are intended to be rescheduled onto infrastructure. --- ## Resume with input: validation flow When input is passed to the resume endpoint, the API does schema-driven validation. The pattern is: 1. Read saved schema from flow-run input storage 2. Validate submitted
run_input
against schema 3. Reject the resume if validation fails 4. Save the validated response under the response key if validation succeeds So the API is not trusting the client to validate correctly; the validation happens server-side too. That’s a strong design choice because it keeps the pause/resume contract authoritative at the API layer. --- ## Endpoints involved The main API endpoints involved are: ### Pause via state orchestration
Copy code
text
POST /flow_runs/{id}/set_state
Used to propose the
PAUSED
state. ### Flow run input storage
Copy code
text
POST /flow_runs/{id}/input
Used to store: - schema - description - response ### Resume endpoint
Copy code
text
POST /flow_runs/{id}/resume
Used to: - validate any provided input - store the response - transition the flow run out of
PAUSED
--- ## Relevant models/concepts The important pieces are: -
Paused
state
The SDK-side state object used to represent a pause request. -
StateDetails
Holds pause metadata like: -
pause_key
-
pause_timeout
-
pause_reschedule
-
run_input_keyset
-
OrchestrationResult
Returned by the state orchestration endpoint to indicate whether the transition was accepted. - Flow run input records Used to store expected schema and actual resumed input. --- ## Practical summary In one sentence:
pause_flow_run()
uses the API by proposing a
PAUSED
state through
/flow_runs/{id}/set_state
, optionally persisting input schema via
/flow_runs/{id}/input
, then waiting until
/flow_runs/{id}/resume
transitions the run back out of pause.
Or even shorter: - Pause = orchestrated state transition to
PAUSED
- Input contract = stored as flow-run input records
- Resume = dedicated API endpoint that validates input and moves state forward --- ## A subtle but useful takeaway
pause_flow_run()
is not just a local Python control-flow primitive. It’s really an orchestrated API-backed checkpoint. That’s why it can support: - UI/API-driven resumes - validated human input - timeout handling - stateful pause identity via
pause_key
--- If you want, I can also give you: - a sequence diagram of the pause/resume API calls, or - a minimal code example showing
pause_flow_run(wait_for_input=...)
and what the corresponding API interactions look like.
t
and it's still printing when we paused it
note that the flow calls a task
@Marvin does this works in prefect 3.0.11
m
thought for 131.3 seconds
Yes —
pause_flow_run()
does exist and works in Prefect 3.0.11
. Based on the code and API for that version, 3.0.11 includes: -
pause_flow_run
-
resume_flow_run
-
POST /flow_runs/{id}/set_state
-
POST /flow_runs/{id}/resume
- flow-run input support for
wait_for_input=...
So the overall pause/resume mechanism I described does apply to 3.0.11. ## What is supported in 3.0.11 In 3.0.11,
pause_flow_run()
can: - pause a flow run by proposing a
PAUSED
state - optionally wait for typed input with
wait_for_input=...
- resume via the API/UI - validate resume input against stored schema - continue execution after resume ## Important caveats for 3.0.11 A few version-specific notes: - Default timeout is 1 hour (
3600
seconds), unless you override it. - There is a distinction between blocking pause and rescheduling/suspend-like behavior. - In 3.0.11, this functionality is implemented with the older
@sync_compatible
pattern internally; that’s fine, just an internal implementation detail. - One notable limitation in 3.0.11: pausing from within a task was not yet fixed there. That last point matters a lot: ### In 3.0.11, pausing from a task is a problem A later fix changed
pause_flow_run()
so it could pause the parent flow when called inside a task. That fix came later, not in 3.0.11. So in 3.0.11: - calling
pause_flow_run()
inside a flow → yes, expected to work - calling
pause_flow_run()
inside a task → likely not supported / may raise an error like
Cannot pause task runs
## Bottom line Yes, it works in 3.0.11 if you mean the standard flow pause/resume behavior. But if your use case is: - pausing from inside a task - or relying on newer UX fixes around pause input forms then 3.0.11 may be too old. ## Useful links - Prefect repo: Prefect on GitHub - Later fix for pause-from-task behavior: PR #19457 - Example related issue/bug around pause input UX in newer versions: Issue #21491 If you want, I can also answer this more concretely for your exact usage: - pause inside a flow - pause inside a task - pause with
wait_for_input
- resume via UI vs API