Can I use prefect as an API, so I let thirt party ...
# ask-marvin
m
Can I use prefect as an API, so I let thirt party raise the request and run a pipeline and as a response return some payload @Marvin
m
thought for 388.2 seconds
Short answer: yes. Prefect can orchestrate your pipeline when a third party makes a request, and you can return the flow’s returned value as the API response. Prefect isn’t itself an API gateway, but you can put a thin HTTP service in front of it that triggers a deployment, optionally waits for completion, then returns the payload. Two common patterns - Synchronous “request/response” (short-running flows): - Your API endpoint calls Prefect to start a flow run and waits up to a timeout. - If it finishes in time, return the flow’s return value as the HTTP response. - If not, return a 202 with a run_id so the caller can poll a status endpoint. - Async + callback/polling (long-running flows): - Start the flow run and immediately return a run_id. - Either: - Have the third party poll a status endpoint you provide, or - Accept a callback URL and have your flow (or a follow-up step) POST the result when done. Important prerequisites - Enable result persistence for the flows whose results you want to return. In Prefect 3.x, persistence is opt-in. - Recommended on the flow:
@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
Copy code
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))
state = fr.state payload = { "run_id": run_id, "state_type": state.type.value if state else None, "state_name": state.name if state else None, "message": state.message if state else None, } if state and state.is_final(): try: payload["result"] = await state.aresult() except Exception as e: payload["result_error"] = f"Could not load result: {e}" return payload ``` Notes for your AWS/ECS setup - Since your workers run on ECS (EC2 launch type), configure
result_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.