Running an app is asynchronous: you start a run and get a runId back immediately, then poll until the run completes. This page walks through the whole flow.
All examples assume you’ve set FLOWY_API and FLOWY_KEY from Authentication.
Find the app you want to run
List the apps in your workspace and grab an app’s id.curl "$FLOWY_API/apps" -H "Authorization: Bearer $FLOWY_KEY"
const res = await fetch(`${BASE}/apps`, {
headers: { Authorization: `Bearer ${KEY}` },
});
const { data: apps } = await res.json();
apps = requests.get(f"{BASE}/apps",
headers={"Authorization": f"Bearer {KEY}"}).json()["data"]
{ "data": [
{ "id": "507f1f77bcf86cd799439011", "title": "Headshot generator",
"published": true, "runCount": 42 }
] }
Look up the app's inputs
Fetch the app to see which inputs it expects. Each input’s nodeId is the key you’ll send in the run request.curl "$FLOWY_API/apps/APP_ID" -H "Authorization: Bearer $FLOWY_KEY"
const res = await fetch(`${BASE}/apps/APP_ID`, {
headers: { Authorization: `Bearer ${KEY}` },
});
const { data: app } = await res.json();
app = requests.get(f"{BASE}/apps/APP_ID",
headers={"Authorization": f"Bearer {KEY}"}).json()["data"]
{ "data": {
"id": "APP_ID", "title": "Headshot generator",
"inputs": [
{ "nodeId": "node_1", "label": "Your photo", "kind": "image_url", "required": true },
{ "nodeId": "node_2", "label": "Style", "kind": "text", "required": false }
],
"outputs": [ { "nodeId": "node_9", "label": "Headshot", "outputType": "image" } ]
} }
Start the run
POST to the app’s runs endpoint with an inputs map of nodeId → value. Text inputs take a string; media inputs take a URL. You get a runId back right away.curl -X POST "$FLOWY_API/apps/APP_ID/runs" \
-H "Authorization: Bearer $FLOWY_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"node_1": "https://example.com/me.jpg",
"node_2": "studio lighting, neutral background"
}
}'
const res = await fetch(`${BASE}/apps/APP_ID/runs`, {
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
inputs: {
node_1: "https://example.com/me.jpg",
node_2: "studio lighting, neutral background",
},
}),
});
const { data } = await res.json();
const runId = data.runId;
res = requests.post(f"{BASE}/apps/APP_ID/runs",
headers={"Authorization": f"Bearer {KEY}"},
json={"inputs": {
"node_1": "https://example.com/me.jpg",
"node_2": "studio lighting, neutral background",
}})
run_id = res.json()["data"]["runId"]
{ "data": { "runId": "64c3f2a1e8b9c0d1f2e3a4b5", "status": "queued" } }
Poll until it's done
Poll the run every couple of seconds until status is completed or failed. Output media URLs are signed and ready to download.curl "$FLOWY_API/runs/RUN_ID" -H "Authorization: Bearer $FLOWY_KEY"
async function waitForRun(runId) {
while (true) {
const res = await fetch(`${BASE}/runs/${runId}`, {
headers: { Authorization: `Bearer ${KEY}` },
});
const { data } = await res.json();
if (data.status === "completed") return data.outputs;
if (data.status === "failed") throw new Error(data.error);
await new Promise((r) => setTimeout(r, 2500));
}
}
import time
def wait_for_run(run_id):
while True:
data = requests.get(f"{BASE}/runs/{run_id}",
headers={"Authorization": f"Bearer {KEY}"}).json()["data"]
if data["status"] == "completed":
return data["outputs"]
if data["status"] == "failed":
raise RuntimeError(data["error"])
time.sleep(2.5)
{ "data": {
"runId": "RUN_ID", "appId": "APP_ID", "status": "completed",
"outputs": [
{ "nodeId": "node_9", "kind": "image", "url": "https://cdn.flowy…/headshot.png" }
]
} }
Output URLs are signed and expire. Download or copy the asset to your own storage soon after the run completes rather than storing the URL long‑term.