Madushan Pathirana
04/09/2026, 2:02 AMMarvin
04/09/2026, 2:02 AMMarvin
04/09/2026, 2:08 AM@flow(persist_result=True, result_storage="s3-bucket/<your-block>", result_serializer="json")
- Use a remote storage (e.g., S3) accessible from your API service. If results are stored on a worker’s local filesystem, your API won’t be able to load them later.
- Ensure your API service has access to the same result storage (e.g., IAM or credentials for the S3 bucket).
- Keep in mind HTTP timeouts. For long runs, prefer async + polling/callback.
Example: Flow that returns a JSON-serializable payload
from prefect import flow
@flow(
name="etl-flow",
persist_result=True,
result_storage="s3-bucket/my-results-block", # a saved S3Bucket block
result_serializer="json",
)
def etl_flow(x: int) -> dict:
# ... do work ...
return {"total": x + 1}
Example: FastAPI service that triggers a deployment and optionally waits
```
from uuid import UUID
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from prefect.deployments import arun_deployment
from prefect.client.orchestration import get_client
from prefect.client.schemas.objects import StateType
app = FastAPI()
class RunRequest(BaseModel):
deployment: str # "flow-name/deployment-name" or deployment UUID
parameters: dict | None = None # flow parameters
wait: bool = False # wait for completion?
timeout: float | None = 30.0 # seconds to wait (None = wait indefinitely)
@app.post("/run")
async def run_flow(req: RunRequest):
# Start the flow run; if not waiting, return immediately
try:
flow_run = await arun_deployment(
name=req.deployment,
parameters=req.parameters,
timeout=req.timeout if req.wait else 0, # 0 => return immediately
)
except Exception as e:
raise HTTPException(400, f"Failed to start run: {e}")
# If we didn't wait or it didn't finish within timeout, return 202 with run_id
if not req.wait or (req.timeout is not None and not flow_run.state.is_final()):
return {
"run_id": str(flow_run.id),
"state": flow_run.state.name if flow_run.state else None,
"detail": "Run started; still executing."
}
# Final state reached within timeout
state = flow_run.state
if state.type == StateType.COMPLETED:
try:
result = await state.aresult() # fetch the flow's returned value
except Exception as e:
raise HTTPException(500, f"Could not load result from storage: {e}")
return {"run_id": str(flow_run.id), "state": state.name, "result": result}
# Non-success final states
raise HTTPException(500, f"Run ended in state {state.name}: {state.message}")
@app.get("/runs/{run_id}")
async def get_status(run_id: str):
async with get_client() as client:
fr = await client.read_flow_run(UUID(run_id))Marvin
04/09/2026, 2:08 AMresult_storage on flows to an S3Bucket block in ap-southeast-1 and make sure your API service can read from that bucket. This avoids “Missing result” errors when your API tries to load the payload after the run finishes on ECS.
- For idempotency (avoid duplicate runs on retries), consider passing an idempotency_key to arun_deployment and mapping that to the incoming request ID.
- If your flows may take longer than your HTTP stack allows, prefer the async + polling/callback approach. You can also return a signed S3 URL or artifact reference to the third party instead of the full payload.
Relevant docs and API references
- Create a flow run from a deployment (REST): REST: create_flow_run_from_deployment
If you share:
- How long these runs typically take
- Whether you need strict request/response semantics vs. async
- Max payload size and format
…I can tailor the example (e.g., add idempotency, callback support, or artifact links) to your exact needs.