Santiago Gutierrez
09/17/2025, 6:55 AM/api/flow_run_states/{id} where I already have the flow run state id.Marvin
09/17/2025, 6:55 AMMarvin
09/17/2025, 7:01 AMdata field. If data is embedded JSON, that’s your serialized output. If data is a “persisted result” reference, you’ll need to fetch the payload from the configured result storage using the storage_key (and storage_block_id) shown in data. The Prefect REST API does not dereference and return persisted result bytes for you.
Links:
- Read flow run state (REST)
- Results and persistence in Prefect 3
How to do it
1) Fetch the flow run state by id
- Prefect Server:
curl -s -H "Authorization: Bearer $PREFECT_API_KEY" \
"$PREFECT_API_URL/api/flow_run_states/$STATE_ID"
- Prefect Cloud (note account/workspace scoping):
curl -s -H "Authorization: Bearer $PREFECT_API_KEY" \
"<https://api.prefect.cloud/api/accounts/$ACCOUNT_ID/workspaces/$WORKSPACE_ID/flow_run_states/$STATE_ID>"
You’ll get a JSON like:
{
"id": "f2f3…",
"type": "COMPLETED",
"name": "Completed",
"timestamp": "2025-09-17T12:34:56.789Z",
"message": null,
"state_details": { ... },
"data": <either inline JSON OR a persisted-result metadata object>
}
2) Case A: Inline/embedded result
If data is a plain JSON value/object (string, number, array, object), that is the serialized result already. For example:
{ ..., "data": "hello world" }
You can print as a string with jq:
curl -s ... | jq -r '.data'
3) Case B: Persisted result (stored externally)
If data is an object with fields like storage_key, storage_block_id, expiration, and serializer, Prefect persisted the result to external storage. Example shape:
"data": {
"storage_key": "results/2d5e.../return.json",
"storage_block_id": "b1a2c3d4-...-....",
"serializer": { "type": "json", ... },
"expiration": "2025-09-18T12:34:56.789Z",
"prefect_version": "3.x.y"
}
What to do next:
- Identify the storage from storage_block_id (e.g., Local filesystem, S3, GCS, Azure). Fetch the block document via the Blocks API (Cloud/Server path depends on your deployment) to see its configuration (e.g., basepath, bucket/container, credentials).
- Download the object at storage_key using that storage’s client/CLI.
- Local filesystem block: the payload will be at <basepath>/<storage_key>
- S3 block: aws s3 cp s3://<bucket>/<storage_key> -
- GCS block: gsutil cat gs://<bucket>/<storage_key>
- Azure block: az storage blob download --container-name <container> --name <storage_key> --file -
- Decode according to `serializer.type`:
- If json (the default for JSON-serializable values), the file content is already a JSON string/bytes; print it directly.
- If pickle/cloudpickle, the payload is a Python pickle; there isn’t a REST-only way to turn that into a human-readable string—you’d need Python to unpickle it.
Notes and gotchas
- If data is null, there is no stored result for that state. This can happen if results weren’t persisted. See Results and persistence for enabling persistence or configuring result storage.
- Make sure you’re querying the terminal “Completed” state (or the state that actually carried the result) for the flow run. Non-terminal states won’t have a final result.
- There is no dedicated REST endpoint that returns the persisted result bytes; dereferencing must be done via the storage indicated in data.
If you can share whether you’re on Prefect Cloud or Server and which result storage block you use (e.g., local, S3, GCS, Azure), I can give exact curl/CLI commands to fetch and print the serialized output.Santiago Gutierrez
09/17/2025, 7:07 AM"data": {
"storage_key": "<my_local_path>/.prefect/storage/<key>",
"expiration": null,
"serializer": {
"type": "pickle",
"picklelib": "cloudpickle",
"picklelib_version": null
},
"prefect_version": "3.4.1",
"storage_block_id": null
}Marvin
09/17/2025, 7:08 AMMarvin
09/17/2025, 7:13 AM<my_local_path>/.prefect/storage/<key>.
- Because it’s pickled, you’ll need Python in that environment to load and print it; there’s no REST-only way to turn pickle bytes into a string.
One-off: print the result as a string on the machine that has the file
python - <<'PY'
import sys, cloudpickle
p = sys.argv[1]
with open(p, "rb") as f:
obj = cloudpickle.load(f)
# If it’s a string, this will print it; otherwise you'll see its repr
print(obj)
PY "<my_local_path>/.prefect/storage/<key>"
If you just need the raw serialized bytes as a string (base64), you can do:
python - <<'PY'
import sys, base64
p = sys.argv[1]
print(base64.b64encode(open(p, "rb").read()).decode())
PY "<my_local_path>/.prefect/storage/<key>"
Prefer JSON for future runs (so you can read directly via REST or from storage)
- Use a JSON serializer and enable persistence at the flow (or task) level:
from prefect import flow, task
from prefect.serializers import JSONSerializer
@flow(result_serializer=JSONSerializer(), persist_result=True)
def my_flow():
return "hello world"
- Or set settings so all results use JSON and persist by default:
- PREFECT_RESULTS_DEFAULT_SERIALIZER=json
- PREFECT_RESULTS_PERSIST_BY_DEFAULT=true
With JSON, the persisted file is plain JSON and the REST data may be embedded JSON or point to a JSON file you can read with `curl`/`jq`.
If you need to fetch results from another machine, consider using a remote result storage block (S3/GCS/Azure) instead of local storage so you can `curl`/CLI the object directly.
Docs:
- Results and persistence
- Read flow run state (REST)
If you’d like, tell me whether the executing environment is accessible to you and I can give exact commands to print or copy that file; or if you want to switch to S3/GCS/Azure, I can share the minimal config changes.Santiago Gutierrez
09/17/2025, 7:32 AM<my_local_path>/.prefect/storage/<key> .
It seems the contents are just several encrypted lines of text. . . so cloudpickle library complains upon encountering the first line in the file:
ValueError: invalid literal for int() with base 10: 'XXXXXXXX'Marvin
09/17/2025, 7:32 AMMarvin
09/17/2025, 7:34 AM<my_local_path>/.prefect/storage/<key> isn’t a plain pickle file and cloudpickle.load() will fail.
Use Prefect’s ResultStore to read and deserialize it for you:
Option 1: Read with your current Prefect profile (same machine/env that wrote the file)
from prefect.results import get_result_store
store = get_result_store()
record = store.read("<my_local_path>/.prefect/storage/<key>") # use the exact storage_key you got from the API
print(record.result) # this is your original Python value
Option 2: If running from a different base path, point ResultStore at local storage explicitly
- If your storage_key is relative to a basepath, do this:
from prefect.filesystems import LocalFileSystem
from prefect.results import ResultStore
store = ResultStore(result_storage=LocalFileSystem(basepath="<my_local_path>"))
record = store.read(".prefect/storage/<key>") # note: key relative to basepath
print(record.result)
- If your storage_key is absolute (as shown in the API output), you can still pass it as-is to store.read() as in Option 1.
Tips
- You can confirm what you’re reading by inspecting record.metadata.serializer and record.metadata.storage_key.
- If you get a not-found error, verify the path exists and that you’re running with the same user/PREFECT_HOME that created it.
- If the value is a custom class, ensure compatible versions of Python and the class definitions are importable when unpickling.
For future runs: switch to JSON so the payload is plain text and easier to read via REST or disk
from prefect import flow
from prefect.serializers import JSONSerializer
@flow(result_serializer=JSONSerializer(), persist_result=True)
def my_flow():
return "hello world"
Or set:
- PREFECT_RESULTS_DEFAULT_SERIALIZER=json
- PREFECT_RESULTS_PERSIST_BY_DEFAULT=true
Docs:
- Results and persistence: https://docs-3.prefect.io/v3/advanced/results
- Read flow run state (REST): https://docs-3.prefect.io/v3/api-ref/rest-api/server/flow-run-states/read-flow-run-state
If you paste the exact storage_key shape (absolute vs relative) and confirm whether you’re on the same machine/profile, I can tailor the exact ResultStore call you need.